第二章:Lambda表達式


本章內容:

  Lambda管中窺豹

  在哪里以及如何使用Lambda表達式

  函數式接口,類型推斷

  方法引用

  Lambda復合

2.1 Lambda管中窺豹

  可以把Lambda表達式理解為簡潔的表示可傳遞的匿名函數的一種方式:它沒有名稱,但它有參數列表、方法主體、返回類型等。

  匿名:

    它不像普通方法那樣有一個明確的名稱,寫得少做得多

  函數:

    Lambda函數不像方法那樣屬於某個特定的類,但它和方法一樣有參數列表、函數主體、返回類型,還可能有可以拋出的異常列表

  傳遞:

    Lambda表達式可以作為參數傳遞給方法

  簡潔:

    不需要像匿名類那樣寫很多的代碼模板

參數列表:這里采用了Comparator中compare方法的參數,兩個Apple

箭頭:箭頭 -> 將參數列表與Lambda主體分隔開

Lambda主體:比較兩個Apple的重量,表達式就是Lambda的返回值

 

  測試:

  下面哪個不是有效的Lambda表達式:

    1. () -> {}

    2. () -> "Hello"

    3. () -> {return "Hello";}

    4. (Integer i) -> return "Hello" + i;

    5. (String s) -> {"Hello";}

  答案:

    4和5不是有效的Lambda表達式,4:(Integer i) -> {return "Hello" + i;};5:(String s) -> {return "Hello";}

Lambda示例:

 

2.2 在哪里以及如何使用Lambda

2.2.1 函數式接口:只定義了一個抽象方法的接口

  例如之前定義的Predicate<T>就是一個函數式接口,Comparator和Runnable也是函數式接口。

  那使用函數式接口能干什么呢?Lambda表達式允許你直接以內聯的形式為函數式接口的抽象方法提供實現,並把整個表達式作為函數式接口的實例。

public class LambdaDemo {
    
    public static void main(String[] args) {
        
        Runnable r1 = () -> System.out.println("Hello World1");
        Runnable r2 = new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello World2");
            }
        };

        process(r1);
        process(r2);
        process(() -> System.out.println("Hello World3"));
        
    }
    
    public static void process(Runnable r) {
        r.run();
    }
    
}

2.2.2 函數描述符

  函數式接口的抽象方法的簽名基本上就是Lambda表達式的簽名。我們將這種抽象方法叫做函數描述符。

2.3 使用函數式接口

2.3.1 Predicate

  java.util.function.Predicate<T>接口定義了一個名叫test的抽象方法,它接受一個泛型T對象,並返回一個boolean

public static <T> List<T> filter(List<T> list, Predicate<T> p){
        List<T> result = new ArrayList<T>();
        for (T t : list) {
            if(p.test(t)) {
                result.add(t);
            }
        }
        return result;
}


// 測試

// 使用Predicate
System.out.println("使用Predicate:");
List<String> list = new ArrayList<String>();
list.add(null);
list.add("Hello World");
list.add("");
Predicate<String> p = (String s) -> s != null && !s.isEmpty();
System.out.println(filter(list , p));

2.3.2 Consumer

  java.util.function.Consumer<T>定義了一個叫accept的抽象方法,它接受一個泛型T的對象,並沒有返回(void)

public static <T> void forEach(List<T> list, Consumer<T> c) {
        for(T t : list) {
            c.accept(t);
        }
}

// 測試

// 使用Consumer
System.out.println("// 使用Consumer:");
forEach(Arrays.asList(1,2,3,4,5), (Integer i) -> System.out.println(i));

2.3.3 Function

  java.util.function.Function<T, R>定義了一個叫apply的抽象方法,它接受一個泛型T的對象,並返回一個泛型R對象

public static <T, R> List<R> map(List<T> list, Function<T, R> f){
        List<R> result = new ArrayList<R>();
        for (T t : list) {
            result.add(f.apply(t));
        }
        return result;
}

// 測試

// 使用Function
System.out.println("// 使用Function:");
List<Integer> l = map(Arrays.asList("lambda", "in", "action"), (String s) -> s.length());
System.out.println(l);

2.4 類型檢查

  Lambda的類型是從使用Lambda的上下文推斷出來的,下面描述了Lambda表達式的類型檢查過程

2.5 類型推斷

  你還可以進一步簡化你的代碼。java編譯器會從上下文(目標類型)推斷出用什么函數式接口來配合Lambda表達式,這意味着它也可以推斷出適合Lambda的簽名,因為函數描述符可以通過目標類型來獲取。這樣就可以在Lambda語法中省去參數類型的聲明。

List<Apple> heavirThan150 = filter(inventory, a -> a.getWeight() > 150);

  Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
  c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight());

  參數沒有顯式類型。有時候顯式寫出參數類型更易讀,這個取決於程序員的選擇。

2.6 方法的引用

  方法的引用讓你可以重復使用現有的方法定義,並像Lambda一樣傳遞他們。

inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
inventory.sort(Comparator.comparing((Apple a) -> a.getWeight())); inventory.sort(Comparator.comparing(Apple::getWeight));//你的第一個方法的引用

2.6.1 管中窺豹

  當你需要使用方法的引用時,目標引用放在分隔符 :: 前,方法的名稱放在后面。例如 Apple::getWeight 就是引用了Apple類中的getWeight 方法。

Lambda 及其等效方法引用的例子:

Lambda  等效的方法引用
(Apple a) -> a.getWeight() Apple::getWeight
(str, i) -> str.substring(i) String::substring
(String s) -> System.out.println(s); System.out::println

 

 

 

 

 

BiFunction<String, Integer, String> bf = (String str, Integer i) -> str.substring(i); 
bf
= String::substring; String methadQuote = methadQuote("Hello", 2, bf); System.out.println("方法的引用:"); System.out.println(methadQuote); Consumer<String> consumer = (String str) -> System.out.println(str); consumer = System.out::println; methadQuote1("Hello World", consumer);

// 方法的引用
public static String methadQuote(String str, Integer i, BiFunction<String, Integer, String> bf) {
return bf.apply(str, i);
}
public static void methadQuote1(String str, Consumer<String> c) {
c.accept(str);
}

 

  如何構建方法的引用:

    方法引用主要有三類:

      1. 指向靜態方法的方法引用(例如Integer的parseInt方法,寫成 Integer::parseInt)

      2. 指向任意類型實例方法的方法引用(例如String的length方法,寫成String::length)

      3. 指向現有對象的實例方法的方法引用(例如Apple對象a有getWeight方法,寫成Apple::getWeight)

    解釋一下第二種:類似於String::length這種引用思想就是你在引用一個對象的方法,而這個對象本身是Lambda的一個參數。(這個對象必須放在泛型參數的第一位--不確定)

    

2.6.2 構造函數引用

  對於一個現有的構造函數,你可以利用它的名稱和關鍵字new來創建它的一個引用:ClassName::new。它適合Supplier的簽名 () -> T

/**
* 構造函數引用
*/
// 無參
Supplier<Apple> s = Apple::new;
// 一個參數
Function<String, Apple> f = Apple::new;
// 兩個參數
BiFunction<Integer, String, Apple> biFunction = Apple::new;

   如果想要為更多參數的構造函數使用方法引用怎么辦呢?

  答案:

    自己創建這樣的函數式接口:

private String name;
private Integer age;
private String address;

public interface TriFunction<T, U, V, R> {

    R apple(T t, U u, V v);
    
}

// 自定義的函數式接口
TriFunction<String, Integer, String, Student> tri = Student::new;

2.7 復合Lambda表達式的有用方法

  即將講到的方法為默認方法,后面章節會細講

2.7.1 比較器復合

Comparator<Apple> com = Comparator.comparing(Apple::getWeight);

  1. 逆序

inventory.sort(Comparator.comparing(Apple::getWeight).reversed());

  2. 比較器鏈

inventory.sort(Comparator.comparing(Apple::getWeight).reversed().thenComparing(Apple::getColor));

2.7.2 謂詞復合

  謂詞接口包含三個方法:negate,and,or

Predicate<Apple> redApple = (Apple a) -> "red".equals(a.getColor());
Predicate<Apple> notRedApple = redApple.negate();// 產生現有對象redApple的非
Predicate<Apple> redAndHeavyApple = redApple.and(a -> a.getWeight() > 150);
Predicate<Apple> redAndHeavyAppleOrGreen = redApple.and(a -> a.getWeight() > 150)
                                                   .or(a -> "green".equals(a.getColor()));

  注意:and和or在表達式中按照從左到右確定優先級。因此,a.or(b).and(c) 為 (a || b) && c

2.7.3 函數復合:

  你還可以把Function接口所代表的的Lambda表達式復合起來。Function接口為此配了andThen和compose兩個默認方法,他們都返回一個Function的實例。

Function<Integer, Integer> g = x -> x + 1;
Function<Integer, Integer> h = y -> y * 2;
Function<Integer, Integer> j = g.andThen(h);// 相當於數學中的 h(g(x))
Integer apply = j.apply(1);
System.out.println("andThen函數復合:" + apply);// 4
j = g.compose(h);// 相當於數學中的 g(h(x))
apply = j.apply(1);
System.out.println("compose函數復合:" + apply);// 3

 

  

備注:

摘自文獻:《Java8實戰》(中文版)《Java8 in Action》(英文版)

 

代碼(GitHub地址): https://github.com/changlezhong/java8InAction

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM