最近在准备面试,把知识点复习一遍,整理出的笔记记录下,里面会穿插代码和面试例题。
内容不是原创,是总结和收集,并在理解的基础上进行一些完善,如果侵权了请联系作者,若有错误也请各位指正。因为收集的时候忘记把来源记录下来了,所以就不po出处了,请见谅(这是个坏习惯,一定改)。
将一个类的定义放在另一个类的定义内部,这就是内部类。
内部类作为外部类的一个成员,并且依附于外部类而存在的。内部类可为静态,可用protected和private修饰(而外部类只能使用public和缺省的包访问权限)。内部类主要有以下几类:成员内部类、局部内部类、静态内部类、匿名内部类。
成员内部类是最普通的内部类,它的定义为位于另一个类的内部,形如下面的形式:
class Outter {
class Inner { //内部类
public void Inner() {
System.out.println("Inner");
}
}
}
不过要注意的是,当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:
外部类.this.成员变量
外部类.this.成员方法
虽然成员内部类可以无条件地访问外部类的成员,而外部类想访问成员内部类的成员却不是这么随心所欲了。在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问:
class Outter {
private double num = 0;
public Outter(double num) {
this.num = num;
getInnerInstance().inner(); //必须先创建成员内部类的对象,再进行访问
}
private Inner getInnerInstance() {
return new Inner();
}
class Inner { //内部类
public void inner() {
System.out.println("Inner");
}
}
}
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。注意,局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。
class People{
public People(){}
}
class Asian{
public Asian(){}
public People getChinese(){
class Chinese extends People{//局部内部类
int age = 18;
}
return new Chinese();
}
}
匿名内部类应该是平时我们编写代码时用得最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更加容易维护。
public class Test {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
public void run() {
}
});
t.start();
//Java8新特性 Lambda表达式
new Thread(()->{System.out.println("多线程任务执行!")},"t").start();
}
}
静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。
public class Test {
public static void main(String[] args) {
Outter.Inner inner = new Outter.Inner();
}
}
class Outter {
public Outter() {
}
static class Inner {
public Inner() {
}
}
}
通过反编译字节码文件看看究竟。事实上,编译器在进行编译的时候,会将成员内部类单独编译成一个字节码文件。下面是Outter.java的代码:
public class Outter {
private Inner inner = null;
public Outter() {
}
public Inner getInnerInstance() {
if(inner == null)
inner = new Inner();
return inner;
}
protected class Inner {
public Inner() {
}
}
}
编译之后,出现了两个字节码文件:
反编译Outter$Inner.class文件得到下面信息:
第11行到35行是常量池的内容,下面看看第38行的内容:
final com.inner.test2.Outter this$0;
这行是一个指向外部类对象的指针,看到这里想必大家豁然开朗了。也就是说编译器会默认为成员内部类添加了一个指向外部类对象的引用,那么这个引用是如何赋初值的呢?下面接着看内部类的构造器:
public com.inner.test2.Outter$Inner(com.inner.test2.Outter);
从这里可以看出,虽然我们在定义的内部类的构造器是无参构造器,编译器还是会默认添加一个参数,该参数的类型为指向外部类对象的一个引用,所以成员内部类中的Outter this&0 指针便指向了外部类对象,因此可以在成员内部类中随意访问外部类的成员。从这里也间接说明了成员内部类是依赖于外部类的,如果没有创建外部类的对象,则无法对Outter this&0引用进行初始化赋值,也就无法创建成员内部类的对象了。
当类中,或者方法中定义了一个局部变量,当匿名(局部)内部类使用该变量时(比如线程方法),变量生命周期随着外部类一起被回收,而内部类不一定结束了,Java采用了 复制 的手段来保证变量的可用,即对变量进行一份拷贝,内部类使用的变量是另一个局部变量,只不过值和方法中局部变量的值相等。
如果局部变量的值在编译期间就可以确定,则直接在匿名内部里面创建一个拷贝。如果局部变量的值无法在编译期间确定,则通过构造器传参的方式来对拷贝进行初始化赋值。
但是这个方式会存在数据不一致问题,意思是当源数据改变了,而拷贝的数据却还保留原值的情况。所以为了解决这个问题,java编译器就限定必须将变量限制为final变量,不允许对变量进行更改(对于引用类型的变量,是不允许指向新的对象),这样数据不一致性的问题就得以解决了。
在《Think in java》中有这样一句话:
使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
在我们程序设计中有时候会存在一些使用接口很难解决的问题,这个时候我们可以利用内部类提供的、可以继承多个具体的或者抽象的类的能力来解决这些程序设计问题。可以这样说,接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整。
(1)外部类的引用的持有。
(2)生存周期不同。
(3)普通内部类不能声明static的方法和变量。
结构上来说
从JVM加载角度来说
Java类代码加载的顺序:
1)先加载类,然后执行static变量初始化,接下来执行对象的创建。
2)java中的类只有在被用到的时候才会被加载。
3)java类只有在类字节码被加载后才可以被构造成对象实例。
4)初始化构造时,先父后子;只有在父类所有都构造完后子类才被初始化。
5)静态的只在加载字节码时执行一次,即在对象实例第一次new的时候执行且只执行一次;非静态new多少次就会执行多少次。
内部类是延时加载的,也就是说只会在第一次使用时加载。不使用就不加载,所以可以很好的实现单例模式。不论是静态内部类还是非静态内部类都是在第一次使用时才会被加载。
成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象
public class Test {
public static void main(String[] args) {
//成员内部类第一种方式:
Outter outter = new Outter();
Outter.Inner inner = outter.new Inner(); //必须通过Outter对象来创建
//成员内部类第二种方式:
Outter.Inner inner1 = outter.getInnerInstance();
//静态内部类
Outter.Inner2 inner2 = new Outter.Inner2();
}
}
class Outter {
private Inner inner = null;
public Outter() {
}
public Inner getInnerInstance() {
if(inner == null)
inner = new Inner();
return inner;
}
class Inner {
public Inner() {
}
}
static class Inner2{
public Inner2() {
}
}
}
关于成员内部类的继承问题。一般来说,内部类是很少用来作为继承用的。但是当用来继承的话,要注意两点:
class WithInner {
class Inner{
}
}
class InheritInner extends WithInner.Inner {
// InheritInner() 是不能通过编译的,一定要加上形参
InheritInner(WithInner wi) {
wi.super(); //必须有这句调用
}
public static void main(String[] args) {
WithInner wi = new WithInner();
InheritInner obj = new InheritInner(wi);
}
}