图片于2018.5月厦门团建拍摄
这是在网上看到的一个观点说final可增加局部变量的生命周期,当然这个观点本人不认为正确。变量的生命周期和GC相关,而GC有个java对象可达性算法有关,final没有这功能。
局部变量如果没有用final修饰,他的生命周期和方法的生命周期是一样的,当方法弹栈,这个局部变量也会消失,那么如果局部内部类对象还没有马上消失想用这个局部变量,就没有了,如果用final修饰会在类加载的时候进入常量池,即使方法弹栈,常量池的常量还在,也可以继续使用。
1.java参数传递类型
1、基本类型作为参数传递时,是传递值的拷贝,无论你怎么改变这个拷贝,原值是不会改变的
2、对象作为参数传递时,是把对象在内存中的地址拷贝了一份传给了参数。
2.final修饰变量的含义
- final当修饰基本数据类型时表示不可改变其值
- final当修饰引用类型变量时表示不可改变引用变量的引用地址
上面的两个校验都是编译器做的检验,如果final的使用不符合上述两种规则,将编译不通过。
3.编译器对匿名内部类的操作
编译器在编译时会将匿名内部类引用的局部变量和方法参数以及外部类的引用都会被当做该构造函数的参数
结论
匿名内部类的使用方式,让开发者可以理解为当操作外部变量时(局部变量和方法参数)应该可以生效的。但是基于上述的1,3两点是做不到的。因为匿名内部类在构造时传入的是值的拷贝,即使操作也只是操作的拷贝数据。不是真正的外部变量。所以对于局部变量和方法参数需要加final表示不可以改变值。但是匿名内部类操作外部类成员变量是可以的,因为匿名内部类可以拿到外部类的真实引用去操作。
匿名内部类使用外部成员变量
public class Outer {
private int x =100;
public int add() {
Inner inner = new Inner() {
@Override
public int add() {
x = x +1;
return x;
}
};
return inner.add();
}
private interface Inner {
int add();
}
}
我们使用javac命令对上述代码编译可以看到匿名内部类实际上会产生匿名内部类的单独class文件。
class Outer$1 implements Inner {
Outer$1(Outer var1) {
this.this$0 = var1;
}
public int add() {
Outer.access$002(this.this$0, Outer.access$000(this.this$0) + 1);
return Outer.access$000(this.this$0);
}
}
为什么匿名内部类可以访问外部类成员变量,是因为编译器在编译的时候会将外部类的引用传进来。所以可以正常的操作外部类的变量,操作外部类的变量会直接影响到外部类。
匿名内部类使用局部变量
public class Outer {
private int x =100;
public int add(final int x) {
final int y = 1;
Inner inner = new Inner() {
@Override
public int add() {
return x+y;
}
};
return inner.add();
}
private interface Inner {
int add();
}
}
同样看通过javac命令看产出的匿名内部类是什么样的。
class Outer$2 implements Inner {
Outer$2(Outer var1, int var2) {
this.this$0 = var1;
this.val$x = var2;
}
public int add() {
return this.val$x + 1;
}
}
可以看到匿名内部类的构造方法增加了两个参数,外部类引用和int类型的x。y被省略掉是因为y相当于常量编译器优化直接替换为1,已可以理解为内联。