什么时候应该在Java 8中使用Supplier?

时间:2016-10-25 15:59:05

标签: java java-8 functional-interface

此代码有什么区别?

    onItemClickHandler = (event) => {
        console.log("test");
        console.log(event.target);
    }

我开始学习Java 8中的功能接口,而不了解供应商的好处。究竟何时以及如何使用它们。供应商是否提高了绩效或者提取抽象水平的好处?

感谢您的回答!这不是重复的问题,因为我使用搜索并没有找到我需要的东西。

更新1: 你的意思是这个?

Supplier<LocalDate> s1 = LocalDate::now;
LocalDate s2 = LocalDate.now();

System.out.println(s1.get()); //2016-10-25
System.out.println(s2); //2016-10-25

8 个答案:

答案 0 :(得分:9)

它肯定不会改善性能。您的问题与此类似:我们为什么要使用变量?我们可以简单地在每次需要时重新计算所有内容。正确?

如果你需要多次使用一种方法,但它有一个冗长的语法。

假设您有一个名为MyAmazingClass的类,并且您在其中有一个名为MyEvenBetterMethod的方法(这是静态的),您需要在其中调用15次代码中的15个不同位置。当然,你可以做点像......

int myVar = MyAmazingClass.MyEvenBetterMethod();
// ...
int myOtherVar = MyAmazingClass.MyEvenBetterMethod();
// And so on...

...但你也可以

Supplier<MyAmazingClass> shorter = MyAmazingClass::MyEvenBetterMethod;

int myVar = shorter.get();
// ...
int myOtherVar = shorter.get();
// And so on...

答案 1 :(得分:9)

我将尝试使用Supplier<LocalDate>代替LocalDate

直接调用LocalDate.now()等静态方法的代码很难进行单元测试。考虑一种情况,我们希望对计算一个人年龄的方法getAge()进行单元测试:

class Person {
    final String name;
    private final LocalDate dateOfBirth;

    Person(String name, LocalDate dateOfBirth) {
        this.name = name;
        this.dateOfBirth = dateOfBirth;
    }

    long getAge() {
        return ChronoUnit.YEARS.between(dateOfBirth, LocalDate.now());
    }
}

这在生产中很好用。但是单元测试要么必须将系统的日期设置为已知值,要么每年更新以期望返回的年龄增加1,两者都是非常令人难以置信的解决方案。

更好的解决方案是单元测试在已知日期注入,同时仍允许生产代码使用LocalDate.now()。也许是这样的:

class Person {
    final String name;
    private final LocalDate dateOfBirth;
    private final LocalDate currentDate;

    // Used by regular production code
    Person(String name, LocalDate dateOfBirth) {
        this(name, dateOfBirth, LocalDate.now());
    }

    // Visible for test
    Person(String name, LocalDate dateOfBirth, LocalDate currentDate) {
        this.name = name;
        this.dateOfBirth = dateOfBirth;
        this.currentDate = currentDate;
    }

    long getAge() {
        return ChronoUnit.YEARS.between(dateOfBirth, currentDate);
    }

}

考虑自创建对象以来过去的人生日。通过此实现,getAge()将基于何时创建Person对象而不是当前日期。我们可以使用Supplier<LocalDate>

来解决这个问题
class Person {
    final String name;
    private final LocalDate dateOfBirth;
    private final Supplier<LocalDate> currentDate;

    // Used by regular production code
    Person(String name, LocalDate dateOfBirth) {
        this(name, dateOfBirth, ()-> LocalDate.now());
    }

    // Visible for test
    Person(String name, LocalDate dateOfBirth, Supplier<LocalDate> currentDate) {
        this.name = name;
        this.dateOfBirth = dateOfBirth;
        this.currentDate = currentDate;
    }

    long getAge() {
        return ChronoUnit.YEARS.between(dateOfBirth, currentDate.get());
    }

    public static void main(String... args) throws InterruptedException {
        // current date 2016-02-11
        Person person = new Person("John Doe", LocalDate.parse("2010-02-12"));
        printAge(person);
        TimeUnit.DAYS.sleep(1);
        printAge(person);
    }

    private static void printAge(Person person) {
        System.out.println(person.name + " is " + person.getAge());
    }
}

输出正确为:

John Doe is 5
John Doe is 6

我们的单元测试可以注入&#34;现在&#34;这样的日期:

@Test
void testGetAge() {
    Supplier<LocalDate> injectedNow = ()-> LocalDate.parse("2016-12-01");
    Person person = new Person("John Doe", LocalDate.parse("2004-12-01"), injectedNow);
    assertEquals(12, person.getAge());
}

答案 2 :(得分:8)

您正在混淆功能接口和方法引用。 Supplier只是一个类似于Callable的接口,自Java 5以来您应该知道,唯一的区别是允许Callable.call抛出已检查的Exception,与Supplier.get不同{1}}。因此这些接口将具有类似的用例。

现在,这些接口也恰好是功能接口,这意味着它们可以作为方法引用实现,指向在调用接口方法时将调用的现有方法。 / p>

所以在Java 8之前,你必须写

Future<Double> f=executorService.submit(new Callable<Double>() {
    public Double call() throws Exception {
        return calculatePI();
    }
});
/* do some other work */
Double result=f.get();

现在,你可以写

Future<Double> f=executorService.submit(() -> calculatePI());
/* do some other work */
Double result=f.get();

Future<Double> f=executorService.submit(MyClass::calculatePI);
/* do some other work */
Double result=f.get();

何时使用 Callable的问题根本没有改变。

同样,何时使用Supplier的问题不取决于您如何实现它,而是取决于您使用的API,即

CompletableFuture<Double> f=CompletableFuture.supplyAsync(MyClass::calculatePI);
/* do some other work */
Double result=f.join();// unlike Future.get, no checked exception to handle...

答案 3 :(得分:4)

<块引用>

供应商是否提高了绩效或可能带来的好处 抽象层?

不,这不是为了提高性能。 Supplier 用于延迟执行,即您指定的功能(代码)将在使用时运行。以下示例演示了差异:

import java.time.LocalDateTime;
import java.util.function.Supplier;

public class Main {
    public static void main(String[] args) throws InterruptedException {
        // Create a reference to the current date-time object when the following line is
        // executed
        LocalDateTime ldt = LocalDateTime.now();
        System.out.println(ldt);// Line-1

        // Create a reference to a functionality that will get the current date-time
        // whenever this functionality will be used
        Supplier<LocalDateTime> dateSupplier = LocalDateTime::now;

        // Sleep for 5 seconds
        Thread.sleep(5000);

        System.out.println(ldt);// Will display the same value as Line-1
        System.out.println(dateSupplier.get());// Will display the current date-time when this line will be executed

        // Sleep again for 5 seconds
        Thread.sleep(5000);

        System.out.println(ldt);// Will display the same value as Line-1
        System.out.println(dateSupplier.get());// Will display the current date-time when this line will be executed
    }
}

示例运行的输出:

2021-04-11T00:04:06.205105
2021-04-11T00:04:06.205105
2021-04-11T00:04:11.211031
2021-04-11T00:04:06.205105
2021-04-11T00:04:16.211659

另一个有用的案例:

import java.util.List;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        List<String> list = List.of("Hello", "B2C", "World", "Stack Overflow", "is", "a", "gr8", "platform");

        // A simple Stream for demo; you can think of a complex Stream with more
        // intermediate operations
        Stream<String> stream = list.stream()
                                    .filter(s -> s.length() <= 5)
                                    .map(s -> s.substring(1));

        System.out.println(stream.anyMatch(s -> Character.isLetter(s.charAt(0))));
        System.out.println(stream.anyMatch(s -> Character.isDigit(s.charAt(0))));
    }
}

输出:

true
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
    at java.base/java.util.stream.ReferencePipeline.anyMatch(ReferencePipeline.java:528)
    at Main.main(Main.java:13)

输出是不言自明的。一个丑陋的解决方法可能是每次都创建一个新的 Stream,如下所示:

import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> list = List.of("Hello", "B2C", "World", "Stack Overflow", "is", "a", "gr8", "platform");

        System.out.println(list.stream().filter(s -> s.length() <= 5).map(s -> s.substring(1))
                .anyMatch(s -> Character.isLetter(s.charAt(0))));
        
        System.out.println(list.stream().filter(s -> s.length() <= 5).map(s -> s.substring(1))
                .anyMatch(s -> Character.isDigit(s.charAt(0))));
    }
}

现在,看看使用 Supplier 可以多么干净地做到这一点:

import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        List<String> list = List.of("Hello", "B2C", "World", "Stack Overflow", "is", "a", "gr8", "platform");

        Supplier<Stream<String>> streamSupplier = () -> list.stream()
                                                            .filter(s -> s.length() <= 5)
                                                            .map(s -> s.substring(1));

        System.out.println(streamSupplier.get().anyMatch(s -> Character.isLetter(s.charAt(0))));

        System.out.println(streamSupplier.get().anyMatch(s -> Character.isDigit(s.charAt(0))));
    }
}

输出:

true
true

答案 4 :(得分:3)

我将添加我的观点,因为我对答案不满意: 当您想延迟执行时,供应商会很有用。

没有供应商

config.getLocalValue(getFromInternet() /*value if absent*/);

在调用getLocalValue之前,将调用getFromInternet,但是只有在不存在本地值的情况下,才会使用getFromInternet()的值。

现在,如果config.getLocalValue可以接受供应商,我们可以延迟执行,加上如果存在本地值,我们将不执行。

config.getLocalValue(() -> getFromInternet())

差异 供应商使之成为可能:execute only when and if needed

答案 5 :(得分:1)

我确定您的查询已由已提供的答案回答。这是我对供应商的理解。

它为我提供了默认的Java定义接口,该接口充当任何lambda方法ref目标返回时间的包装器。简而言之,在任何您编写了没有args并返回Object的方法的任何地方,都可以由Supplier接口包装。否则,在常规类中,您将捕获返回值作为对象类,然后进行强制转换,在常规类中,您将定义自己的T(ype)来保存返回值。它只是提供了一个统一的返回类型通用持有人,您可以在其上调用get()从上述no arg方法中提取返回的对象。

答案 6 :(得分:0)

Effective Java by Joshua Bloch第5项中的解释为我清除了这一点:

Java 8中引入的Supplier接口非常适合代表工厂。输入供应商的方法通常应使用有界通配符类型(第31项)来约束工厂的类型参数,以允许客户端传入创建指定类型的任何子类型的工厂。例如,以下是一种使用客户提供的工厂制作马赛克以生成每个图块的方法:

Mosaic create(Supplier<? extends Tile> tileFactory) { ... }

您可能希望将资源工厂发送给对象,该对象将使用资源工厂来创建对象。现在,假设您要发送不同的资源工厂,每个资源工厂在继承树的不同位置生成对象,但是接收资源工厂的对象无论如何都必须能够接收它们。

然后,您可以实现一个供应商,该供应商可以接受使用有界通配符返回扩展/实现特定类/接口的对象的方法,并将该供应商用作资源工厂。

阅读本书的第5项可能有助于完全理解用法。

答案 7 :(得分:0)

此示例说明如何使用Supplier来提高性能,但是Supplier本身并不能提高性能。

/**
 * Checks that the specified object reference is not {@code null} and
 * throws a customized {@link NullPointerException} if it is.
 *
 * <p>Unlike the method {@link #requireNonNull(Object, String)},
 * this method allows creation of the message to be deferred until
 * after the null check is made. While this may confer a
 * performance advantage in the non-null case, when deciding to
 * call this method care should be taken that the costs of
 * creating the message supplier are less than the cost of just
 * creating the string message directly.
 *
 * @param obj     the object reference to check for nullity
 * @param messageSupplier supplier of the detail message to be
 * used in the event that a {@code NullPointerException} is thrown
 * @param <T> the type of the reference
 * @return {@code obj} if not {@code null}
 * @throws NullPointerException if {@code obj} is {@code null}
 * @since 1.8
 */
public static <T> T requireNonNull(T obj, Supplier<String> messageSupplier) {
    if (obj == null)
        throw new NullPointerException(messageSupplier == null ?
                                       null : messageSupplier.get());
    return obj;
}

在此方法中,供应商使用的方式是,只有对象实际上为空,然后获取String。因此,它可以提高操作的性能,但不是Supplier而是操作方法。