java 匿名了 final_Java匿名类遇上final

时间: 2018/10/19

Content

final的普通语义

final遇见内部类

闭包

内存泄漏

1. final的普通语义

关于Java中final关键字的常规语义就是表明其修饰的对象是不可变的, 被修饰的对象通常有. 值变量,引用变量,类,函数。此处需要注意的是,如果final修饰的是引用变量,那么引用变量的值(地址)不可变,但是引用变量值对应的对象实可以变的。

分别介绍一下修饰不同对象的情况:

1). 值变量

2). 引用变量

3). 类

4). 方法

// 1)

final int a = 1;

a = 1; // 这个语句会报错,不允许修改

// 2)

final Map map = new HashMap();

map.put("key", "value"); // map值(地址)对应对象(在堆)可以被修改,一般认为对象被生成以后,其地址就是确定了

map = new HashMap(); // 会报错, map值(地址)不允许修改

// 3)

final class Cls{

// .....

}

class SubCls extends Cls{ //编译报错,Cls不能为继承

// ....

}

// 4)

class Parent{

public final void method(){

// ...

}

}

class Child extends Parent{

public void mehtod(){ // 编译报错,不允许覆盖

// ..

}

}

2. final遇见内部类

Java中要求如果方法中定义的中类如果引用方法中的局部变量,那要要求局部变量必须要用final修饰(JDK8中已经不需要,但是本质也是和final类似——只读),实例代码如下:

interface Inner{

void method();

}

class Outer{

public Inner createInner(){

final int a = 12;

final Map map = new HashMap();

Inner inner = new Inner(){

public void method(){

int b = a + 1;

System.out.println(" in Inner, b=" + b);

map.put("innerKey", "innerValue");

}

};

System.out.println("in Outer, createInner finish!");

return inner;

}

public static void main(String []args){

Inner inner = new Outer().createInner();

inner.method();

}

}

输出如下:

in Outer, createInner finish!

in Inner, b=13

Note: 上述代码仅仅是展示使用,其中createInner()方法中的map变量是存在内存泄漏的,因为外界无法访问他,但是却会被一致持有。关于内存泄漏的问题,通过查看上述代码便后的class文件的内容即可发现。

上述文件编译后,生成了三个文件

Inner.class

Outer.class

Outer$1.class

打开Outer$1.class可以看到如下内容:

class Outer$1 implements Inner {

Outer$1(Outer this$0, int var2, Map var3) {

this.this$0 = this$0;

this.val$a = var2;

this.val$map = var3;

}

public void method() {

int b = this.val$a + 1;

System.out.println(" in Inner, b=" + b);

this.val$map.put("innerKey", "innerValue");

}

}

可以看到编译后的内容,Inner匿名类拥有另一个带有三个参数的构造方法,

Outer this$0: 也就是拥有了Outer(外部类)当前对象的一个引用,所以我们Inner的子类中,可以通过Outer.this访问外部Outer类的当前实例。

var2: 此处应该为Outer createInner()方法中的局部变量a

var3: 此处应该为Outer createInner()方法中的局部变量Map

通过上述编译后的代码,我们大概可以明白为什么匿名类可以访问其外部数据的原因,接下来我们可以讨论一下为什么要对createInner()中的局部变量a, map用final进行修饰。

网络上有很多人说是生命周期的问题,但是我觉得不是这个原因,也觉得不存在生命周期的问题(欢迎讨论)。

为了简化表述,以下将Inner匿名类里面的a表述为Inner().a, 将createInner()方法中的a表示为 createInner.a.

通过编译后的代码可以看出来,Inner().a和createInner.a不是同一个对象(在内存中不是同一个), 同样的两个map(值,存在于堆)在内存中也是不同的,但是两个map的都指向了堆上的同一个HashMap对象。理论上我们是可以重新设置Inner().a和Inner().map的值的,但是java编译器并不允许这样做, 具体原因我认为可能是如下原因:

在匿名类内部访问外面的变量看起来是一个很正常的需求,而且直观看起来应该是同一个东西。但是在方法调用结束以后局部变量会被销毁(栈里面的内容,也就是createInner.a, createInner.map。如果是同一个东西的话,那么意味着jvm在方法调用结束以后还不能销毁这些局部变量,需要将这些局部变量的生命周期保持到和Inner一样长,这样让jvm的实现起可能会更为复杂(提升这些变量的生命周期)。

所以,为了实现在Inner中可以访问createInner()中的a, map,同时他们看起来和createInner()中的一样(一致),并且避免JVM对对象生命周期的管理过于复杂,采用了一个中折中的办法:

将被用到的变量作为Inner的构造函数参数传入并在Inner内部设置对应的实例(private)。

将createInner().a, createInner().map设置为final,并且匿名类类部不可以修改对应实例属性的值,保证一致性。

通过上述的 1中,可以很自然实现在Inner中很自然的访问createInner中局部变量的值;由于Inner中使用的变量实际上和外部函数中的局部变量是不一样的,通过上述2可以保证他们一致(都不允许修改了,肯定一致), 否则开发者在内部修改值,但是却不会影响到外面的局部变量,这会让人困惑(天然看起来应该是一个东西啊,但是却不能一起变化)。

3. 闭包

此处引出了Java对闭包的支持,其实Java目前是支持了闭包的,匿名类就是一个典型的例子。将自由变量(createInner.a,createInner.map)封装到Inner中,但是Java的闭包确实有条件的闭包,因为Java只实现了capture-by-value, 只是把局部变量的值copy到了匿名类中, 没有实现capture-by-reference。如果是capture-by-reference的实现方式,可能需要将局部变量提升到对象中(也就是讲局部变量的生命周期延长,变为和Inner类一样长, 那么在createInner()执行完毕以后,就不会销毁 a, map了)。

此处有一个系列的参考文章关于``Javascript```中闭包的内容,图文并茂。深入理解javascript原型和闭包]

4. 内存泄漏

Java中并没有真正的实现延长生命周期, 但是间接实现了createInner.map的生命周期,因为Inner.map是一个对实际的HashMap()(位于堆中)对象的引用, 所以在createInner()方法中创建,但是却不会在该方法执行以后被GC回收, 该对象的生命周期和其创建Inner实例一样长。在本例中的代码的内存泄漏就由此而生。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值