匿名内部类 与 局部变量
匿名类不能访问外部类方法中的局部变量,除非变量被声明为final类型;
通常在一个方法中, 有局部变量和临时创建的匿名类, 使用时:
Integer i = 8848;
new Thread(
() -> {
Integer j = i; // 报错 i 无法使用
System.out.println("j = " + j);
})
.start();
i = 996;
这样除非声明的变量 i 为 final (或者后续不再使用, 编译器会自动转换为 final), 不然无法在内部类中操作局部变量 i .
其实就是内部类的对象在使用它,内部类对象生命周期中都可能调用它, 而内部类试图访问外部方法中的局部变量时,外部方法的局部变量很可能已经不存在了,那么就得延续其生命, 拷贝到内部类中,而拷贝会带来不一致性,从而需要使用 final 声明保证一致性。
此时,内部类会自动拷贝外部变量 的引用,为了避免: 内部类得到的引用值不一致、内部类修改引用,而导致外部方法的参数值在修改前和修改后不一致.
如果是外部类的成员变量就不需要是 final 的,因为内部类本身都会含有一个外部类的引用(外部类.this),所以回调的时候一定可以访问到.
匿名内部类 与 成员变量
- 成员变量,通过匿名类的构造函数,复制了一份实例的地址引用,去调用实例的属性 (属性为 private 也行),保证了变量改变时数据引用的一致性,不用加 final;
- 全局变量同理(static),在匿名内部类里,可以直接通过类直接访问变量,可以保证变量引用的一致性,所以不用加 final;
匿名内部类定义
- 匿名内部类: 在类的成员方法内,同时完成定义和实例化的类。拥有语法糖,编译器帮忙创建类,这个匿名在编译后其实已经有名字了,通过这个编译生成的类名,去创建对象,跟正常的实现类的流程是一样的。
ch.pipeline()
.addLast(
new ChannelDuplexHandler() {
// 处理 读写之外的 特殊的事件
@Override
public void userEventTriggered(
ChannelHandlerContext ctx, Object evt) throws Exception {
IdleStateEvent event = (IdleStateEvent) evt;
// 是否 读超时
if (event.state() == IdleState.WRITER_IDLE) {
ctx.writeAndFlush(new PingMessage());
}
}
});
// 又或者这样的
return ()-> {};
-
原因: 编译程序实现上的困难, 内部类对象的生命周期会超过局部变量的生命周期。
- 局部变量的生命周期:当该方法被调用时,该方法中的局部变量在栈中被创建,当方法调用结束时,退栈,这些局部变量全部死亡。
- 而内部类对象生命周期与其它类一样:自创建一个匿名内部类对象,系统为该对象分配内存,直到没有引用变量指向分配给该对象的内存,它才会死亡(被JVM垃圾回收)。
-
所以完全可能出现的一种情况是:成员方法已调用结束,局部变量已死亡,但匿名内部类的对象仍然活着。如果匿名内部类的对象访问了同一个方法中的局部变量,就要求只要匿名内部类对象还活着,那么栈中的那些它要所访问的局部变量就不能“死亡”。
过程
匿名内部类对象访问同一个方法中被定义为final类型的局部变量。定义为 final 后,编译程序的实现方法:对于匿名内部类对象要访问的所有final类型局部变量,都拷贝成为该对象中的一个数据成员。这样,即使栈中局部变量已死亡,但被定义为final类型的局部变量的值永远不变,因而匿名内部类对象在局部变量死亡后,照样可以访问final类型的局部变量,因为它自己拷贝了一份,且与原局部变量的值始终一致。