Java Function中的容易被忽略的方法identity()


/*
 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package java.util.function;

import java.util.Objects;

/**
 * Represents a function that accepts one argument and produces a result.
 * 表示接受一個參數並產生一個結果的函數。
 *       
 * @param <T> the type of the input to the function
 * 參數<T>是函數的輸入類型
 * @param <R> the type of the result of the function
 * 參數<R>是函數的返回類型
 * @since 1.8
 */
// 注明是函數式接口
@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     * 將此函數應用於給定參數。
     * @param t 函數參數
     * @return R 函數返回類型
     */
    R apply(T t);

    /**
     * Returns a composed function that first applies the {@code before}
     * function to its input, and then applies this function to the result.
     * If evaluation of either function throws an exception, it is relayed to
     * the caller of the composed function.
     *
     * @param <V> the type of input to the {@code before} function, and to the
     *           composed function
     * @param before the function to apply before this function is applied
     * @return a composed function that first applies the {@code before}
     * function and then applies this function
     * @throws NullPointerException if before is null
     *
     * @see #andThen(Function)
     */
    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    /**
     * Returns a composed function that first applies this function to
     * its input, and then applies the {@code after} function to the result.
     * If evaluation of either function throws an exception, it is relayed to
     * the caller of the composed function.
     * 返回一個組合函數,該函數首先將該函數應用於其輸入,然后將該函數應用於結果。如果對任一函數的求值拋出異常,則將其中繼到組合函數的調用方。
     * @param <V> the type of output of the {@code after} function, and of the
     *           composed function
     * @param after the function to apply after this function is applied
     * @return a composed function that first applies this function and then
     * applies the {@code after} function
     * @throws NullPointerException if after is null
     *
     * @see #compose(Function)
     */
    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    /**
     * Returns a function that always returns its input argument.
     * 該方法返回一個函數,該函數返回輸入的參數。
     * @param <T> the type of the input and output objects to the function
     * @return a function that always returns its input argument
     */
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

之前只了解Function是函數式接口,支持Lambda表達式,今日由於需要,才了解到Functionidentity()

Functionidentity()返回t -> T,即本身。
也就是說identity()可以換為t -> T

今天的需求大概是:

已經有了List<User>,利用stream()獲取一個鍵值對Map<id, User>

// 構造Map鍵值對,key:Integer, value:IndexEntity
// key為指標實體的id,value為對應的指標實體
Map<Integer, IndexEntity> map = indexEntities.stream().collect(Collectors.toMap(IndexEntity::getId, Function.identity()));

 

IndexEntity::getId是Java8新出的方法引用。

java8 特性 Function.identity()

Function.identity()是什么?

  1. // 將Stream轉換成容器或Map
  2. Stream< String> stream = Stream.of("I", "love", "you", "too");
  3. Map<String, Integer> map = stream.collect(Collectors.toMap(Function.identity(), String::length));

Function是一個接口,那么Function.identity()是什么意思呢?解釋如下:

Java 8允許在接口中加入具體方法。接口中的具體方法有兩種,default方法和static方法,identity()就是Function接口的一個靜態方法。
Function.identity()返回一個輸出跟輸入一樣的Lambda表達式對象,等價於形如t -> t形式的Lambda表達式。

identity() 方法JDK源碼如下:

  1. static Function identity() {
  2. return t -> t;
  3. }

Function.identity()的應用

下面的代碼中,Task::getTitle需要一個task並產生一個僅有一個標題的key。task -> task是一個用來返回自己的lambda表達式,上例中返回一個task。

  1. private static Map<String, Task> taskMap(List<Task> tasks) {
  2. return tasks.stream().collect(toMap(Task::getTitle, task -> task));
  3. }

可以使用Function接口中的默認方法identity來讓上面的代碼代碼變得更簡潔明了、傳遞開發者意圖時更加直接,下面是采用identity函數的代碼。

  1. import static java.util.function.Function.identity;
  2.  
  3. private static Map<String, Task> taskMap(List<Task> tasks) {
  4. return tasks.stream().collect(toMap(Task::getTitle, identity()));
  5. }

Function.identity() or t->t?

  1. Arrays.asList( "a", "b", "c")
  2. .stream()
  3. .map(Function.identity()) // <- This,
  4. .map( str -> str) // <- is the same as this.
  5. .collect(Collectors.toMap(
  6. Function.identity(), // <-- And this,
  7. str -> str)); // <-- is the same as this.

上面的代碼中,為什么要使用Function.identity()代替str->str呢?它們有什么區別呢?

在上面的代碼中str -> strFunction.identity()是沒什么區別的因為它們都是t->t。但是我們有時候不能使用Function.identity,看下面的例子:

  1. List list = new ArrayList<>();
  2. list.add(1);
  3. list.add(2);

下面這段代碼可以運行成功:

int[] arrayOK = list.stream().mapToInt(i -> i).toArray(); 

但是如果你像下面這樣寫:

int[] arrayProblem = list.stream().mapToInt(Function.identity()).toArray(); 

運行的時候就會錯誤,因為mapToInt要求的參數是ToIntFunction類型,但是ToIntFunction類型和Function沒有關系

《Java8新特性》之Lambda表達式、函數式接口、方法引用、Optional

1、Java8 Lambda表達式


Lambda表達式也稱為閉包,它允許我們把函數當作參數一樣傳遞給某個方法,或者把代碼本身當作數據處理。

早期Java開發者只能使用匿名內部類來實現Lambda表達式。

最簡單的可以由逗號分隔的參數列表、->符號、語句塊三部分組成。

例如:

// 例子1
// 參數e的類型是編譯器推理出來的
Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );

// 例子2
// 當然也可以將執行參數的類型寫上
Arrays.asList( "a", "b", "d" ).forEach((String e)-> System.out.println( e ) );

// 例子3
// 當有多個參數時
Arrays.asList( "a", "b", "d" ).sort((e1,e2)-> e1.compareTo(e2));

// 例子4
// 當Lambda的語句塊只有一行時,可以不使用return語句。
Arrays.asList( "a", "b", "d" ).sort((e1,e2)-> e1.compareTo(e2));

ps: 切記當有多個參數,或需要指定參數類型的時候,參數列表要加括號。


2、 函數式接口

函數式接口(Functional Interface)就是一個有且僅有一個抽象方法,但是可以有多個非抽象方法的接口。

作用: 這樣的接口可以隱式轉換為Lambda表達式。

只要某個開發者在該接口中添加一個函數,則該接口就不再是函數式接口,進而導致編譯失敗。為了客服這種問題,並顯式說明某個接口是函數式接口,Java8提供了一個特殊的注解**@FunctionalInterface**Java 庫中的所有相關接口都已經帶有這個注解了。

@FunctionalInterface
interface Addtions {
    int test(int a, int b);// 我是核心
    default void hello() {
        System.out.println("我不會影響到函數式接口的定義");
    }
    static void hello1(){
        System.out.println("我也不會影響到函數式接口的定義");
    }
}

常用的幾個接口:

  • java.util.function.Function
    • R apply(T t);
  • java.util.function.Supplier
    • T get();
  • java.util.function.Predicate
    • boolean test(T t);
  • java.util.function.Consumer
    • void accept(T t);
  • java.lang.Runnable
  • java.util.concurrent.Callable
  • java.security.PrivilegedAction
  • java.lang.reflect.InvocationHandler

寫lamdba表達式時會經常用到四個標黑的函數式接口,重點是她們方法的返回值和方法參數。


3、接口的默認方法和靜態方法

Java 8允許我們給接口添加一個非抽象的方法實現,只需要使用 default關鍵字即可,這個特征又叫做擴展方法,示例如下:

  • 默認方法可以被實現類重寫Override
class FunctionalInterfaceTest implements Formula{
    @Override
    public double calculate(int a) {
        return 0;
    }
    // 可以重寫sqrt方法。
    @Override
    public double sqrt(int a) {
        return Formula.super.sqrt(a);
    }
}
@FunctionalInterface
interface Formula {
    double calculate(int a);
    
// 該方法(默認方法)可以被實現類重寫
    default double sqrt(int a) {
        return Math.sqrt(a);
    }
    static void hello1(){
        System.out.println("我是新來的(JAVA8),我叫靜態方法,");
    }
}

4、方法引用

方法引用使得開發者可以直接引用現存的方法、Java類的構造方法或者實例對象。方法引用和Lambda表達式配合使用,使得java類的構造方法看起來緊湊而簡潔,沒有很多復雜的模板代碼。

可見使用Lambda表達式的寫法和使用方法引用的寫法的效果是一樣的,但是使用方法引用有時會更加簡化代碼

  • 構造器引用
    • 類名::new
  • 靜態方法引用
    • 類名::靜態方法
  • 對象方法引用
    • 類名::方法
    • 當Lambda表達式的參數列表第一個參數為實例方法的調用者,第二個參數(或無參)是實例方法的參數時,可以使用這種方法。
  • 實例方法引用
    • 實例對象::成員方法
    • 要先獲取一個實例對象
public class Test {
    private String name;
    public String getName() {
        return this.name;
    }
    public Test(String name) {
        this.name = name;
    }
    public static String staticMethod(){
        return "我是靜態方法!";
    }
    
    public static void main(String[] args) {
        Test test1 = new Test("小明");
        
        // Lambda表達式
        Supplier<String> func1 = () -> test1.getName();
        System.out.println("Lambda表達式測試:" + func1.get());
        
        // 實例方法引用
        Supplier<String> func2 = test1::getName;
        System.out.println("方法引用方式測試:" + func2.get());
        
        // 靜態方法引用
        Supplier<String> func3 = Test::staticMethod;
        System.out.println("靜態方法引用測試:" + func3.get());

        // 構造方法引用(構造器引用) 
        Function<String, Test> func4 = Test::new;
        Test test2 = func4.apply("xxx");
        System.out.println("構造方法引用測試:" + test2);
        
        // 對象方法引用
        // Test為類名,getName為成員方法。
        Function<Test, String> func5 = Test::getName;
        System.out.println("對象方法測試引用:" + func5.apply(test1));
    }
}

5、Optional

Java應用中最常見的bug就是NullPointerException,

就比如比較兩個字符串是否相等

s1.equals(s2),如果s1==null,那么一運行,console立馬就爆紅了。

所以Java8提供了Optional來解決這問題。

  • isPresent(): 如果Optional實例持有一個非空值,方法返回true,否則返回false
  • orElseGet():,Optional實例持有null,則可以接受一個lambda表達式生成的默認值
  • map(): 可以將現有的Opetional實例的值轉換成新的值
  • orElse(): Opetional 實例持有null的時候返回傳入的默認值, 方法與orElseGet() 方法類似。
  • filter(): 如果optional實例不為null,並且filter中lambda表達式返回true,就返回一個Optional實例;反之返回一個空optional。
    • If a value is present, and the value matches the given predicate,return an {@code Optional} describing the value, otherwise return an empty {@code Optional}.

  1. 當optional實例為null時
Optional< String > fullName = Optional.ofNullable( null );
System.out.println( "Full Name is set? " + fullName.isPresent() );
System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) ); 
System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );

// 下面為輸出結果
Full Name is set? false
Full Name: [none]
Hey Stranger!

  2.當optional實例不為null時

Optional< String > firstName = Optional.of( "Tom" );
System.out.println( "First Name is set? " + firstName.isPresent() );        
System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) ); 
System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ));

//輸出結果
First Name is set? true
First Name: Tom
Hey Tom!

探索Java8:(一)Stream的使用

 
轉載

Java 8 API添加了一個新的抽象稱為流Stream,可以讓你以一種聲明的方式處理數據。

Stream 使用一種類似用 SQL 語句從數據庫查詢數據的直觀方式來提供一種對 Java 集合運算和表達的高階抽象。

Stream API可以極大提高Java程序員的生產力,讓程序員寫出高效率、干凈、簡潔的代碼。

這種風格將要處理的元素集合看作一種流, 流在管道中傳輸, 並且可以在管道的節點上進行處理, 比如篩選, 排序,聚合等。

元素流在管道中經過中間操作(intermediate operation)的處理,最后由最終操作(terminal operation)得到前面處理的結果。


什么是 Stream?

Stream(流)是一個來自數據源的元素隊列並支持聚合操作

  • 元素是特定類型的對象,形成一個隊列。 Java中的Stream並不會存儲元素,而是按需計算
  • 數據源 流的來源。 可以是集合,數組,I/O channel, 產生器generator 等。
  • 聚合操作 類似SQL語句一樣的操作, 比如filter, map, reduce, find, match, sorted等。

和以前的Collection操作不同, Stream操作還有兩個基礎的特征:

  • Pipelining: 中間操作都會返回流對象本身。 這樣多個操作可以串聯成一個管道, 如同流式風格(fluent style)。 這樣做可以對操作進行優化, 比如延遲執行(laziness)和短路( short-circuiting)。
  • 內部迭代: 以前對集合遍歷都是通過Iterator或者For-Each的方式, 顯式的在集合外部進行迭代, 這叫做外部迭代。 Stream提供了內部迭代的方式, 通過訪問者模式(Visitor)實現。

一、流的創建

  • stream() − 為集合創建串行流。
  • parallelStream() − 為集合創建並行流。parallelStream其實就是一個並行執行的流.它通過默認的ForkJoinPool,可能提高你的多線程任務的速度。並行流在遍歷時可能是無序的。
public class ParallelStream { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); numbers.stream().forEach(System.out::print); } } 

二、 forEach、map、filter、limit、sorted

numbers.stream().forEach(System.out::print);  
numbers.stream().forEach(i->System.out.print(i));  

上述兩種方法是等價的。

forEach

forEach用來對stream中的數據進行迭代,比如上面創建流的操作就使用了forEach。看會上面的例子后理解forEach不會很難的。需要注意的是,forEach操作是不能改變遍歷對象本身的。

Map

map 方法用於映射每個元素到對應的結果,多數情況下用來處理數據。下面給出一個讓原list個位置元素自增2的代碼:

public class MapDemo { public static void main(String[] args) { List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); List<Integer> outPutList = numbers.stream().map(i -> i + 2).distinct().collect(Collectors.toList()); outPutList.forEach(n->System.out.print(n+" ")); } } 

filter

filter 方法用於通過設置的條件過濾出元素。以下代碼片段使用 filter 方法過濾出空字符串:

List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); // 獲取空字符串的數量 int count = strings.stream().filter(string -> string.isEmpty()).count(); 

Limit

limit 方法用於獲取指定數量的流。 以下代碼片段使用 limit 方法打印出 10 條數據:

Random random = new Random(); random.ints().limit(10).forEach(System.out::println); 

還有一個常用的是配合skip()方法用來進行分頁操作。

int pageSize=10; int currentPage=1; return pageList.stream() .skip(pageSize * (currentPage-1)) .limit(pageSize) .collect(Collectors.toList()); 

sorted

sorted 方法用於對流進行排序。以下代碼片段使用 sorted 方法對輸出的 10 個隨機數進行排序:

Random random = new Random(); random.ints().limit(10).sorted().forEach(System.out::println); 

Collectors

Collectors 可用於返回列表或字符串,上面介紹map的例子就用到了Collectors,下面給出菜鳥教程的一個例子:

List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl"); List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList()); System.out.println("篩選列表: " + filtered); String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", ")); System.out.println("合並字符串: " + mergedString); 

三、 統計

顧名思義,統計就是用來統計數據的,一般用於int、double、long等基本類型上。

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); IntSummaryStatistics stats = integers.stream().mapToInt((x) -> x).summaryStatistics(); System.out.println("列表中最大的數 : " + stats.getMax()); System.out.println("列表中最小的數 : " + stats.getMin()); System.out.println("所有數之和 : " + stats.getSum()); System.out.println("平均數 : " + stats.getAverage()); 

Stream先介紹到這里,我們Stream代碼覺得陌生是因為剛接觸聲明式編程的風格,下一篇應該會介紹lambda表達式和Optional的用法,我們會更多地用聲明式的編程風格。

探索Java8:(二)Function接口的使用

 
轉載

Java8 添加了一個新的特性Function,顧名思義這一定是一個函數式的操作。我們知道Java8的最大特性就是函數式接口。所有標注了@FunctionalInterface注解的接口都是函數式接口,具體來說,所有標注了該注解的接口都將能用在lambda表達式上。

標注了@FunctionalInterface的接口有很多,但此篇我們主要講Function,了解了Function其他的操作也就很容易理解了。

@FunctionalInterface public interface Function<T, R> { R apply(T t); /** * @return a composed function that first applies the {@code before} * function and then applies this function */ default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); } /** * @return a composed function that first applies this function and then * applies the {@code after} function */ default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); } } 

為了方便地閱讀源碼,我們需要了解一些泛型的知識,如果你對泛型已經很熟悉了,那你可以跳過這段 。

泛型是JDK1.5引入的特性,通過泛型編程可以使編寫的代碼被很多不同的類型所共享,這可以很好的提高代碼的重用性。因為本篇重點不是介紹泛型,所以我們只關注上述Function源碼需要用到的泛型含義。

1. 泛型類

泛型類使用<T>來表示該類為泛型類,其內部成員變量和函數的返回值都可以為泛型<T> ,Function源碼的標識為<T,R>,也就是兩個泛型參數,此處不再贅述,具體泛型類可以看網上的文章。

2. 泛型方法和通配符

在方法修飾符的后面加一個<T>表明該方法為泛型方法,如Function 的源碼里的compose方法的<V>。通配符也很好理解,還是compose的例子,我們可以看到compose的參數為一個Function類型,其中Functin的參數指定了其第一個參數必須是V的父類,第二個參數必須繼承T,也就是T的子類。

源碼解析

1.apply

講完了上面這些就可以開始研究源碼了。

首先我們已經知道了Function是一個泛型類,其中定義了兩個泛型參數T和R,在Function中,T代表輸入參數,R代表返回的結果。也許你很好奇,為什么跟別的java源碼不一樣,Function 的源碼中並沒有具體的邏輯呢?

其實這很容易理解,Function 就是一個函數,其作用類似於數學中函數的定義 ,(x,y)跟<T,R>的作用幾乎一致。

 

y=f(x)y=f(x)

 

所以Function中沒有具體的操作,具體的操作需要我們去為它指定,因此apply具體返回的結果取決於傳入的lambda表達式。

 R apply(T t); 

舉個例子:

public void test(){ Function<Integer,Integer> test=i->i+1; test.apply(5); } /** print:6*/ 

我們用lambda表達式定義了一個行為使得i自增1,我們使用參數5執行apply,最后返回6。這跟我們以前看待Java的眼光已經不同了,在函數式編程之前我們定義一組操作首先想到的是定義一個方法,然后指定傳入參數,返回我們需要的結果。函數式編程的思想是先不去考慮具體的行為,而是先去考慮參數,具體的方法我們可以后續再設置。

再舉個例子:

public void test(){ Function<Integer,Integer> test1=i->i+1; Function<Integer,Integer> test2=i->i*i; System.out.println(calculate(test1,5)); System.out.println(calculate(test2,5)); } public static Integer calculate(Function<Integer,Integer> test,Integer number){ return test.apply(number); } /** print:6*/ /** print:25*/ 

我們通過傳入不同的Function,實現了在同一個方法中實現不同的操作。在實際開發中這樣可以大大減少很多重復的代碼,比如我在實際項目中有個新增用戶的功能,但是用戶分為VIP和普通用戶,且有兩種不同的新增邏輯。那么此時我們就可以先寫兩種不同的邏輯。除此之外,這樣還讓邏輯與數據分離開來,我們可以實現邏輯的復用

當然實際開發中的邏輯可能很復雜,比如兩個方法F1,F2都需要兩個個邏輯AB,但是F1需要A->B,F2方法需要B->A。這樣的我們用剛才的方法也可以實現,源碼如下:

public void test(){ Function<Integer,Integer> A=i->i+1; Function<Integer,Integer> B=i->i*i; System.out.println("F1:"+B.apply(A.apply(5))); System.out.println("F2:"+A.apply(B.apply(5))); } /** F1:36 */ /** F2:26 */ 

也很簡單呢,但是這還不夠復雜,假如我們F1,F2需要四個邏輯ABCD,那我們還這樣寫就會變得很麻煩了。

2.compose和andThen

compose和andThen可以解決我們的問題。先看compose的源碼

  default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); } 

compose接收一個Function參數,返回時先用傳入的邏輯執行apply,然后使用當前Function的apply。

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); } 

andThen跟compose正相反,先執行當前的邏輯,再執行傳入的邏輯。

這樣說可能不夠直觀,我可以換個說法給你看看

compose等價於B.apply(A.apply(5)),而andThen等價於A.apply(B.apply(5))。

public void test(){ Function<Integer,Integer> A=i->i+1; Function<Integer,Integer> B=i->i*i; System.out.println("F1:"+B.apply(A.apply(5))); System.out.println("F1:"+B.compose(A).apply(5)); System.out.println("F2:"+A.apply(B.apply(5))); System.out.println("F2:"+B.andThen(A).apply(5)); } /** F1:36 */ /** F1:36 */ /** F2:26 */ /** F2:26 */ 

我們可以看到上述兩個方法的返回值都是一個Function,這樣我們就可以使用建造者模式的操作來使用。

B.compose(A).compose(A).andThen(A).apply(5); 

這個操作很簡單,你可以自己試試。

探索Java8:(三)Predicate接口的使用

 
轉載

上一篇學習了下Function接口的使用,本篇我們學習下另一個實用的函數式接口Predicate。

Predicate的源碼跟Function的很像,我們可以對比這兩個來分析下。直接上Predicate的源碼:

public interface Predicate<T> { /** * Evaluates this predicate on the given argument. */ boolean test(T t); /** * Returns a composed predicate that represents a short-circuiting logical * AND of this predicate and another. When evaluating the composed * predicate, if this predicate is {@code false}, then the {@code other} * predicate is not evaluated. */ default Predicate<T> and(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) && other.test(t); } /** * Returns a predicate that represents the logical negation of this * predicate. */ default Predicate<T> negate() { return (t) -> !test(t); } /** * Returns a composed predicate that represents a short-circuiting logical * OR of this predicate and another. When evaluating the composed * predicate, if this predicate is {@code true}, then the {@code other} * predicate is not evaluated. */ default Predicate<T> or(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) || other.test(t); } /** * Returns a predicate that tests if two arguments are equal according * to {@link Objects#equals(Object, Object)}. */ static <T> Predicate<T> isEqual(Object targetRef) { return (null == targetRef) ? Objects::isNull : object -> targetRef.equals(object); } } 

Predicate是個斷言式接口其參數是<T,boolean>,也就是給一個參數T,返回boolean類型的結果。跟Function一樣,Predicate的具體實現也是根據傳入的lambda表達式來決定的。

boolean test(T t); 

接下來我們看看Predicate默認實現的三個重要方法and,or和negate

    default Predicate<T> and(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) && other.test(t); } default Predicate<T> negate() { return (t) -> !test(t); } default Predicate<T> or(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) || other.test(t); } 

這三個方法對應了java的三個連接符號&&、||和!,基本的使用十分簡單,我們給一個例子看看:

int[] numbers= {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}; List<Integer> list=new ArrayList<>(); for(int i:numbers) { list.add(i); } Predicate<Integer> p1=i->i>5; Predicate<Integer> p2=i->i<20; Predicate<Integer> p3=i->i%2==0; List test=list.stream().filter(p1.and(p2).and(p3)).collect(Collectors.toList()); System.out.println(test.toString()); /** print:[6, 8, 10, 12, 14]*/ 

我們定義了三個斷言p1,p2,p3。現在有一個從1~15的list,我們需要過濾這個list。上述的filter是過濾出所有大於5小於20,並且是偶數的列表。

假如突然我們的需求變了,我們現在需要過濾出奇數。那么我不可能直接去改Predicate,因為實際項目中這個條件可能在別的地方也要使用。那么此時我只需要更改filter中Predicate的條件。

List test=list.stream().filter(p1.and(p2).and(p3.negate())).collect(Collectors.toList());
/** print:[7, 9, 11, 13, 15]*/ 

我們直接對p3這個條件取反就可以實現了。是不是很簡單?

isEqual這個方法的返回類型也是Predicate,所以我們也可以把它作為函數式接口進行使用。我們可以當做==操作符來使用。

		List test=list.stream()
            .filter(p1.and(p2).and(p3.negate()).and(Predicate.isEqual(7))) .collect(Collectors.toList()); /** print:[7] */

java8 函數式接口編程:https://blog.csdn.net/qq_28410283/category_7718494.html


免責聲明!

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



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