天天看點

【睡JDK】Java函數式程式設計接口詳解之Predicate

一、初識

Predicate是Java提供的重要的函數程式設計接口之一,作用主要是用于邏輯判斷。

首先看看源碼:

@FunctionalInterface
public interface Predicate<T> {

    boolean test(T t);

    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);
    }

    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}
           

對函數式程式設計接口有一定了解的同學可能會疑惑,為啥 有這麼多方法,不是說函數式程式設計接口隻有一個方法嗎?确實沒錯,但這個方法隻限于沒有實作的方法,不包括有實作的方法,自從Java引入了default關鍵字後,在接口内是可以編寫default方法的。

喏,上面不是還有一個static方法?其實想想也是能想得通的,static方法屬于類的資訊,不屬于執行個體資訊,我們平時編碼的時候可能不會這麼寫,但是不代表不可以這麼寫哦。

二、基礎用法

我們現在不必糾結其他方法,把注意力集中在

boolean test(T t)

方法上。我們先看一段示例代碼:

List<Integer> list = Arrays.asList(1, 2, 3, 4, 6);
List<Integer> list1 = list.stream()
    .filter(num -> num < 5)
    .collect(Collectors.toList());
           

這段代碼就是過濾清單中小樣5的數字,并生成一個新的清單,當我們點進

filter

方法的實作,代碼如下:

@Override
    public final Stream<P_OUT> filter(Predicate<? super P_OUT> predicate) {
        Objects.requireNonNull(predicate);
        return new StatelessOp<P_OUT, P_OUT>(this, StreamShape.REFERENCE,
                                     StreamOpFlag.NOT_SIZED) {
            @Override
            Sink<P_OUT> opWrapSink(int flags, Sink<P_OUT> sink) {
                return new Sink.ChainedReference<P_OUT, P_OUT>(sink) {
                    @Override
                    public void begin(long size) {
                        downstream.begin(-1);
                    }

                    @Override
                    public void accept(P_OUT u) {
                        if (predicate.test(u))
                            downstream.accept(u);
                    }
                };
            }
        };
    }
           

這裡我們暫時不要關心其他代碼,注意點放在兩個地方,一個就是filter的參數清單

filter(Predicate<? super P_OUT> predicate)

,另一個就是

predicate.test(u)

看到這個兩個地方,你或許就明白了我們平時常用的filter方法其實就是依賴Predicate函數接口來完成邏輯判斷的。

那我們該如何使用Predicate接口呢?請看用例:

// 邏輯判斷工具
public class LogicUtil {
    public static boolean qualified(Predicate<Person> predicate, Person person) {
        return predicate.test(person);
    }
}

// 測試代碼
public static void main(String[] args) {
    List<Person> list = Arrays.asList(new Person("小明",180), new Person("小剛", 178));
    for (Person person : list) {
        if (LogicUtil.qualified(p -> p.getHeight() >= 180, person)) {
            System.out.println(person.getName() + "身高合格!");
        }
    }
}
           

這裡隻是舉了一很簡單的例子,過濾身高大于180的。這裡我們将過濾的條件由調用者傳入,能夠增加程式設計的靈活性,也就是說邏輯的判斷由調用者自行實作。

Predicate使用場景推薦:

在一個公共函數内,大部分邏輯是通用的,但是一小部分判斷邏輯是不一樣的,可以使用Predicate作為公共函數的入參,将那一小部分判斷邏輯通過Lambda表達式的方式傳入公共函數。就像上面

filter

函數的實作一樣。

三、高階用法

既然是接口,那麼我們當然可以去實作它啦。如果你的判斷邏輯比較複雜,用Lambda表達式比較繁瑣或不夠整潔時,你可以去實作Predicate接口,如下:

// 實作接口
public class Filter implements Predicate<Person> {
    @Override
    public boolean test(Person person) {
        return person.getHeight() >= 180 && Objects.equals(person.getGender(), "man");
    }
}

// 工具
public class LogicUtil {
    public static boolean qualified3(Predicate<Person> filter, Person person) {
        return filter.test(person);
    }
}

// 測試代碼
public static void main(String[] args) {
    List<Person> list = Arrays.asList(new Person("小明",180, "man"),
                                      new Person("小剛", 178, "man"),
                                      new Person("小紅", 190, "woman"));
    for (Person person : list) {
        if (LogicUtil.qualified3(new Filter(), person)) {
            System.out.println(person.getName() + "合格!");
        }
    }
}
           

這種用法在抽象設計方面有很大的優勢。

四、Predicate的其他方法

文章開頭我們就看到Predicate接口内還有and、negate、or、isEqual等方法,下面就簡單的介紹一下。

4.1 and

先看看and函數的源碼:

default Predicate<T> and(Predicate<? super T> other) {
    Objects.requireNonNull(other);
    return (t) -> test(t) && other.test(t);
}
           
說明:在傳回語句中,(t)是lambda表達式的參數,test(t) && other.test(t)是主體。

and函數的功能就是拼接兩個Predicate,傳回新的Predicate,看看用法:

// 身高過濾器
public class HeightFilter implements Predicate<Person> {
    @Override
    public boolean test(Person person) {
        return person.getHeight() >= 180;
    }
}

// 性别過濾器
public class GenderFilter implements Predicate<Person> {
    @Override
    public boolean test(Person person) {
        return Objects.equals(person.getGender(), "man");
    }
}

// 測試代碼
public static void main(String[] args) {
    List<Person> list = Arrays.asList(new Person("小明", 180, "man"),
                                      new Person("小剛", 178, "man"),
                                      new Person("小紅", 190, "woman"));
    List<Person> list1 = list.stream()
        .filter(new HeightFilter().and(new GenderFilter()))
        .collect(Collectors.toList());
}

           

4.2 negate

先看看negate函數源碼:

default Predicate<T> negate() {
    return (t) -> !test(t);
}
           

函數很簡單,就是傳回predicate的否定。

用法:

// 身高過濾器
public class HeightFilter implements Predicate<Person> {
    @Override
    public boolean test(Person person) {
        return person.getHeight() >= 180;
    }
}

// 測試代碼
public static void main(String[] args) {
    List<Person> list = Arrays.asList(new Person("小明", 180, "man"),
                                      new Person("小剛", 178, "man"),
                                      new Person("小紅", 190, "woman"));
    List<Person> list1 = list.stream()
        .filter(new HeightFilter().negate())
        .collect(Collectors.toList());
}
           

就是傳回所有身高小于180的資料人。

4.3 or

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

學完and函數,了解or就沒啥問題了,簡單說,就是滿足衆多條件中一個一個即可:

// 身高過濾器
public class HeightFilter implements Predicate<Person> {
    @Override
    public boolean test(Person person) {
        return person.getHeight() >= 180;
    }
}

// 性别過濾器
public class GenderFilter implements Predicate<Person> {
    @Override
    public boolean test(Person person) {
        return Objects.equals(person.getGender(), "woman");
    }
}

// 測試代碼
public static void main(String[] args) {
    List<Person> list = Arrays.asList(new Person("小明", 180, "man"),
                                      new Person("小剛", 178, "man"),
                                      new Person("小紅", 160, "woman"));
    List<Person> list1 = list.stream()
        .filter(new HeightFilter().or(new GenderFilter()))
        .collect(Collectors.toList());
}
           
此時,小明、小紅都滿足條件

4.4 isEqual

再來看看最後一個靜态方法:

static <T> Predicate<T> isEqual(Object targetRef) {
    return (null == targetRef)
        ? Objects::isNull
            : object -> targetRef.equals(object);
}
           

咋一看,有的小夥伴可能不是很了解,其實拆分一下也不難。

首先很容易看出裡面是一個三目運算符;

其次Objects::isNull就是lambda表達式的一種簡化寫法,還原就是如下語句:

即:當targetRef=null時,Objects.isNull(object)的結果就是Predicate的結果

否則,傳回object -> targetRef.equals(object)

用法:

// 測試代碼
public static void main(String[] args) {
    Person xiaoming = new Person("小明", 180, "man");
    List<Person> list = Arrays.asList(xiaoming,
                                      new Person("小剛", 178, "man"),
                                      new Person("小紅", 160, "woman"));
    List<Person> list1 = list.stream()
        .filter(Predicate.isEqual(xiaoming))
        .collect(Collectors.toList());
}
           

五、Predicate的其他變體

接口名 參數 傳回類型 描述
BiPredicate (T, U) boolean 接受兩個參數
DoublePredicate double boolean 接受double類型參數
IntPredicate int boolean 接受int類型參數
LongPredicate long boolean 接受long類型參數

好了,有關Predicate接口的介紹到此為止,(づ ̄3 ̄)づ╭❤~

文章推薦:

【睡JDK】Java函數式程式設計接口詳解之Consumer、Function

【睡JDK】Java函數式程式設計接口詳解之Supplier

【睡JDK】Java函數式程式設計接口詳解之UnaryOperator、BinaryOperator

【睡JDK】Java函數式程式設計接口詳解之Predicate
【睡JDK】Java函數式程式設計接口詳解之Predicate