JDK8的随笔(03)_Lambda表达式的变量使用范围讨论

Lambda变量使用以及使用范围

概念普及 捕获变量 capture variables

啥是capture variables

先看一段代码的样例:

public class LocalClassExample {

    static String regularExpression = "[^0-9]";

    public static void validatePhoneNumber(
        String phoneNumber1, String phoneNumber2) {

        final int numberLength = 10;

        // Valid in JDK 8 and later:

        // int numberLength = 10;

        class PhoneNumber {

            String formattedPhoneNumber = null;

            PhoneNumber(String phoneNumber){
                // numberLength = 7;
                String currentNumber = phoneNumber.replaceAll(
                  regularExpression, "");
                if (currentNumber.length() == numberLength)
                    formattedPhoneNumber = currentNumber;
                else
                    formattedPhoneNumber = null;
            }

            public String getNumber() {
                return formattedPhoneNumber;
            }

            // Valid in JDK 8 and later:

//            public void printOriginalNumbers() {
//                System.out.println("Original numbers are " + phoneNumber1 +
//                    " and " + phoneNumber2);
//            }
        }

        PhoneNumber myNumber1 = new PhoneNumber(phoneNumber1);
        PhoneNumber myNumber2 = new PhoneNumber(phoneNumber2);

        // Valid in JDK 8 and later:

//        myNumber1.printOriginalNumbers();

        if (myNumber1.getNumber() == null) 
            System.out.println("First number is invalid");
        else
            System.out.println("First number is " + myNumber1.getNumber());
        if (myNumber2.getNumber() == null)
            System.out.println("Second number is invalid");
        else
            System.out.println("Second number is " + myNumber2.getNumber());

    }

    public static void main(String... args) {
        validatePhoneNumber("123-456-7890", "456-7890");
    }
}

样例的代码是一个进行电话号码的正则表达的format处理。使用了一个内部类进行处理。
OK,上面的说法是错误的。不是内部类,是局部类
内部类是在类的内部直接定义的类,而局部类实在类的内部的{ }中定义的类,{ }能够是一个block能够是一个方法也能够是一个if语句等等。

final int numberLength = 10;

上面这个是在类LocalClassExample的validatePhoneNumber方法中的一个局部变量。
依据JDK8前的标准,一个局部类能够訪问局部变量的前提是,这个局部变量应该是声明为final的。


所以上面的样例中numberLength被声明为final,是能够被PhoneNumber这个class内部直接訪问的。
这个訪问就叫做captured variable,捕获变量

capture variables在JDK8以后的活用

有效final

说个题外话。在JDK8以后。一个局部变量即使不是final的,可是假设是有效final的。也是能够被capture variable的。啥意思呢?看以下:
这里写图片描写叙述
在jdk8曾经。假设我们不使用final来定义这个numberLenth的话,那么会出错,信息如图。


而假设是jdk8的话就不会出错,图略。

所谓”有效final“指的是,一个变量在声明以后从来美没有被改变过,那么这个变量就是”有效final“的。
假设中途改变过,那么就不能够。

如:
这里写图片描写叙述

方法參数access

题外话。

            public void printOriginalNumbers() {
                System.out.println("Original numbers are " + phoneNumber1 +
                    " and " + phoneNumber2);
            }
        }

上面的代码片段中訪问了phoneNumber1和phoneNumber2。这两个变量是来源于以下的validatePhoneNumber方法中的參数。

    public static void validatePhoneNumber(
        String phoneNumber1, String phoneNumber2) {

概念普及 Shadowing

啥是Shadowing

Shadowing即屏蔽。

public class ShadowTest {

    public int x = 0;  // 行1

    class FirstLevel {

        public int x = 1; // 行2

        void methodInFirstLevel(int x) { // 行3
            System.out.println("x = " + x);
            System.out.println("this.x = " + this.x);
            System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
        }
    }

    public static void main(String... args) {
        ShadowTest st = new ShadowTest();
        ShadowTest.FirstLevel fl = st.new FirstLevel();
        fl.methodInFirstLevel(23);
    }
}

先猜猜执行结果。
答案:

x = 23
this.x = 1
ShadowTest.this.x = 0

代码中的行1 行2 行3都粗线了x这个变量。


行1 : class的全局变量
行2: 内部类的全局变量
行3: 方法的參数
java的特点是命名能够一样,可是作用域是不同的。从而在各个使用变量的地方就须要加入修饰来告诉jvm你究竟在取哪一个变量。
从样例中能够看出,这三个x相互都是屏蔽的。
利用x直接取值的话。取得的肯定是通过方法变量直接传递而来的值。


通过this.x取值的话,this指的是内部类这个小范围的实现。所以是内部类的x=1。


上升到最上端的ShadowTest.this.x 则是整个class的x的全局变量,结果自然是0。


屏蔽主要看作用范围以及调用时候的前缀来推断究竟取得的是哪一个变量。

capture variable和Shadowing在Lambda表达式中的表现

Lambda表达式支持capture variable

public class LambdaScopeTest {

    public int x = 0;

    class FirstLevel {

        public int x = 1;

        void methodInFirstLevel(int x) {

            // The following statement causes the compiler to generate
            // the error "local variables referenced from a lambda expression
            // must be final or effectively final" in statement A:
            //
            // x = 99;

            Consumer<Integer> myConsumer = (y) -> 
            {
                System.out.println("x = " + x); // Statement A
                System.out.println("y = " + y);
                System.out.println("this.x = " + this.x);
                System.out.println("LambdaScopeTest.this.x = " +
                    LambdaScopeTest.this.x);
            };

            myConsumer.accept(x);

        }
    }

    public static void main(String... args) {
        LambdaScopeTest st = new LambdaScopeTest();
        LambdaScopeTest.FirstLevel fl = st.new FirstLevel();
        fl.methodInFirstLevel(23);
    }
}

看图效果更好:
这里写图片描写叙述
变量x最初的来源是内部类的FirstLevel的方法methodInFirstLevel的參数。


而在第23行的Lambda表达式的调用中,成功进行了读取,那么这就是一个capture variable的行为,所以Lambda表达式是支持capture variable的。


输出结果是:

x = 23
y = 23
this.x = 1
LambdaScopeTest.this.x = 0

这里有一点点烧脑。
首先,this.x=1和LambdaScopeTest.this.x = 0在前面已经解释过了,ok没有问题。


x=23 y=23须要着重说明一下。
首先,x=23的结果。来源于main方法中的以下的这个參数的传入。

fl.methodInFirstLevel(23);

传入的參数即为x的数值23,System.out.println(“x = ” + x); 的x的数值直接来源于方法參数23。是一个capture variable的直接的使用。


而y=23是怎么来的呢?

            Consumer<Integer> myConsumer = (y) -> 
            {
                System.out.println("x = " + x); // Statement A
                System.out.println("y = " + y);
                System.out.println("this.x = " + this.x);
                System.out.println("LambdaScopeTest.this.x = " +
                    LambdaScopeTest.this.x);
            };

            myConsumer.accept(x);

上面的代码中。我们来简化一下:

            Consumer<Integer> myConsumer = (y) -> 
            {
                System.out.println("y = " + y);
            };

            myConsumer.accept(x);

看不清的话再继续简化:

            Consumer<Integer> myConsumer = (y) -> 
            {
                System.out.println("y = " + y);
            };
            myConsumer.accept(23);

能够看到,Lambda表达式实现了Consumer<Integer>的函数接口accept方法,那么泛型已经限定了是Integer。那么作为输入的(y) 就是accept(T t)的參数t,T也就对应成为了Integer类型。

外部传入的參数是x,x本身的数值就是23,那么y的数值自然而然也就是23,由外部传递而来,和capture variable没有关系。

Lambda表达式不支持Shadowing

上面给我Shadowing的样例,事实上把我们自己的样例改一改就是Shadowing。
用同样的变量名屏蔽就实现了Shadowing。
这里写图片描写叙述
我们把Lambda表达式中的y改成了x,使得这个x与方法定义的int x一致。


依照之前的样例来说,这应该实现了一个屏蔽互不干涉,可是错误信息随之而来。
从错误信息我们能够看出,事实上Lambda表达式并没有真正的实现了一个scope,它所实现的scope依照javadoc的语言来说仅仅是一个语义性质的scope,所以反复的定义就会使得编译器觉得你在反复定义。
至于为什么。那是JVM层设计的问题了,今后怎样变化是否变化不得而知,当下的标准即是如此。

つづく・・・

posted on 2017-08-15 11:02  ljbguanli  阅读(443)  评论(0编辑  收藏  举报