一次搞定内部类
介绍
内部类,也叫嵌套类(Nested Classes),主要包含:成员内部类、局部内部类、匿名内部类和静态内部类。
Java的内部类也是一个语法糖,它仅仅是一个编译时的概念,outer.java里面定义了一个内部类inner,一旦编译成功,就会生成两个完全不同的.class文件了,分别是outer.class和outer$inner.class。所以内部类的名字完全可以和它的外部类名字相同。
这里注意静态内部类的.class没有outer$的引用。
静态和非静态的区别:
1.非静态内部类能够访问外部类的静态和非静态成员。静态类不能访问外部类的非静态成员。他只能访问外部类的静态成员。
2.内部静态类不需要有指向外部类的引用。但非静态内部类需要持有对外部类的引用。
image.pngimage.png
effective final 例子:
public class EffectivelyFinalDemo
{
public static void main(String[] args)
{
//局部内部类和匿名内部类访问的局部变量必须由final修饰,java8开始,可以不加final修饰符,由系统默认添加
//因此下面两句的效果是一样的
//final int age=99;
int age=99;
//运行代码 <1>将会抛出以下错误
//EffectivelyFinalDemo.java:14: 错误: 从内部类引用的本地变量必须是最终变量或实际上
//的最终变量
//age=11; <1>
A a=new A()
{
public void test()
{
//Cannot refer to a non-final variable age
//inside an inner class defined in a different method
System.out.println(age);
}
};
a.test();
}
}
//接口
interface A
{
void test();
}
还需要了解
变量引用的例子:
public class ShadowTest {
public int x = 0;
class FirstLevel {
public int x = 1;
void methodInFirstLevel(int x) {
System.out.println("x = " + x);
System.out.println("this.x = " + this.x);
System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
}
}
public static void main(String... args) {
ShadowTest st = new ShadowTest();
ShadowTest.FirstLevel fl = st.new FirstLevel();
fl.methodInFirstLevel(23);
}
}
输出结果:
x = 23
this.x = 1
ShadowTest.this.x = 0
为什么内部类调用的外部变量必须是final修饰的
回答一:因为生命周期的原因。方法中的局部变量,方法结束后这个变量就要释放掉,final保证这个变量始终指向一个对象。首先,内部类和外部类其实是处于同一个级别,内部类不会因为定义在方法中就会随着方法的执行完毕而跟随者被销毁。问题就来了,如果外部类的方法中的变量不定义final,那么当外部类方法执行完毕的时候,这个局部变量肯定也就被GC了,然而内部类的某个方法还没有执行完,这个时候他所引用的外部变量已经找不到了。如果定义为final,java会将这个变量复制一份作为成员变量内置于内部类中,这样的话,由于final所修饰的值始终无法改变,所以这个变量所指向的内存区域就不会变。 为了解决:局部变量的生命周期与局部内部类的对象的生命周期的不一致性问题
回答二:看stackoverflow上的一个讨论 http://stackoverflow.com/questions/3910324/why-java-inner-classes-require-final-outer-instance-variables
stackoverflow里最高票的答案说到,当主方法结束时,局部变量会被cleaned up 而内部类可能还在运行。当局部变量声明为final时,当使用已被cleaned up的局部变量时会把局部变量替换成常量:
The compiler can then just replace the use of lastPrice and price in the anonymous class with the values of the constants (at compile time, ofcourse)
也就是说当变量是final时,编译器会将final局部变量"复制"一份,复制品直接作为局部内部中的数据成员.这样,当局部内部类访问局部变量时,其实真正访问的是这个局部变量的"复制品"。因此:当运行栈中的真正的局部变量死亡时,局部内部类对象仍可以访问局部变量(其实访问的是"复制品")。而且,由于被final修饰的变量赋值后不能再修改,所以就保证了复制品与原始变量的一致。给人的感觉:好像是局部变量的"生命期"延长了。这就是java的闭包。
而java8的lambda 表达式之所以不用写final,是因为Java8这里加了一个语法糖:在lambda表达式以及匿名类内部,如果引用某局部变量,则直接将其视为final。本质并没有改变。
为什么要使用内部类
原因主要有以下四点:
1.内部类可以很好的实现隐藏
一般的非内部类,是不允许有 private 与protected权限的,但内部类可以
2.内部类拥有外围类的所有元素的访问权限
3.可是实现多重继承
4.可以避免修改接口而实现同一个类中两种同名方法的调用。
其中最主要的是第三点,实现多继承,
在《Think in java》中有这样一句话:使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
因为Java不支持多继承,支持实现多个接口。但有时候会存在一些使用接口很难解决的问题,这个时候我们可以利用内部类提供的、可以继承多个具体的或者抽象的类的能力来解决这些程序设计问题。可以这样说,接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整。
大家都知道Java只能继承一个类,它的多重继承在我们没有学习内部类之前是用接口来实现的。但使用接口有时候有很多不方便的地方。比如我们实现一个接口就必须实现它里面的所有方法。而有了内部类就不一样了。它可以使我们的类继承多个具体类或抽象类。
必须有个例子(成员局部类为例):
package insidecategory;
public class Example1 {
public String name()
{
return "liutao";
}
}
package insidecategory;
public class Example2 {
public int age()
{
return 25;
}
}
package insidecategory;
public class MainExample
{
private class test1 extends Example1
{
public String name()
{
return super.name();
}
}
private class test2 extends Example2
{
public int age()
{
return super.age();
}
}
public String name()
{
return new test1().name();
}
public int age()
{
return new test2().age();
}
public static void main(String args[])
{
MainExample mi=new MainExample();
System.out.println("姓名:"+mi.name());
System.out.println("年龄:"+mi.age());
}
}
注意看类3,里面分别实现了两个内部类 test1,和test2 ,test1类又继承了Example1,test2继承了Example2,这样我们的类三MainExample就拥有了Example1和Example2的方法和属性,也就间接地实现了多继承。
什么情况下使用局部内部类
如果我们在用一个内部类的时候仅需要创建它的一个对象并创给外部,就可以这样做。
看一个例子:
public interface Destination {
String readLabel();
}
局部内部类
public class Goods1 {
public Destination dest(String s) {//dest中我们定义了一个内部类
class GDestination implements Destination {
private String label;
private GDestination(String whereTo) {
label = whereTo;}
public String readLabel() { return label; }}
return new GDestination(s);//最后由这个方法返回这个内部类的对象
}
public static void main(String[] args) {
Goods1 g= new Goods1();
Destination d = g.dest("Beijing");}
}
内部类加载时机
-
内部类和静态内部类都是延时加载的,也就是说只有在明确用到内部类时才加载。只使用外部类时不加载。
-
外部类初次加载,会初始化静态变量、静态代码块、静态方法,但不会加载内部类和静态内部类。
-
实例化外部类,调用外部类的静态方法、静态变量,则外部类必须先进行加载,但只加载一次。
-
直接调用静态内部类时,外部类不会加载。
-
一个加载顺序是 静态代码块,主函数(如果有),普通代码块,构造函数, 普通方法
具体参考:https://blog.csdn.net/dreamsever/article/details/79686008
非静态内部类的引用导致的内存泄漏问题
非静态内部类会持有外部类的引用,如果内部类的生命周期比外部类的生命周期长,则会造成外部类对象无法销毁,导致内存泄漏,典型的就是android中handler的使用,handler初始化是一个内部类,持有acitivity的引用,会造成内存泄漏。
参考:https://blog.csdn.net/zhrx26/article/details/51152342
参考:
https://blog.csdn.net/mid120/article/details/53644539
https://www.jianshu.com/p/f55b11a4cec2
https://blog.csdn.net/tianxiaoqi2008/article/details/7266264
https://blog.csdn.net/hikvision_java_gyh/article/details/8963562
https://www.cnblogs.com/dolphin0520/p/3811445.html https://blog.csdn.net/u010454030/article/details/80548732
https://blog.csdn.net/shaw1994/article/details/48378471