Java-为什么局部/匿名内部类形参需要加final 修饰?

java为什么匿名内部类的参数引用时final

1. 闭包(Closure

什么是闭包,简答来说:

· 一个依赖于 外部环境自由变量 的函数

· 这个函数能够访问外部环境里的 自由变量

JavaScript举个例子:

function Add(y) {

    return function(x) {

        return x+y;

       }

}

对内部函数function(x)来说,y就是自由变量,而且function()的返回值依赖于这个外部自由变量y。而外围的Add(y)函数正好就是那个包含自由变量y的环境。而且JavaScript语法允许内部函数function(x)访问外部函数Add(y)的局部变量。满足这三个条件,外部函数Add(y)对内部函数function(x)构成了闭包(Closure.

Java中我们看不到这样的结构,因为Java语法不允许这样直接的函数嵌套和跨域访问变量。

2. Java中不存在闭包么?

看这个例子:

class Add {

    private int x = 2;

    public int add() {

        int y = 3;

        return x+y;

    }

}

其实Java到处都是闭包,使我们反而感觉不出来。

3. Java内部类是闭包:包含指向 外部类 的指针。

public class Outer {

    private int x = 100;

    private class Inner {

        private y = 100;

        public int innerAdd() {

            return x+y;

        }

    }

}

内部类(Inner Class)通过包含一个指向外部类的引用,做到自由访问外部环境类的所有字段,变相把环境中的自由变量封装到函数里,形成一个闭包。

4. 尴尬的匿名内部类

看下面的例子:

interface AnnoInner() { add(); }

public class Outer {

        public AnnoInner getAnnoInner(final int x) {

            final int y = 100;

            return new AnnoInner() {

                int z = 100;

                public int add() { return x+y+z;}

                //public void changY() { y+=1; } //这个函数无法修改外部环境中的自由变量Y

            };

        }

}

匿名内部类因为是匿名,所以不能显示地声明 构造函数,也不能忘构造函数里 传参。不但返回的只是个 AnnoInner 的接口,而且还没有和它 外围环境getAnnoInner()方法的局部变量xy构成任何 类的结构(Java的闭包也不能用)。 但是匿名内部类的add()方法却直接使用了 xy,说明 外部getAnnoInner()方法 实际上已经对内部类AnnerInner构成了一个闭包。

但这里别扭的是,xy都必须用 final 修饰,不可以修改。如果用 changY()函数试图修改外部getAnnoInner()函数的成员变量y,编译不通过。

 

其实是Java编译器在编译的时候偷偷对 函数做了手脚,将外部环境方法的xy局部变量,拷贝了一份到 匿名内部类中。

interface AnnoInner() { add(); }

public class Outer {

    public AnnoInner getAnnoInner(final int x) {

        final int y = 100;

        return new AnnoInner() {

            int copyX = x;

            int copyY = y;

            int z = 100;

            public int add() { return x+y+z;}

            //public void changY() { y+=1; } //这个函数无法修改外部环境中的自由变量Y

        };

    }

}

对于匿名内部类,Java编译器实现的只是 值传递(capture-by-value),并未实现 引用传递(capture-by-reference)。

而只有引用传递才能保持匿名内部类 与外部环境的 局部变量保持同步!

但是 Java又不肯说明,只能粗暴的一刀切,就说 既然内外不能同步,那就不允许大家改变外围的局部变量,就必须用 final修饰了。


总结:

·  Java设计者想要匿名函数或者Lambda函数内部外部保持数据一致性(知道了目的才好理解)

·  然而Java只是实现了capture-by-value形式的闭包,也就是匿名函数内部会重新拷贝一份自由变量,然后函数外部函数内部就有两份数据

·  那么要想实现1中的目的,只能要求两处变量不变。JDK8之前要求使用final修饰,JDK8聪明些了,可以使用effectively final的方式。总之,两边不变就行了。


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值