• 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] */
  • 相关阅读:
    Fire and Motion[转载]
    HLSL2GLSL v0.9 Released
    CEGUI Release of 0.5.0 stable by CrazyEddie 6th November 2006
    MapInfo 连接Oracle
    MapInfo连接SQLServer
    视图的创建及使用(sql server 2005)
    MapInfo 建立永久表
    MapInfo Update Feature
    MapInfo导入.TAB和.mws的方法
    触发器的创建及使用(sqlserver 2000)
  • 原文地址:https://www.cnblogs.com/Chary/p/13821147.html
Copyright © 2020-2023  润新知