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表达式:简化代码的秘密武器
【10月更文挑战第11天】 在Java编程中,Lambda表达式是一种简洁而强大的工具,它允许我们将函数作为参数传递给其他方法。本文将介绍Lambda表达式的基本概念、使用方法以及在实际项目中的应用案例,帮助你更好地理解和利用这一特性来简化代码。
20 8
|
9天前
|
并行计算 Java API
Java中的Lambda表达式及其应用
本文将深入探讨Java中的Lambda表达式,从基本概念到实际应用。我们将了解Lambda表达式的定义、优势和使用场景,并通过实例展示其在Java编程中的强大功能。无论是初学者还是经验丰富的开发者,都可以从中获得有价值的见解。
|
8天前
|
Java
让星星⭐月亮告诉你,jdk1.8 Java函数式编程示例:Lambda函数/方法引用/4种内建函数式接口(功能性-/消费型/供给型/断言型)
本示例展示了Java中函数式接口的使用,包括自定义和内置的函数式接口。通过方法引用,实现对字符串操作如转换大写、数值转换等,并演示了Function、Consumer、Supplier及Predicate四种主要内置函数式接口的应用。
14 1
|
12天前
|
并行计算 Java API
探索Java中的Lambda表达式:简化代码,提高可读性
【10月更文挑战第5天】Lambda表达式在Java 8中引入,旨在简化集合操作和并行计算。本文通过介绍Lambda表达式的基本概念、语法结构以及实际应用示例,展示了如何利用这一特性编写更加简洁、易读的代码。我们将从Lambda的基础入手,逐步深入到其在函数式接口中的应用,并探讨其对Java编程范式的影响。
|
12天前
|
Java
探索Java中的Lambda表达式
【10月更文挑战第5天】Lambda表达式是Java 8引入的一个新特性,它允许我们将功能作为方法参数,或者代码作为数据进行处理。这种表达式提供了一种简洁的表示匿名函数的方式,使得代码更加简洁易读。本文将深入探讨Lambda表达式的基本概念,使用方法,以及如何在Java程序中有效地应用它们。
11 1
|
22天前
|
Java 数据处理 开发者
Java中的Lambda表达式:简化你的代码之路
【8月更文挑战第66天】Lambda表达式在Java 8中首次引入,它为Java开发者提供了一种更简洁、更灵活的编程方式。本文将通过简单易懂的语言和实际代码示例,引导你理解Lambda表达式的基本概念、语法结构以及如何在Java项目中应用它来简化代码。无论你是Java新手还是有经验的开发者,这篇文章都将帮助你更好地掌握这一强大的工具。
42 11
|
22天前
|
Java API
Java中的Lambda表达式:一种简洁而强大的编程工具
本文将深入探讨Java中的Lambda表达式,它是一种简洁而强大的编程工具。通过本文,您将了解Lambda表达式的基本概念、语法结构以及使用方法。我们还将介绍Lambda表达式的应用场景和最佳实践,并通过实例演示其在简化代码、提高可读性和编写并行代码方面的优势。最后,我们将讨论使用Lambda表达式时需要注意的一些事项和限制。无论您是初学者还是有经验的Java开发人员,都可以从本文中获得有价值的知识和技能。现在,让我们一起探索Java中Lambda表达式的奇妙世界吧!
Java 8 Predicate 函数接口
Java 8 Predicate 函数接口
276 0
|
4天前
|
安全 Java UED
Java中的多线程编程:从基础到实践
本文深入探讨了Java中的多线程编程,包括线程的创建、生命周期管理以及同步机制。通过实例展示了如何使用Thread类和Runnable接口来创建线程,讨论了线程安全问题及解决策略,如使用synchronized关键字和ReentrantLock类。文章还涵盖了线程间通信的方式,包括wait()、notify()和notifyAll()方法,以及如何避免死锁。此外,还介绍了高级并发工具如CountDownLatch和CyclicBarrier的使用方法。通过综合运用这些技术,可以有效提高多线程程序的性能和可靠性。
|
4天前
|
缓存 Java UED
Java中的多线程编程:从基础到实践
【10月更文挑战第13天】 Java作为一门跨平台的编程语言,其强大的多线程能力一直是其核心优势之一。本文将从最基础的概念讲起,逐步深入探讨Java多线程的实现方式及其应用场景,通过实例讲解帮助读者更好地理解和应用这一技术。
19 3