程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Java SE 8 for the Really Impatient讀書筆記——Java 8 Lambda表達式,impatientlambda

Java SE 8 for the Really Impatient讀書筆記——Java 8 Lambda表達式,impatientlambda

編輯:JAVA綜合教程

Java SE 8 for the Really Impatient讀書筆記——Java 8 Lambda表達式,impatientlambda


1. lambda表達式的語法

語法十分簡單:參數->主體

 

1.1 參數

可以是零個參數,一個參數,也可以是多個參數,參數可以指定類型,在編譯器可以推導出參數類型的情況下,也可以省略參數類型。

 

兩個參數的例子:

(String first, String second)-> Integer.compare(first.length(), second.length())

 

0個參數的例子:

() -> { for (int i = 0; i < 1000; i++) doWork(); }

 

從jdk7開始,泛型可以簡化寫成如下形式:

Map<String, String> myMap = new HashMap<>(); 

編譯器會根據變量聲明時的泛型類型自動推斷出實例化HashMap時的泛型類型。

同樣的,如果編譯器可以推導出Lambda表達式中參數的類型,則也可以省略,例如:

Comparator<String> comp = (first, second) -> Integer.compare(first.length(), second.length());

編譯器可以推斷出first和second的類型為String,此時,參數類型可省略。

 

在只有一個參數,且可推斷出其類型的情況下,可以再將括號省略: 

EventHandler<ActionEvent> listener = event ->System.out.println("Thanks for clicking!");

  

同方法參數一樣,表達式參數也可以添加annotations或者final修飾:

(final String name) -> ...
(@NonNull String name) -> 

 

 1.2 主體

主體一定要有返回值。

 

如果主體只有一句,則可以省略大括號:

Comparator<String> comp = (first, second) -> Integer.compare(first.length(), second.length());

 

多於一句的情況,需要用{}括上:

(String first, String second) -> {
   if (first.length() < second.length()) return -1;
   else if (first.length() > second.length()) return 1;
   else return 0;
}

 

主體必須有返回值,只在某些分支上有返回值也是不合法的,例如:

(int x) -> { if (x >= 0) return 1; }

 這個例子是不合法的。

 

2. 函數式接口 

只包含一個抽象方法的接口叫做函數式接口。

函數式接口可使用注解@FunctionalInterface標注(不強制,但是如果標注了,編譯器就會檢查它是否只包含一個抽象方法)

可以通過lambda表達式創建函數式接口的對象,這是lambda表達式在java中做的最重要的事情

在jdk8以前,其實已經存在著一些接口,符合上述函數式接口的定義。

 

2.1 JDK 8之前已有的函數式接口

java.lang.Runnable java.util.concurrent.Callable java.security.PrivilegedAction java.util.Comparator java.io.FileFilter java.nio.file.PathMatcher java.lang.reflect.InvocationHandler java.beans.PropertyChangeListener java.awt.event.ActionListener javax.swing.event.ChangeListener

 

在jdk8以前,這些接口的使用方式與其他接口並無不同。

 

通過兩個例子來說明lambda表達式如何創建函數式接口實例

1.創建Runnable函數式接口實例,以啟動線程——jdk8以前:

import java.util.*;

public class OldStyle {
    public static void main(String[] args) {
        // 啟動一個線程
        Worker w = new Worker();
        new Thread(w).start();
        // 啟動一個線程
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }).start();

    }
}

class Worker implements Runnable {
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}

 運行結果:

Thread-0
Thread-1

 

從代碼角度來看,不管是通過內部類還是通過匿名內部類,啟動線程需要編寫的代碼都較為繁瑣,其中,由程序員自定義的僅僅是run方法中的這一句話:

System.out.println(Thread.currentThread().getName());

 

lambda表達式風格的啟動線程:

// 啟動一個線程
Runnable runner = () -> System.out.println(Thread.currentThread().getName());
runner.run();
第一行實際上創建了一個函數式接口Runnable的實例runner,可以看出,lambda表達式的實體,恰好是run方法的方法體部分。


2.創建Comparator函數式接口實例,實現根據String的長度來排序一個String數組——jdk8以前:
import java.util.*;

public class OldStyle {
    public static void main(String[] args) {
        // 排序一個數組
        class LengthComparator implements Comparator<String> {
            public int compare(String first, String second) {
                return Integer.compare(first.length(), second.length());
            }
        }
        String[] strings = "Mary had a little lamb".split(" ");
        Arrays.sort(strings, new LengthComparator());
        System.out.println(Arrays.toString(strings));
    }
}

 

lambda表達式:

import java.util.*;

public class LambdaStyle {
    public static void main(String[] args) {    
        // 排序一個數組
        String[] strings = "Mary had a little lamb".split(" ");
        Arrays.sort(strings, (first, second) -> Integer.compare(first.length(), second.length()));
        System.out.println(Arrays.toString(strings));
    }
}

 

可以看出,函數式接口通過lambda表達式創建實例,是如此的精簡 

 

java.util.function中定義了幾組類型的函數式接口以及針對基本數據類型的子接口。

Predicate -- 傳入一個參數,返回一個bool結果, 方法為boolean test(T t)
Consumer -- 傳入一個參數,無返回值,純消費。 方法為void accept(T t)
Function<t,r> -- 傳入一個參數,返回一個結果,方法為R apply(T t)
Supplier -- 無參數傳入,返回一個結果,方法為T get()
UnaryOperator -- 一元操作符, 繼承Function<t,t>,傳入參數的類型和返回類型相同。
BinaryOperator -- 二元操作符, 傳入的兩個參數的類型和返回類型相同, 繼承BiFunction

 

3. 方法引用

方法引用增強了lambda表達式的可讀性 

方法引用例1:同樣是排序一個數組的例子,這次是不區分大小寫的排序一個數組:

import java.util.*;

public class LambdaStyle {
    public static void main(String[] args) {
        // 排序一個數組
        String[] strings = "Mary had a little lamb".split(" ");     
        Arrays.sort(strings, (s1, s2) -> {
            int n1 = s1.length();
            int n2 = s2.length();
            int min = Math.min(n1, n2);
            for (int i = 0; i < min; i++) {
                char c1 = s1.charAt(i);
                char c2 = s2.charAt(i);
                if (c1 != c2) {
                    c1 = Character.toUpperCase(c1);
                    c2 = Character.toUpperCase(c2);
                    if (c1 != c2) {
                        c1 = Character.toLowerCase(c1);
                        c2 = Character.toLowerCase(c2);
                        if (c1 != c2) {
                            // No overflow because of numeric promotion
                            return c1 - c2;
                        }
                    }
                }
            }
            return n1 - n2;
        });

        System.out.println(Arrays.toString(strings));
    }
}

 

上述例子,由於lambda表達式的主體代碼較長,導致代碼可讀性下降,通過方法引用可以解決這個問題

方法引用例2:

import java.util.*;

public class LambdaStyle {
    public static void main(String[] args) {
        // 排序一個數組
        String[] strings = "Mary had a little lamb".split(" ");
        Arrays.sort(strings, LambdaStyle::myCompareToIgnoreCase);
        System.out.println(Arrays.toString(strings));
    }

    public static int myCompareToIgnoreCase(String s1, String s2){
        int n1 = s1.length();
        int n2 = s2.length();
        int min = Math.min(n1, n2);
        for (int i = 0; i < min; i++) {
            char c1 = s1.charAt(i);
            char c2 = s2.charAt(i);
            if (c1 != c2) {
                c1 = Character.toUpperCase(c1);
                c2 = Character.toUpperCase(c2);
                if (c1 != c2) {
                    c1 = Character.toLowerCase(c1);
                    c2 = Character.toLowerCase(c2);
                    if (c1 != c2) {
                        // No overflow because of numeric promotion
                        return c1 - c2;
                    }
                }
            }
        }
        return n1 - n2;
    }
}

將主體代碼抽出來寫到一個方法中,然後引用這個方法。

 

方法表達式的三種主要使用情況:

前兩種很好理解,例2是第一種情況,第二種情況如下例所示

方法引用例3:

import java.util.*;

public class LambdaStyle {
    public static void main(String[] args) {
        // 排序一個數組
        String[] strings = "Mary had a little lamb".split(" ");
        LambdaStyle lambdaStyle = new LambdaStyle();
        Arrays.sort(strings, lambdaStyle::myCompareToIgnoreCase);
        System.out.println(Arrays.toString(strings));
    }

    public int myCompareToIgnoreCase(String s1, String s2){
        int n1 = s1.length();
        int n2 = s2.length();
        int min = Math.min(n1, n2);
        for (int i = 0; i < min; i++) {
            char c1 = s1.charAt(i);
            char c2 = s2.charAt(i);
            if (c1 != c2) {
                c1 = Character.toUpperCase(c1);
                c2 = Character.toUpperCase(c2);
                if (c1 != c2) {
                    c1 = Character.toLowerCase(c1);
                    c2 = Character.toLowerCase(c2);
                    if (c1 != c2) {
                        // No overflow because of numeric promotion
                        return c1 - c2;
                    }
                }
            }
        }
        return n1 - n2;
    }
}

 

在第三種情況中,第一個參數會成為執行方法的對象。

String類中實際上已經提供了不區分大小寫比較字符串的方法:

public int compareToIgnoreCase(String str)

 

用法:

String s = "jdfjsjfjskd";
String ss = "dskfksdkf";
int i = s.compareToIgnoreCase(ss);
System.out.println(i);

 

通過這個方法來實現不區分大小寫的排序一個數組就是第三種情況

方法引用例4:

import java.util.*;

public class LambdaStyle {
    public static void main(String[] args) {
        // 排序一個數組
        String[] strings = "Mary had a little lamb".split(" ");
        Arrays.sort(strings, String::compareToIgnoreCase);
        System.out.println(Arrays.toString(strings));
    }
}

 

對於函數式接口Comparator來說,它的抽象方法為:

int compare(T o1, T o2);

這個方法有兩個參數,前面出現在lambda表達式參數中的s1,s2,實際上就是這兩個參數。

而本文所舉的關於方法引用的前兩個例子,所引用的方法都有兩個參數。

而對於第三個關於方法引用的例子,String的compareToIgnoreCase方法只有一個參數。這時,第一個參數會成為執行方法的對象,(s1.compareToIgnoreCase(s2))

 

另外,也可以通過如下形式方法引用:

this::實例方法

super::實例方法

方法引用例5:

public class SuperTest {
    public static void main(String[] args) {
        class Greeter {
            public void greet() {
                System.out.println("Hello, world!");
            }
        }

        class ConcurrentGreeter extends Greeter {
            public void greet() {
                Thread t = new Thread(super::greet);
                t.start();
            }
        }

        new ConcurrentGreeter().greet();
    }
}

 

4. 構造器引用

和方法引用相似,只不過通過如下方式引用:

類::new

 

例1

Stream<Button> stream = labels.stream().map(Button::new);
Button[] buttons4 = stream.toArray(Button[]::new);

 

5.變量作用域

lambda表達式引用值,而不是變量。

lambda 表達式中引用的局部變量必須是:顯示聲明為final的,或者雖然沒有被聲明為final,但實際上也算是有效的final的。

 

在Java中與其相似的是匿名內部類關於局部變量的引用。

例1:匿名內部類引用局部變量——jdk8以前

public class Outter {

    public static void main(String[] args) {
        final String  s1 = "Hello ";
        new Inner() {
            @Override
            public void printName(String name) {
                System.out.println(s1 + name);
            }
        }.printName("Lucy");

    }
}

interface Inner{
    public void printName(String name);

};

如例1所示,在jdk8以前,匿名內部類引用外部類定義的局部變量,則該變量必須是final的。

 

jdk8將這個條件放寬,匿名內部類也可以訪問外部類有效的final局部變量——即這個變量雖然沒有顯示聲明為final,但定義後也沒有再發生變化。

例2:匿名內部類引用局部變量——jdk8

public class Outter {

    public static void main(String[] args) {
        String  s1 = "Hello ";
        new Inner() {
            @Override
            public void printName(String name) {
                System.out.println(s1 + name);
            }
        }.printName("Lucy");

    }
}

interface Inner{
    public void printName(String name);

};

匿名內部類引用的外部類變量s1可以不顯示定義為final。但是s1必須在初始化後不再改變。

 

lambda表達式對於引用局部變量的規則同jdk8中的匿名內部類一樣:顯示聲明為final的,或者雖然沒有被聲明為final,但實際上也算是有效的final的

import java.io.*;
import java.nio.charset.*;
import java.nio.file.*;
import java.util.*;
import java.util.stream.*;

public class VariableScope {
    public static void main(String[] args) {
        repeatMessage("Hello", 100);
    }
    
    public static void repeatMessage(String text, int count) {
        Runnable r = () -> {
            for (int i = 0; i < count; i++) {
                System.out.println(text);
                Thread.yield();
            }
        };
        new Thread(r).start();
    }

    public static void repeatMessage2(String text, int count) {
        Runnable r = () -> {
            while (count > 0) {
                // count--; // Error: Can't mutate captured variable
                System.out.println(text);
                Thread.yield();
            }
        };
        new Thread(r).start();
    }

    public static void countMatches(Path dir, String word) throws IOException {
        Path[] files = getDescendants(dir);
        int matches = 0;
        for (Path p : files)
            new Thread(() -> {
                if (contains(p, word)) {
                    // matches++;
                    // ERROR: Illegal to mutate matches
                }
            }).start();
    }


    private static int matches;

    public static void countMatches2(Path dir, String word) {
        Path[] files = getDescendants(dir);
        for (Path p : files)
            new Thread(() -> {
                if (contains(p, word)) {
                    matches++;
                    // CAUTION: Legal to mutate matches, but not threadsafe
                }
            }).start();
    }

    // Warning: Bad code ahead
    public static List<Path> collectMatches(Path dir, String word) {
        Path[] files = getDescendants(dir);
        List<Path> matches = new ArrayList<>();
        for (Path p : files)
            new Thread(() -> {
                if (contains(p, word)) {
                    matches.add(p);
                    // CAUTION: Legal to mutate matches, but not threadsafe
                }
            }).start();
        return matches;
    }

    public static Path[] getDescendants(Path dir) {
        try {
            try (Stream<Path> entries = Files.walk(dir)) {
                return entries.toArray(Path[]::new);
            }
        } catch (IOException ex) {
            return new Path[0];
        }
    }

    public static boolean contains(Path p, String word) {
        try {
            return new String(Files.readAllBytes(p),
                    StandardCharsets.UTF_8).contains(word);
        } catch (IOException ex) {
            return false;
        }
    }
}

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved