JDK8的隨筆(03)_Lambda表達式的變量使用範圍討論

Lambda變量使用以及使用範圍

概念普及 捕獲變量 capture variables

啥是capture variables

先看一段代碼的樣例:java

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語句等等。express

final int numberLength = 10;

上面這個是在類LocalClassExample的validatePhoneNumber方法中的一個局部變量。
依據JDK8前的標準,一個局部類可以訪問局部變量的前提是,這個局部變量應該是聲明爲final的。markdown


因此上面的樣例中numberLength被聲明爲final,是可以被PhoneNumber這個class內部直接訪問的。
這個訪問就叫作captured variable,捕獲變量jvm

capture variables在JDK8之後的活用

有效final

說個題外話。在JDK8之後。一個局部變量即便不是final的,但是假設是有效final的。也是可以被capture variable的。啥意思呢?看如下:
這裏寫圖片描寫敘述
在jdk8曾經。假設咱們不使用final來定義這個numberLenth的話,那麼會出錯,信息如圖。函數


而假設是jdk8的話就不會出錯,圖略。post

所謂」有效final「指的是,一個變量在聲明之後歷來美沒有被改變過,那麼這個變量就是」有效final「的。
假設中途改變過,那麼就不可以。ui

如:
這裏寫圖片描寫敘述this

方法參數access

題外話。spa

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層設計的問題了,從此怎樣變化是否變化不得而知,當下的標準便是如此。

つづく・・・

相關文章
相關標籤/搜索