何时以及为什么要使用Java的Supplier和Consumer接口?

136
作为非Java程序员学习Java,我正在阅读有关“Supplier”和“Consumer”接口的内容。我目前无法理解它们的用途和意义。
何时以及为什么要使用这些接口?有人能给我一个简单易懂的例子吗?
我发现文档示例对我的理解不够简洁。

7
API文档的每一页顶部都有一个标为“USE”的链接,您可以点击它来查看ConsumerSupplier的用法。您也可以在教程中搜索“Consumer”... - Holger
15
我喜欢Stuart Marks的回答。我认为大多数回答者都错过了重点。问题不是“如何”编写Suppliers、Consumers和Functions,而是在世界上“为什么”你想要使用它们?对于不习惯使用它们的人来说,它们会使代码变得更加复杂。但是使用它们的好处并不清楚。 - anton1980
就我所见(我与您对于离题描述的沮丧感同身受),这只是一种巧妙的方式,将对象类型和对象处理从代码中使用的对象中抽象出来。这使得可以通过简单地定义不同的新类并将它们注入到供应商和消费者接口中,将相同的代码应用于许多不同类型的对象。因此,在警察记录系统中,所有嫌疑人都使用相同的表面代码,但每个人的最终打印输出取决于每个嫌疑人的分类,例如“公民”、“小偷”、“盗窃”、“重罪犯”等。 - Trunk
7个回答

214
你之所以难以理解像java.util.function中的函数式接口这样的内容,是因为这里定义的接口没有任何含义!它们主要存在于表示结构,而不是语义
这在大多数Java API中都是不典型的。典型的Java API,例如类或接口,具有意义,你可以开发一个关于它所代表的东西的心智模型,并用它来理解其操作。例如,考虑java.util.List。列表是其他对象的容器。它们有一个序列和一个索引。列表中包含的对象数量由size()返回。每个对象在0..size-1(包括)范围内都有一个索引。可以通过调用list.get(i)来检索索引为i的对象。等等。 java.util.function中的函数式接口没有这样的含义。相反,它们只是表示函数的结构的接口,例如参数的数量、返回值的数量以及(有时)参数或返回值是否为原始类型。因此我们有了像Function<T,R>这样的东西,它表示一个接受类型为T的单个参数并返回类型为R的值的函数。就是这样。这个函数做什么?嗯,它可以做任何事情......只要它接受一个参数并返回一个值。这就是为什么Function<T,R>的规范几乎只有“表示接受一个参数并产生结果的函数”。
显然,在编写代码时,它具有意义,而且那个意义必须来自某个地方。对于函数式接口,含义来自它们使用的上下文。在孪生Map<K,V> API中,接口Function<T,R>没有孤立的意义。然而,存在以下内容:
V computeIfAbsent(K key, Function<K,V> mappingFunction)

(为了简洁起见,通配符已省略)

啊,这里使用的Function是作为“映射函数”。那它是做什么用的呢?在这个上下文中,如果key还没有出现在映射中,就会调用映射函数,并将key传递给它并期望它生成一个值,然后将生成的键值对插入到映射中。

因此,你不能查看Function(或任何其他函数接口)的规范并试图理解它们的含义。你必须查看它们在其他API中的使用方式才能理解它们的含义,并且这个含义仅适用于该上下文。


7
基本上,它只是作为类型的功能。 - Jack Guo
另一个有用的信息是,函数式接口可以有多个实现方法,可以为您的代码添加行为。 - Jhon Mario Lotero
1
最后两段解决了我一直以来的疑惑。我第一次遇到Suppliers是在尝试使用Forge API编写(或学习编写)Minecraft模组时。我正在跟随教程,其中展示了编写一个接收名为“block”的Supplier参数的函数。根据你所说的,这可能是因为方块可以根据其掉落表提供不同的物品(尽管通常只掉落自身)。这是总体思路吗?一个类,在不同的上下文中,可以“获取”(或在消费者的情况下设置)不同的东西? - Twisted Code

117

这是供应商:

public Integer getInteger() {
    return new Random().nextInt();
}

这是消费者:

public void sum(Integer a, Integer b) {
    System.out.println(a + b);
}

简单来说,一个供应商是返回某个值(作为其返回值)的方法。而消费者是使用某个值(作为方法参数)并对其进行一些操作的方法。

这将转换为类似以下内容:

// new operator itself is a supplier, of the reference to the newly created object
Supplier<List<String>> listSupplier = ArrayList::new;
Consumer<String> printConsumer = a1 -> System.out.println(a1);
BiConsumer<Integer, Integer> sumConsumer = (a1, a2) -> System.out.println(a1 + a2);

关于用法,最基本的例子是:Stream#forEach(Consumer) 方法。它接受一个消费者(Consumer),该消费者从你正在迭代的流中消耗元素,并对每个元素执行一些操作。可能会打印它们。

Consumer<String> stringConsumer = (s) -> System.out.println(s.length());
Arrays.asList("ab", "abc", "a", "abcd").stream().forEach(stringConsumer);

3
那么,供应商是创建返回“某物”的方法实例的一种方式吗? - james emanon
3
@jamesemanon 没错。这可能是一个方法引用或 lambda 表达式。 - Rohit Jain
21
与直接调用方法相比,使用Supplier的好处在于它可以充当中间人并传递“返回”值。 - james emanon
2
Consumer<Integer, Integer> 不是有效的。一个消费者只有一个类型参数。 - johnlemon
6
为什么要创造这样一个结构?在Java中拥有它解决了什么问题? - Trunk
显示剩余3条评论

38

Supplier是指不接收参数并返回值的任何方法。它的工作就是提供预期类的实例。例如,每个'getter'方法的引用都是一个Supplier

public Integer getCount(){
    return this.count;
}

它的实例方法引用 myClass::getCount 是一个 Supplier<Integer> 的实例。

Consumer 是任何接受参数且不返回值的方法。它被调用是由于其副作用。在Java术语中,Consumer 是指代 void 方法的惯用语法。'setter' 方法是一个很好的例子:

public void setCount(int count){
    this.count = count;
}

它的实例方法引用 myClass::setCount 是一个 Consumer<Integer>IntConsumer 的实例。

Function<A,B> 是一个接受一个类型为 A 的参数并返回另一个类型为 B 的任何方法。 这可以称为“转换”。Function<A,B> 接受 A 并返回 B。值得注意的是,对于给定的 A 值,函数应始终返回特定的 B 值。实际上,AB 可以是相同的类型,例如以下内容:

public Integer addTwo(int i){
    return i+2;
}

它的实例方法引用 myClass:addTwo 是一个 Function<Integer, Integer> 和一个 ToIntFunction<Integer>

对于getter的类方法参考是另一个函数的示例。

public Integer getCount(){
    return this.count;
}

它的类方法引用 MyClass::getCount 是一个 Function<MyClass,Integer>ToIntFunction<MyClass> 的实例。


21
为什么在java.util.function包中定义了Consumer/Supplier/其他功能接口:Consumer和Supplier是Java 8中提供的许多内置功能接口之一。所有这些内置功能接口的目的是为具有常见函数描述符(功能方法签名/定义)的功能接口提供一个准备好的“模板”。
假设我们需要将类型T转换为另一种类型R。如果我们将像这样定义的任何函数作为参数传递给方法,那么该方法将需要定义一个功能接口,其功能/抽象方法以类型T的参数作为输入并输出类型R的参数。现在,可能会有很多这样的情况,程序员们将不得不为他们的需求定义多个功能接口。为了避免这种情况,简化编程并在使用功能接口方面带来共同标准,已定义了一组内置功能接口,例如Predicate、Function、Consumer和Supplier。
Consumer的作用是什么:Consumer功能接口接受一个输入,对该输入执行某些操作,并不给出任何输出。它的定义如下(来自Java源代码)-
@FunctionalInterface
public interface Consumer<T> {
 void accept(T t);
}

这里的accept()是一个功能性/抽象方法,它接受输入并不返回输出。因此,如果您想要输入一个整数,并对其进行处理而没有任何输出,则可以使用Consumer的实例,而不是定义自己的接口。

Supplier是做什么的:Supplier功能接口不接受任何输入,但返回一个输出。它的定义如下(来自Java源代码)-

@FunctionalInterface
public interface Supplier<T> {
  T get();
}

无论在什么地方,只要你需要一个返回某些东西(例如整数),但不需要输出的函数实例,就可以使用 Supplier。
如果需要更清晰的说明以及示例用法,可以参考我的博客文章——关于 Consumer 和 Supplier 接口的使用,分别参考以下链接:http://www.javabrahman.com/java-8/java-8-java-util-function-consumer-tutorial-with-examples/http://www.javabrahman.com/java-8/java-8-java-util-function-supplier-tutorial-with-examples/

12

1. 意义

请参考我在这里和另一个这里的答案,简而言之,这些新接口为每个人提供了惯例描述性,以便使用(+时髦的方法链接,如.forEach(someMethod().andThen(otherMethod())))。

2. 区别

Consumer:接收某物,执行某些操作,不返回任何内容:void accept(T t)

Supplier:不接收任何内容,返回某物:T get()(与Consumer相反,基本上是通用的“getter”方法)

3. 用法

// Consumer: It takes something (a String) and does something (prints it) 
    List<Person> personList = getPersons();

     personList.stream()
                    .map(Person::getName)    
                    .forEach(System.out::println); 

供应商:封装重复的代码,例如代码执行时间

public class SupplierExample {

    public static void main(String[] args) {

        // Imagine a class Calculate with some methods
        Double result1 = timeMe(Calculate::doHeavyComputation);
        Double result2 = timeMe(Calculate::doMoreComputation);
    }
    private static Double timeMe(Supplier<Double> code) {

        Instant start = Instant.now();
        // Supplier method .get() just invokes whatever it is passed
        Double result = code.get();
        Instant end = Instant.now();

        Duration elapsed = Duration.between(start,end);
        System.out.println("Computation took:" + elapsed.toMillis());

        return result;
    }
}

1
消费者和供应者是Java提供的接口。消费者用于迭代列表元素,供应者用于提供对象。
通过代码演示,您可以轻松理解。 消费者
package com.java.java8;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

/**
 * The Class ConsumerDemo.
 *
 * @author Ankit Sood Apr 20, 2017
 */
public class ConsumerDemo {

    /**
     * The main method.
     *
     * @param args
     *            the arguments
     */
    public static void main(String[] args) {

    List<String> str = new ArrayList<>();
    str.add("DEMO");
    str.add("DEMO2");
    str.add("DEMO3");

    /* Consumer is use for iterate over the List */
    Consumer<String> consumer = new Consumer<String>() {
        @Override
        public void accept(String t) {

        /* Print list element on consile */
        System.out.println(t);
        }
    };

    str.forEach(consumer);

    }

}

Supplier

package com.java.java8;

import java.util.function.Supplier;

/**
 * The Class SupplierDemo.
 *
 * @author Ankit Sood Apr 20, 2017
 */
public class SupplierDemo {

    /**
     * The main method.
     *
     * @param args
     *            the arguments
     */
    public static void main(String[] args) {
    getValue(() -> "Output1");
    getValue(() -> "OutPut2");
    }

    /**
     * Gets the value.
     *
     * @param supplier
     *            the supplier
     * @return the value
     */
    public static void getValue(Supplier<?> supplier) {
    System.out.println(supplier.get());
    }

}

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接