码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2015-03-12 17:12:02      阅读:244      评论:0      收藏:0      [点我收藏+]

标签:lambda   使用范围   变量   jdk8   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层设计的问题了,今后如何变化是否变化不得而知,当下的标准即是如此。

つづく???

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

标签:lambda   使用范围   变量   jdk8   lambda表达式   

原文地址:http://blog.csdn.net/forevervip/article/details/44208329

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!