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

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

20200510181139786.png

Pre


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


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


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


Predicate 断言型函数式接口


20200516090258393.png

package java.util.function;
import java.util.Objects;
/**
 * Represents a predicate (boolean-valued function) of one argument.
 *
 * <p>This is a <a href="package-summary.html">functional interface</a>
 * whose functional method is {@link #test(Object)}.
 *
 * @param <T> 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<T> 接口定义了一个名叫 test 的抽象方法,它接受泛型T 对象,并返回一个 boolean 。


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


举个例子

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 <T>
     * @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的函数式接口来供自己调用 ,如下

@FunctionalInterface
public interface Filter<T> {
    boolean filter(T t);
}

那么就变成了如下的样子

    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方法中调用如下

 List<Enginner> javaEngineerList= filterCustom(enginnerList, enginner -> enginner.getJob().equals("Java"));
System.out.println(javaEngineerList);


20200516093159105.png


Consumer 消费型函数式接口


20200516201713324.png


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<T> 定义了一个名叫 accept 的抽象方法,它接受泛型 T的对象,没有返回( void )。


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


举个例子


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

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

20200516210151238.png


Function 功能型函数式接口


20200516205045829.png


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
 * @param <R> 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<T, R> 接口定义了一个叫作 apply 的方法,它接受一个泛型 T 的对象,并返回一个泛型 R 的对象。


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


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

String 长度的 Integer 列表

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 <T>
     * @param <R>
     * @return 返回 List<R>
     */
    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);
    }
}

20200516210131980.png


Supplier 供给型函数式接口

20200516211144618.png


package java.util.function;
/**
 * Represents a supplier of results.
 *
 * <p>There is no requirement that a new or distinct result be returned each
 * time the supplier is invoked.
 * 
 * @param <T> the type of results supplied by this supplier
 *
 * @since 1.8
 */
@FunctionalInterface
public interface Supplier<T> {
    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}


无参数,返回一个结果。

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


2020051621174739.png

小结


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


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


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


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


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


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

List<Integer> list = new ArrayList<>();
for (int i = 0; i < 100; i++){
  list.add(i);
}

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


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


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

20200516212246329.png


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<T> 、 IntToDoubleFunction 等

20200516213553115.png

20200516213623205.png

来个小测验

请构造一个可以利用这些函数式接口的有效Lambda
表达式:
(1)  T->R
(2)  (int, int)->int
(3)  T->void
(4)  ()->T
(5)  (T, U)->R
(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

20200516214051854.png


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


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

自己的函数式接口如下:


@FunctionalInterface
public interface BufferedReaderProcessor {
  String process(BufferedReader b) throws IOException;
}

调用

BufferedReaderProcessor p = (BufferedReader br) -> br.readLine();


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

Function<BufferedReader, String> f = (BufferedReader b) -> {
try {
  return b.readLine();
}
catch(IOException e) {
  throw new RuntimeException(e);
  }
};


相关文章
|
6月前
|
安全 Java API
Java中的Lambda表达式:简洁与功能的结合
Java中的Lambda表达式:简洁与功能的结合
496 211
|
8月前
|
SQL JSON 安全
Java 8 + 中 Lambda 表达式与 Stream API 的应用解析
摘要:本文介绍了Java 8+核心新特性,包括Lambda表达式与Stream API的集合操作(如过滤统计)、函数式接口的自定义实现、Optional类的空值安全处理、接口默认方法与静态方法的扩展能力,以及Java 9模块化系统的组件管理。每个特性均配有典型应用场景和代码示例,如使用Stream统计字符串长度、Optional处理Map取值、模块化项目的依赖声明等,帮助开发者掌握现代Java的高效编程范式。(150字)
163 1
|
11月前
|
SQL Rust Java
怎么理解Java中的lambda表达式
Lambda表达式是JDK8引入的新语法,用于简化匿名内部类的代码写法。其格式为`(参数列表) -&gt; { 方法体 }`,适用于函数式接口(仅含一个抽象方法的接口)。通过Lambda表达式,代码更简洁灵活,提升Java的表达能力。
212 4
|
10月前
|
Java 编译器 API
Java Lambda 表达式:以 Foo 接口为例深入解析
本文深入解析了 Java 8 中 Lambda 表达式的用法及其背后的函数式接口原理,以 `Foo` 接口为例,展示了如何通过简洁的 Lambda 表达式替代传统匿名类实现。文章从 Lambda 基本语法、函数式接口定义到实际应用层层递进,并探讨默认方法与静态方法的扩展性,最后总结常见误区与关键点,助你高效优化代码!
238 0
|
Java API 开发者
Java中的Lambda表达式与Stream API的协同作用
在本文中,我们将探讨Java 8引入的Lambda表达式和Stream API如何改变我们处理集合和数组的方式。Lambda表达式提供了一种简洁的方法来表达代码块,而Stream API则允许我们对数据流进行高级操作,如过滤、映射和归约。通过结合使用这两种技术,我们可以以声明式的方式编写更简洁、更易于理解和维护的代码。本文将介绍Lambda表达式和Stream API的基本概念,并通过示例展示它们在实际项目中的应用。
|
安全 Java API
Java中的Lambda表达式:简化代码的现代魔法
在Java 8的发布中,Lambda表达式的引入无疑是一场编程范式的革命。它不仅让代码变得更加简洁,还使得函数式编程在Java中成为可能。本文将深入探讨Lambda表达式如何改变我们编写和维护Java代码的方式,以及它是如何提升我们编码效率的。
|
Java
Java接口和抽象类
Java接口和抽象类
216 0
|
设计模式 搜索推荐 Java
java接口和抽象类的区别,以及使用选择
java接口和抽象类的区别,以及使用选择
226 0
|
设计模式 Java
【惊天揭秘】Java编程绝技大曝光:接口、抽象类、静态类与非静态类的神秘面纱终被揭开!
【8月更文挑战第22天】Java支持面向对象编程,通过接口、抽象类、静态类(如枚举与工具类)及普通类实现设计原则。接口定义行为规范,允许多重继承;抽象类含未实现的抽象方法,需子类完成;静态类常为工具类,提供静态方法;普通类则实例化对象。恰当运用这些结构能提升程序质量。
188 2