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
posted @ 2020-10-15 16:05  CharyGao  阅读(1589)  评论(0编辑  收藏  举报