Java 变量捕获(Captured Variable)

Java 变量捕获(Captured Variable)

引言

Java 中的局部类(Local Class,本地类)匿名类(Anonymous Class)都存在变量捕获(captured variable)

只有理解了什么是变量捕获之后,我们后续才能更好地理解 Lambda 表达式的作用域,因为 Lambda 表达式也存在变量捕获。

什么是变量捕获

首先,我们知道局部类和匿名类都可以访问封闭类的成员,例如以下代码:

package com.wuxianjiezh.test;

public class ScopeTest {

    interface Say {
        public void say();
    }

    private String member = "我是封闭类的成员";

    public void print() {

        class LocalClass {

            public void say() {
                // 局部类可以访问封闭类的成员
                System.out.println(member);
            }
        }

        LocalClass localClass = new LocalClass();
        localClass.say();
    }

    public void print2() {

        Say say = new Say() {

            @Override
            public void say() {
                // 匿名类可以访问封闭类的成员
                System.out.println(member);
            }
        };

        say.say();
    }

    public static void main(String[] args) {
        ScopeTest test = new ScopeTest();
        test.print();
        test.print2();
    }
}

此外,局部类和匿名类也都可以访问局部变量(本地变量)。但是,它们只能访问由 final 声明的局部变量。当局部类或匿名类访问封闭块的局部变量或参数时,还会捕获该局部变量或参数。譬如以下代码:

package com.wuxianjiezh.test;

public class ScopeTest {

    interface Say {
        public void say();
    }

    private String member = "我是封闭类的成员";

    public void print(final String param) {

        final String local = "我是局部变量";

        class LocalClass {

            public void say() {
                // 局部类可以访问封闭类的成员
                System.out.println(member);

                // 局部类可以访问封闭块的最终局部变量和参数
                // 从 Java 8 开始,在方法中声明的局部类可以访问方法的参数
                System.out.println(param + "\t" + local);
            }
        }

        LocalClass localClass = new LocalClass();
        localClass.say();
    }

    public void print2(final String param) {

        final String local = "我是局部变量";

        Say say = new Say() {

            @Override
            public void say() {
                // 匿名类可以访问封闭类的成员
                System.out.println(member);

                // 匿名类可以访问封闭块的最终局部变量和参数
                // 从 Java 8 开始,在方法中声明的匿名类可以访问方法的参数
                System.out.println(param + "\t" + local);
            }
        };

        say.say();
    }

    public static void main(String[] args) {
        ScopeTest test = new ScopeTest();
        test.print("方法一");
        test.print2("方法二");
    }
}

上面代码中的变量 local 我们就称之为捕获的变量(captured variable)

注意,从 Java 8 开始,局部类和匿名类都可以访问最终(final)实际上的最终(effectively final)的封闭块的局部变量和参数。

实际上的最终(effectively final)
在初始化之后其值永远不会改变的变量或参数。

譬如,以下代码从 Java 8 开始都是有效的:

package com.wuxianjiezh.test;

public class ScopeTest {

    interface Say {
        public void say();
    }

    private String member = "我是封闭类的成员";

    public void print(String param) {

        // 从 Java 8 开始有效
        String local = "我是局部变量";

        class LocalClass {

            public void say() {
                // 局部类可以访问封闭类的成员
                System.out.println(member);

                // 局部类可以访问封闭块的实际上的最终局部变量和参数
                // 从 Java 8 开始,在方法中声明的局部类可以访问方法的参数
                System.out.println(param + "\t" + local);
            }
        }

        LocalClass localClass = new LocalClass();
        localClass.say();
    }

    public void print2(String param) {

        // 从 Java 8 开始有效
        String local = "我是局部变量";

        Say say = new Say() {

            @Override
            public void say() {
                // 匿名类可以访问封闭类的成员
                System.out.println(member);

                // 匿名类可以访问封闭块的实际上的最终局部变量和参数
                // 从 Java 8 开始,在方法中声明的匿名类可以访问方法的参数
                System.out.println(param + "\t" + local);
            }
        };

        say.say();
    }

    public static void main(String[] args) {
        ScopeTest test = new ScopeTest();
        test.print("方法一");
        test.print2("方法二");
    }
}

但是,当我们使用赋值语句改变了实际上的最终变量时,编译器就会报错。譬如以下代码在编译时就会报错:

package com.wuxianjiezh.test;

public class ScopeTest {

    interface Say {
        public void say();
    }

    private String member = "我是封闭类的成员";

    public void print(String param) {

        // 从 Java 8 开始有效
        String local = "我是局部变量";

        class LocalClass {

            public void say() {
                // 局部类可以访问封闭类的成员
                System.out.println(member);

                // 编译器报错:Variable 'param' is accessed
                // from within inner class, needs to be final or effectively final
                param = "改变";

                // 编译器报错:Variable 'local' is accessed
                // from within inner class, needs to be final or effectively final
                local = "改变";

                // 局部类可以访问封闭块的实际上的最终局部变量和参数
                // 从 Java 8 开始,在方法中声明的局部类可以访问方法的参数
                System.out.println(param + "\t" + local);
            }
        }

        LocalClass localClass = new LocalClass();
        localClass.say();
    }

    public void print2(String param) {

        // 从 Java 8 开始有效
        String local = "我是局部变量";

        Say say = new Say() {

            @Override
            public void say() {
                // 匿名类可以访问封闭类的成员
                System.out.println(member);

                // 编译器报错:Variable 'param' is accessed
                // from within inner class, needs to be final or effectively final
                param = "改变";

                // 编译器报错:Variable 'local' is accessed
                // from within inner class, needs to be final or effectively final
                local = "改变";

                // 匿名类可以访问封闭块的实际上的最终局部变量和参数
                // 从 Java 8 开始,在方法中声明的匿名类可以访问方法的参数
                System.out.println(param + "\t" + local);
            }
        };

        say.say();
    }

    public static void main(String[] args) {
        ScopeTest test = new ScopeTest();
        test.print("方法一");
        test.print2("方法二");
    }
}

总结

最后,我们可以总结出几下点:

  • 匿名类除了没有名字外,其它和局部类并没有什么不同(这不是本文的结论,这是官方的说法:“They are like local classes except that they do not have a name.”)
  • 从 Java 8 开始,局部类和匿名类都可以访问最终(final)实际上的最终(effectively final)的封闭块的局部变量和参数
  • 实际上的最终是指在初始化之后其值永远不会改变的变量或参数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值