目录
写在前面
其实,很早之前就想总结一下静态内部类和非静态内部类部分的知识。还是动力不足吧,导致一直没有动手。然后这次应该属于百度的功劳,因为我搜到的相关文章都是东一句西一句的片面之词(一个无奈的表情自己体会)。被逼无奈,我只能自己动手了。分享这部分内容的同时,也算是自己也对这块儿知识做个完整的总结吧。
其实,对于一个Java程序员来说,非静态内部类和静态内部类是经常会见到的。有很多种情况,我们会去翻框架的源代码。
比如,捣鼓配置文件的时候,看源码应该是最快的,配置文件里面,一般就是通过构造函数的方式配置类,用以在合适的时候反射出实例。
还有使用新框架,学习新框架的时候,边实践边看源代码,应该是熟悉框架、上手框架最快的方式。
我想说的是:框架的源代码里面,会经常使用内部类。那么今天,我们就一起来学习下内部类。
其实,内部类非常简单。非静态内部类,我可以把它当成是外部类的一个成员变量;静态内部类,可以把它当成外部类的一个静态变量。而又由于它们是类,所以又具有类的属性。
内部类的理解
在面向对象编程里面,我们经常会使用类(class)。那class我们用的好好的,为什么还要去定义内部类呢?JDK的设计者为啥还要允许内部类呢?
其实,在JDK1.1版本开始,Java就已经支持内部类了(不要吃惊哦,把嘴巴闭上,大惊小怪,没见过世面的样子)。首先,大家先假想一下,如果自己是JDK的设计者,那么我们在设计类时。在一个类里面,可以包括静态的有静态代码块、静态变量、静态方法;非静态的有代码块、成员变量、非静态方法,以及构造函数。那么接下来,我们当然会想让它也会包含自己,也就是包含类。这样想就很明白了,因为这才是一个完整的思考。如此,内部类就顺理成章的被安排了~~
那这时,肯定有些童鞋就会问了:那内部类里面,再包含内部类可不可以呢?如果可以的话,那么在内部类的内部类里面,再定义内部类,也是可以的呗?
答案是可以的,大家也可以自己尝试的。真的是可以的。不过大多数业务场景,并不需要酱紫去实现吧,所以比较少见。
所以,内部类也是一种类,它跟正常的外部类,在本质上没有区别。内部类编译的时候,静态部分,还是会放在方法区;栈帧需要多大的局部变量表、多深的操作数栈也会在编译期确定。非静态的内容,对象依然会分配在堆上;引用类型,Java基本基本类型数据,依然会直接分配在栈内存;方法调用、方法执行时,依然会生成栈帧,进行压栈、出栈操作~~
只不过,内部类放在了外部类的里面,需要遵守一些外部类的约束,如此而已。
明白了这些,问题就简单多了,我们仅仅需要关注一下内部类区别于外部类的地方,就可以了;其他的,与外部类,完全一致。
PS:本文讨论,限Java7版本。Java8为了安全,终于去掉了方法区。原来存在方法区的内容转移到了堆上。在Java之前的版本中,必须根据相应的业务场景,对JVM参数进行合理设置,不然很容易出现方法区OOM的情况。这部分内容,大家有兴趣,后续我可以专门找时间进行讲解(过时了都,感兴趣的人估计不多~~)。
一句话总结内部类
非静态内部类是外部类的对象相关,属于该外部类的某个对象;静态内部类是外部类的类相关,即属于整个外部类。
内部类的好处:
1、提供了更好的封装,可以把内部类隐藏在外部类之内,不允许同一个包中的其他类访问;
2、内部类成员可以直接访问外部类的私有数据,因为内部类被当做其外部类成员,同一个类的成员之间可以相互访问。但是外部类不能访问内部类的实现细节。例如内部类的成员变量;
3、匿名内部类适合用于创建那些仅需要一次使用的类。
内部类分类
内部类分为了两种:静态内部类和非静态内部类。
非静态内部类
非静态内部类,其实很简单。对内,非静态内部类可以行使作为类的权利;对外,内部类保存了一个它所寄生的外部类对象的引用(此处,大家联想聚合关系。并与组合聚合相对比)。也就是说:内部类的方法可以通过此引用,引用外部类的private属性。(当调用非静态内部类的实例方法时,必须有一个非静态内部类实例,非静态内部类实例必须寄生在外部类实例里)
当在非静态内部类的方法内访问某个变量时,
系统优先在该方法内查找是否存在该名字的局部变量,如果存在就是用该变量;
如果不存在,则到该方法所在的内部类中查找是否存在该名字的成员变量,如果存在则使用该成员变量;
如果不存在,则到该内部类所在的外部类中查找是否存在该类名字的成员变量,如果存在则使用该成员变量;
如果依然不存在,系统将出现编译错误:提示找不到变量。
看一个例子:
public class Outer {
private double w;
public Outer(){}
public Outer(double w){ this.w = w; }
private class Inner {
private double l;
private String c;
public Inner(){}
public Inner(double l,String c){
this.l = l;
this.c = c;
}
public double getL() { return l; }
public void setL(double l) { this.l = l; }
public String getC() { return c; }
public void setC(String c) { this.c = c; }
public void info(){
// 此处可以不通过this,Outer.this 指定具体属性,默认会按照从内到外的顺序查找
System.out.println("内部类属性,l="+this.l+"c="+this.c);
System.out.println("外部类属性,w="+Outer.this.w);
}
}
public void test(){
Inner inner = new Inner(1.1D,"Hello");
inner.info();
}
public static void main(String[] args) {
Outer outer = new Outer(2.5D);
outer.test();
}
}
这一段代码很简单,外部类Outer,在其内部有非静态内部类Inner,外部类方法test(),实例化了内部类Inner对象,调用内部类方法。内部类Inner的info()方法,可以调用内部类Inner的私有属性,也可以调用外部类Outer的私有属性。外部类main使用该类时,通过调用外部类的test()方法,使用内部类方法。
在内存中,创建的对象示意图如下:
非静态内部类的成员可以访问外部类的private成员,但反过来就不成立了。非静态内部类的成员只在非静态内部类范围内是可知的,并不能被外部类直接使用。如果外部类需要访问非静态内部类的成员,则必须显示创建非静态内部类对象来调用访问器其实例成员。
非静态内部类对象必须寄生在外部类对象里,而外部类对象则不必一定有非静态内部类对象寄生其中。简单说:如果存在一个非静态内部类对象,则一定存在一个被它寄生的外部类对象。但外部类对象存在时,外部类对象里不一定寄生了非静态内部类对象。因此外部类对象访问非静态内部类成员时,可能非静态普通内部类对象根本不存在。而非静态内部类对象访问外部类成员时,外部类对象一定存在。
规定:静态成员不能访问非静态成员。外部类的静态方法、静态代码块不能访问非静态内部类。包括不能使用非静态内部类定义变量、创建实例。总之,不允许在外部类的静态成员中直接使用非静态内部类。
非静态内部类里不能有静态方法、静态成员变量、静态初始化块。
PS:静态成员不能访问非静态成员,是跟类的加载顺序有关系。因为相比于非静态成员,静态成员会优先加载。如果静态成员可以访问非静态的成员,那么非静态成员一定会访问到未加载的非静态成员,一定为null。
静态内部类
静态内部类不能访问外部类的实例成员,只能访问外部类的类成员。即使是静态内部类的实例方法,也不能访问外部类的实例成员,只能访问外部类的静态成员。
也就是说,静态内部类对象,不是寄生在外部类的实例中,而是寄生在外部类的类本身中。当静态内部类对象存在时,并不存在一个被它寄生的外部类对象,静态内部类对象只持有外部类的类引用,没有持有外部类对象的引用。所以,静态内部类的实例方法,只能访问外部类的类属性,不能访问外部类的实例属性。
静态内部类是外部类的一个静态成员,因此外部类的所有方法、所有初始化快中可以使用静态内部类来定义变量、创建对象等。
外部类依然不能直接访问静态内部类的成员,但可以使用静态内部类的类名作为调用者来访问静态内部类的类成员,也可以使用静态内部类对象作为调用者来访问静态内部类的实例成员。
下面看一个例子:
public class StaticOuter {
private int prop1 = 5;
private static int prop2 = 9;
public static class StaticInner{
private int age;
private static int n;
public StaticInner(int age){ this.age = age;}
public void accessOuterProp(){
//静态内部类,不可访问外部类非静态属性prop1
System.out.println("外部类静态属性prop2="+StaticOuter.prop2);
System.out.println("静态内部类对象属性age="+this.age);
System.out.println("静态内部类静态属性n="+StaticInner.n);
}
}
public static void main(String[] args) {
StaticOuter.StaticInner inner = new StaticOuter.StaticInner(12);
inner.accessOuterProp();
}
}
由上例可知,静态内部类内部,可以定义静态成员,也可以定义非静态成员。静态内部类的非静态方法,可以调用内部类的非静态成员,但是不可以调用外部类的非静态方法(同上面提到的类加载顺序);但是,可以调用内部类的静态属性,已经外部类的静态属性。
其对应的堆内存如下:
PS:关于类的静态成员与成员变量内存分布进行说明:编译之后,静态的属性,应该分配在方法区中;而成员变量,是在类初始化后,跟随对象,分配在堆上。
总结
非静态内部类,可以当做外部类的成员变量来处理,这很好理解。非静态内部类持有外部类对象的引用,故可以调用外部类某个对象的方法、变量。与外部类同生共死,同生命周期。
PS:非静态内部类中,可以调用外部类的静态变量和非静态变量。原因很简单,还是上面提到的类加载的问题。静态变量率先被加载,非静态方法调用静态方法时,其静态方法早已经初始化好了。
静态内部类,可以当做外部类的静态成员来处理。静态内部类,持有外部类的引用。这里该如何理解呢?我们通过上面静态内部类实例中的堆内存图进行说明。其实这是一层很弱的关系。很多网友的文章中说,静态内部类跟正常类没有太大的区别,也正是这个原因。
静态内部类,是可以new的。Java中new关键字一旦出现,就必定会分配堆内存。也就说,虽名为静态内部类,它其实在封装、使用上,跟普通的类更加相像。所以,从类含义上讲,叫它放在类内部的正常类,更加合适。
PS:所以在此,正式为静态内部类辟谣一下。静态内部类,是可以new的,是可以存在多对象的。关于更加复杂的内存分析、线程安全等问题,大家理解此文之后,自可回去私自考证。
参考资料:《Java疯狂讲义》