Java 8 - 03 Lambda 函数式接口Predicate & Consumer & Function & Supplier

2021-08-17 11:33:28 浏览数 (1)

Pre

Java 8 - 02 Lambda Expression中我们讨论了函数式接口, 函数式接口定义且只定义了一个抽象方法。因为抽象方法的签名可以描述Lambda表达式的签名。函数式接口的抽象方法的签名称为函数描述符。

所以为了应用不同的Lambda表达式,我们需要一套能够描述常见函数描述符的函数式接口Java API中已经有了几个函数式接口,比如 Comparable 、 Runnable 和Callable 。

Java 8 在 java.util.function 包中引入了几个新的函数式接口,比比较常用的Predicate 、 Consumer 和 Function 等 。


Predicate 断言型函数式接口

代码语言:javascript复制
package java.util.function;

import java.util.Objects;

/**
 * Represents a predicate (boolean-valued function) of one argument.
 *
 * This is a functional interface
 * whose functional method is {@link #test(Object)}.
 *
 * @param  the type of the input to the predicate
 *
 * @since 1.8
 */
@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);
    }
}

标注了@FunctionalInterface , 抽象方法只能包含一个 , default 方法 和 static的方法除外 , 比如 Predicate ,只有test是抽象方法,其他的几个都是default级别和static修饰的方法,所以Predicate也是一个函数式接口 。

java.util.function.Predicate 接口定义了一个名叫 test 的抽象方法,它接受泛型T 对象,并返回一个 boolean 。

如果需要表示一个涉及类型 T 的布尔表达式时,就可以使用这个接口

举个例子

代码语言:javascript复制
import com.artisan.domain.Enginner;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

/**
 * @author 小工匠
 * @version v1.0
 * @create 2020-05-16 9:13
 * @motto show me the code ,change the word
 * @blog https://artisan.blog.csdn.net/
 * @description
 **/

public class PredicateDemo {

    /**
     * 过滤符合规则的泛型类
     * @param list
     * @param predicate
     * @param 
     * @return
     */
    private static <T> List<T> filter(List<T> list , Predicate<T> predicate){
        List<T> targetList = new ArrayList<>();
        for (T t :list){
            if (predicate.test(t)){
                targetList.add(t);
            }
        }
        return  targetList;
    }

     public static void main(String[] args) {
        List<Enginner> enginnerList = Arrays.asList(new Enginner("Java", 18), new Enginner("GO", 20));
        List<Enginner> goEngineerList = filter(enginnerList, enginner -> enginner.getJob().equals("GO"));
        System.out.println(goEngineerList);
    }
}

我们通过 filter(enginnerList, (enginner) -> enginner.getJob().equals("GO")); 第二个参数 (enginner) -> enginner.getJob().equals("GO") 方法签名返回的是Boolean,所以可以使用Predicate这个JDK8中提供的接口 。

当然了你也可以直接写一个类似Predicate的函数式接口来供自己调用 ,如下

代码语言:javascript复制
@FunctionalInterface
public interface Filter<T> {

    boolean filter(T t);
    
}

那么就变成了如下的样子

代码语言:javascript复制
    private static <T> List<T> filterCustom(List<T> list , Filter<T> filter){
        List<T> targetList = new ArrayList<>();
        for (T t :list){
            if (filter.filter(t)){
                targetList.add(t);
            }
        }
        return  targetList;
    }

main方法中调用如下

代码语言:javascript复制
 List<Enginner> javaEngineerList= filterCustom(enginnerList, enginner -> enginner.getJob().equals("Java"));
 
System.out.println(javaEngineerList);

Consumer 消费型函数式接口

代码语言:javascript复制
package java.util.function;

import java.util.Objects;

/**
 * Represents an operation that accepts a single input argument and returns no
 * result. Unlike most other functional interfaces, {@code Consumer} is expected
 * to operate via side-effects.
 *
 
 * @since 1.8
 */
@FunctionalInterface
public interface Consumer<T> {

  
    void accept(T t);

  
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

标注了@FunctionalInterface , 抽象方法只能包含一个 , default 方法 和 static的方法除外 , 比如 Consumer,只有accept是抽象方法Consumer是default级的方法,所以Consumer也是一个函数式接口 。

java.util.function.Consumer 定义了一个名叫 accept 的抽象方法,它接受泛型 T的对象,没有返回( void )。

如果需要访问类型 T 的对象,并对其执行某些操作,就可以使用这个接口

举个例子

比如,你可以用它来创建一个 forEach 方法,接受一个 Integers 的列表,并对其中每个元素执行操作。 假设我们要使用这个 forEach 方法,并配合Lambda来打印列表中的所有元素。

代码语言:javascript复制
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;

/**
 * @author 小工匠
 * @version v1.0
 * @create 2020-05-16 20:20
 * @motto show me the code ,change the word
 * @blog https://artisan.blog.csdn.net/
 * @description
 **/

public class ComusmerDemo {

    public static <T> void doForEach(List<T> tList, Consumer<T> consumer){
        for (T t: tList ) {
            consumer.accept(t);
        }
    }

    public static void main(String[] args) {
        // 第二个参数 Lambda是 Consumer 中accept 方法的实现
        doForEach(Arrays.asList(1,2,3,5,7),(Integer i) -> System.out.println(i));
    }
}

Function 功能型函数式接口

代码语言:javascript复制
package java.util.function;

import java.util.Objects;

/**
 * Represents a function that accepts one argument and produces a result.
 *
 * @param  the type of the input to the function
 * @param  the type of the result of the function
 *
 * @since 1.8
 */
@FunctionalInterface
public interface Function<T, R> {

  
    R apply(T t);

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

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

 
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

java.util.function.Function 接口定义了一个叫作 apply 的方法,它接受一个泛型 T 的对象,并返回一个泛型 R 的对象。

如果我们需要定义一个Lambda,将输入对象的信息映射到输出,就可以使用这个接口 ,举个例子提取工程师的职位或把字符串映射为它的长度等等

来个小demo : 利用Function 来创建一个 map 方法,以将一个 String 列表映射到包含每个

String 长度的 Integer 列表

代码语言:javascript复制
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;

/**
 * @author 小工匠
 * @version v1.0
 * @create 2020-05-16 20:47
 * @motto show me the code ,change the word
 * @blog https://artisan.blog.csdn.net/
 * @description
 **/

public class FunctionDemo {

    /**
     * @param list
     * @param function
     * @param 
     * @param 
     * @return 返回 List
     */
    public static <T, R> List<R> doSomething(List<T> list, Function<T, R> function) {
        List<R> rList = new ArrayList<>();
        for (T t : list) {
            R apply = function.apply(t);
            rList.add(apply);
        }
        return rList;
    }

    public static void main(String[] args) {
        // Lambda是 Function 接口的 apply 方法的 实现
        List<Integer> list = doSomething(Arrays.asList("artisan", "small", "happy"), (String s) -> s.length());
        System.out.println(list);
    }

}

Supplier 供给型函数式接口

代码语言:javascript复制
package java.util.function;

/**
 * Represents a supplier of results.
 *
 * There is no requirement that a new or distinct result be returned each
 * time the supplier is invoked.
 * 

 * @param  the type of results supplied by this supplier
 *
 * @since 1.8
 */
@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

无参数,返回一个结果。

代码语言:javascript复制
import com.artisan.domain.Enginner;

import java.util.function.Supplier;

/**
 * @author 小工匠
 * @version v1.0
 * @create 2020-05-16 21:08
 * @motto show me the code ,change the word
 * @blog https://artisan.blog.csdn.net/
 * @description
 **/

public class SupplierDemo {

    public static <T> T doGet(Supplier<T> supplier) {
        return supplier.get();
    }

    public static void main(String[] args) {
        Enginner enginneer = new Enginner("JAVA", 18);

        String s = doGet(() -> enginneer.getJob());

        System.out.println(s);
    }
}

小结

我们介绍了4个泛型函数式接口: Predicate 、 Consumer 、Function 、Supplier

还有些函数式接口专为某些类型而设计。

回顾一下:Java类型要么是引用类型(比如 Byte 、 Integer 、 Object 、 List ),要么是原始类型(比如 int 、 double 、 byte 、 char ).

但是泛型(比如 Consumer 中的 T )只能绑定到引用类型。这是由泛型内部的实现方式造成的。因此,在Java里有一个将原始类型转换为对应的引用类型的机制。这个机制叫作装箱(boxing)。相反的操作,也就是将引用类型转换为对应的原始类型,叫作拆箱(unboxing).

Java还有一个自动装箱机制来帮助程序员执行这一任务:装箱和拆箱操作是自动完成的。

比如,这就是为什么下面的代码是有效的(一个 int 被装箱成为Integer )

代码语言:javascript复制
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 100; i  ){
	list.add(i);
}

但这在性能方面是要付出代价的。装箱后的值本质上就是把原始类型包裹起来,并保存在堆里。因此,装箱后的值需要更多的内存,并需要额外的内存搜索来获取被包裹的原始值。

Java 8为我们前面所说的函数式接口带来了一个专门的版本,以便在输入和输出都是原始类型时避免自动装箱的操作。

比如,在下面的代码中,使用 IntPredicate 就避免了对值 1000 进行装箱操作,但要是用 Predicate 就会把参数 1000 装箱到一个 Integer 对象中:

代码语言:javascript复制
public class IntPredicateDemo {


    public static void main(String[] args) {

        // 无装箱
        IntPredicate intPredicate = (int i) -> i % 2 == 0;
        intPredicate.test(1000);


        // 装箱
        Predicate<Integer> predicate = (Integer i) -> i % 2 == 0;
        predicate.test(1000);
    }
}

一般来说,针对专门的输入参数类型的函数式接口的名称都要加上对应的原始类型前缀,比如 DoublePredicate 、 IntConsumer 、 LongBinaryOperator 、 IntFunction 等。

Function接口还有针对输出参数类型的变种: ToIntFunction 、 IntToDoubleFunction

来个小测验

代码语言:javascript复制
请构造一个可以利用这些函数式接口的有效Lambda
表达式:
(1)  T->R
(2)  (int, int)->int
(3)  T->void
(4)  ()->T
(5)  (T, U)->R
代码语言:javascript复制
(1)  Function<T,R> 不错。它一般用于将类型 T 的对象转换为类型 R 的对象(比如
Function<Apple, Integer> 用来提取苹果的重量)。
(2)  IntBinaryOperator 具有唯一一个抽象方法,叫作 applyAsInt ,它代表的函数描述
符是 (int, int) -> int 。
(3)  Consumer<T> 具有唯一一个抽象方法叫作 accept ,代表的函数描述符是 T -> void 。
(4)  Supplier<T> 具有唯一一个抽象方法叫作 get ,代表的函数描述符是 ()-> T 。或者,
Callable<T> 具有唯一一个抽象方法叫作 call ,代表的函数描述符是 () -> T 。
(5)  BiFunction<T, U, R> 具有唯一一个抽象方法叫作 apply ,代表的函数描述符是 (T,
U) -> R 。

最后 总结关于函数式接口和Lambda


函数式接口如何处理异常信息

Note : ,任何函数式接口都不允许抛出受检异常(checked exception)。如果你需要Lambda表达式来抛出异常,有两种办法:定义一个自己的函数式接口,并声明受检异常,或者把Lambda包在一个 try/catch 块中。

自己的函数式接口如下:

代码语言:javascript复制
@FunctionalInterface
public interface BufferedReaderProcessor {
	String process(BufferedReader b) throws IOException;
}

调用

代码语言:javascript复制
BufferedReaderProcessor p = (BufferedReader br) -> br.readLine();

但是你可能是在使用一个接受函数式接口的API,比如 Function ,没有办法自己创建一个。这种情况下, 可以显式捕捉受检异常:

代码语言:javascript复制
Function<BufferedReader, String> f = (BufferedReader b) -> {
try {
	return b.readLine();
}
catch(IOException e) {
	throw new RuntimeException(e);
	}
};

0 人点赞