Java 8 什么时候应该使用 Supplier?
When we should use Supplier in Java 8?
这段代码有什么区别?
Supplier<LocalDate> s1 = LocalDate::now;
LocalDate s2 = LocalDate.now();
System.out.println(s1.get()); //2016-10-25
System.out.println(s2); //2016-10-25
我在Java8开始学习函数式接口,不明白Supplier的好处。何时以及如何使用它们。供应商是否提高了性能或抽象级别的好处?
感谢您的回答!而且这不是重复的问题,因为我使用搜索并没有找到我需要的东西。
更新 1:
你是说这个吗?
Supplier<Long> s1 = System::currentTimeMillis;
Long s2 = System.currentTimeMillis();
System.out.println(s1.get()); //1477411877817
System.out.println(s2); //1477411877817
try {
Thread.sleep(3000l);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(s1.get()); //1477411880817 - different
System.out.println(s2); //1477411877817
它绝对不会提高性能。您的问题与这个问题类似:我们为什么要使用变量?我们可以简单地在每次需要时重新计算所有内容。对吗?
如果您需要多次使用某个方法,但它的语法很冗长。
假设您有一个名为 MyAmazingClass
的 class,并且其中有一个名为 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...
您混淆了功能接口和方法引用。 Supplier
只是一个接口,类似于 Callable
,你应该从 Java 5 开始就知道了,唯一的区别是 Callable.call
允许抛出 checked Exception
s,不像 Supplier.get
。所以这些接口会有相似的用例。
现在,这些接口也恰好是功能接口,这意味着它们可以作为方法引用来实现,指向一个现有的方法,该方法将在该接口时被调用方法被调用。
所以在 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...
我将介绍一个我们应该使用 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());
}
}
这在生产中运行良好。但是单元测试要么必须将系统的日期设置为已知值,要么每年更新一次以期望返回的年龄增加一个,这两种解决方案都是非常棒的。
更好的解决方案是在已知日期注入单元测试,同时仍允许生产代码使用 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
我们的单元测试可以像这样注入 "now" 日期:
@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());
}
我相信您的查询已经被提供的答案所回答。这是我对供应商的理解。
它给我默认 Java 定义的接口,作为任何 lambda 方法引用目标 return 时间的包装器。简而言之,只要您编写了一个没有参数且 return 是一个对象的方法,就有资格被供应商接口包装。否则,在预泛型中,您将捕获 return 作为对象 class,然后进行转换,而在泛型中,您将定义自己的 T(ype) 来保存 return 值。它只是提供了一个统一的 return 类型的通用持有者,您可以在其上调用 get() 以从上面提到的无参数方法中提取 returned 对象。
Effective Java by Joshua Bloch 的第 5 项中的解释为我解决了这个问题:
The Supplier interface, introduced in Java 8, is perfect for representing factories. Methods that take a Supplier on input should typically constrain the factory’s type parameter using a bounded wildcard type (Item 31) to allow the client to pass in a factory that creates any subtype of a specified type. For example, here is a method that makes a mosaic using a client-provided factory to produce each tile:
Mosaic create(Supplier<? extends Tile> tileFactory) { ... }
您可能希望将资源工厂发送到一个对象,该对象将使用资源工厂制作对象。现在,假设您要发送不同的资源工厂,每个资源工厂在继承树的不同位置生成对象,但是接收资源工厂的对象必须能够在任何情况下接收它们。
然后你可以实现一个供应商,它可以接受 return 对象 extending/implementing 使用有界通配符的某个 class/interface 的方法,并将供应商用作资源工厂。
阅读本书的第 5 项可能有助于完全理解用法。
我对答案不满意,补充一下我的看法:
当你想延迟执行时,Supplier 很有用。
没有供应商
config.getLocalValue(getFromInternet() /*value if absent*/);
在调用 getLocalValue 之前将调用 getFromInternet,但仅当本地值不存在时才会使用 getFromInternet() 的值。
现在,如果 config.getLocalValue
可以接受供应商,我们可以延迟执行,如果存在本地值,我们将不会执行。
config.getLocalValue(() -> getFromInternet())
差异
供应商使之成为可能:execute only when and if needed
此示例说明如何使用 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;
}
在这个方法中,Supplier使用了一种只有对象真的是null的方式来获取String。因此,它可以提高操作的性能但不是Supplier
而是它如何被使用。
Does the Supplier improve performance or maybe the benefits on
abstraction level?
不,这并不是为了提高性能。 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
这段代码有什么区别?
Supplier<LocalDate> s1 = LocalDate::now;
LocalDate s2 = LocalDate.now();
System.out.println(s1.get()); //2016-10-25
System.out.println(s2); //2016-10-25
我在Java8开始学习函数式接口,不明白Supplier的好处。何时以及如何使用它们。供应商是否提高了性能或抽象级别的好处?
感谢您的回答!而且这不是重复的问题,因为我使用搜索并没有找到我需要的东西。
更新 1: 你是说这个吗?
Supplier<Long> s1 = System::currentTimeMillis;
Long s2 = System.currentTimeMillis();
System.out.println(s1.get()); //1477411877817
System.out.println(s2); //1477411877817
try {
Thread.sleep(3000l);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(s1.get()); //1477411880817 - different
System.out.println(s2); //1477411877817
它绝对不会提高性能。您的问题与这个问题类似:我们为什么要使用变量?我们可以简单地在每次需要时重新计算所有内容。对吗?
如果您需要多次使用某个方法,但它的语法很冗长。
假设您有一个名为 MyAmazingClass
的 class,并且其中有一个名为 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...
您混淆了功能接口和方法引用。 Supplier
只是一个接口,类似于 Callable
,你应该从 Java 5 开始就知道了,唯一的区别是 Callable.call
允许抛出 checked Exception
s,不像 Supplier.get
。所以这些接口会有相似的用例。
现在,这些接口也恰好是功能接口,这意味着它们可以作为方法引用来实现,指向一个现有的方法,该方法将在该接口时被调用方法被调用。
所以在 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...
我将介绍一个我们应该使用 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());
}
}
这在生产中运行良好。但是单元测试要么必须将系统的日期设置为已知值,要么每年更新一次以期望返回的年龄增加一个,这两种解决方案都是非常棒的。
更好的解决方案是在已知日期注入单元测试,同时仍允许生产代码使用 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
我们的单元测试可以像这样注入 "now" 日期:
@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());
}
我相信您的查询已经被提供的答案所回答。这是我对供应商的理解。
它给我默认 Java 定义的接口,作为任何 lambda 方法引用目标 return 时间的包装器。简而言之,只要您编写了一个没有参数且 return 是一个对象的方法,就有资格被供应商接口包装。否则,在预泛型中,您将捕获 return 作为对象 class,然后进行转换,而在泛型中,您将定义自己的 T(ype) 来保存 return 值。它只是提供了一个统一的 return 类型的通用持有者,您可以在其上调用 get() 以从上面提到的无参数方法中提取 returned 对象。
Effective Java by Joshua Bloch 的第 5 项中的解释为我解决了这个问题:
The Supplier interface, introduced in Java 8, is perfect for representing factories. Methods that take a Supplier on input should typically constrain the factory’s type parameter using a bounded wildcard type (Item 31) to allow the client to pass in a factory that creates any subtype of a specified type. For example, here is a method that makes a mosaic using a client-provided factory to produce each tile:
Mosaic create(Supplier<? extends Tile> tileFactory) { ... }
您可能希望将资源工厂发送到一个对象,该对象将使用资源工厂制作对象。现在,假设您要发送不同的资源工厂,每个资源工厂在继承树的不同位置生成对象,但是接收资源工厂的对象必须能够在任何情况下接收它们。
然后你可以实现一个供应商,它可以接受 return 对象 extending/implementing 使用有界通配符的某个 class/interface 的方法,并将供应商用作资源工厂。
阅读本书的第 5 项可能有助于完全理解用法。
我对答案不满意,补充一下我的看法: 当你想延迟执行时,Supplier 很有用。
没有供应商
config.getLocalValue(getFromInternet() /*value if absent*/);
在调用 getLocalValue 之前将调用 getFromInternet,但仅当本地值不存在时才会使用 getFromInternet() 的值。
现在,如果 config.getLocalValue
可以接受供应商,我们可以延迟执行,如果存在本地值,我们将不会执行。
config.getLocalValue(() -> getFromInternet())
差异
供应商使之成为可能:execute only when and if needed
此示例说明如何使用 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;
}
在这个方法中,Supplier使用了一种只有对象真的是null的方式来获取String。因此,它可以提高操作的性能但不是Supplier
而是它如何被使用。
Does the Supplier improve performance or maybe the benefits on abstraction level?
不,这并不是为了提高性能。 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