[英]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
我開始學習 Java 8 中的函數式接口,但不了解供應商的好處。 應該何時以及如何使用它們。 供應商是否提高了性能或抽象級別的好處?
感謝您的回答! 這不是重復的問題,因為我使用了搜索並沒有找到我需要的。
更新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
我將經歷一個場景,我們應該使用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
我們的單元測試可以像這樣注入“現在”日期:
@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());
}
它絕對不會提高性能。 您的問題與此類似:為什么我們使用變量? 我們可以簡單地在每次需要時重新計算所有內容。 對嗎?
如果你需要多次使用一個方法,但它有一個冗長的語法。
假設您有一個名為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...
您混淆了函數式接口和方法引用。 Supplier
只是一個接口,類似於Callable
,從 Java 5 開始你就應該知道了,唯一的區別是Callable.call
允許拋出已檢查的Exception
,與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...
我將添加我的觀點,因為我對答案不滿意:當您想延遲執行時,供應商很有用。
無供應商
config.getLocalValue(getFromInternet() /*value if absent*/);
在調用 getLocalValue 之前,將調用 getFromInternet,但僅當本地值不存在時才會使用 getFromInternet() 的值。
現在,如果config.getLocalValue
可以接受供應商,我們可以延遲此執行,而且如果存在本地值,我們將不會執行。
config.getLocalValue(() -> getFromInternet())
差異供應商使之成為可能: execute only when and if needed
供應商是否提高了性能或抽象級別的好處?
不,這不是為了提高性能。 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
我相信您的查詢已經被提供的答案回答了。 這是我對供應商的理解。
它為我提供了默認的 Java 定義的接口,作為任何 lambda 方法引用目標返回時間的包裝器。 簡而言之,只要您編寫了沒有 args 並返回 Object 的方法,就有資格被供應商接口包裝。 沒有它,在預泛型中,您將返回作為對象類捕獲,然后進行轉換,在泛型中您將定義自己的 T(ype) 來保存返回值。 它只是提供了一個統一的返回類型泛型持有者,您可以在該持有者上調用 get() 從上面提到的 no arg 方法中提取返回的對象。
Joshua Bloch在Effective Java第 5 條中的解釋為我澄清了這一點:
在 Java 8 中引入的供應商接口非常適合表示工廠。 在輸入上接受供應商的方法通常應該使用有界通配符類型(條目 31)來約束工廠的類型參數,以允許客戶端傳入創建指定類型的任何子類型的工廠。 例如,下面是一種使用客戶提供的工廠生產每個瓷磚來制作馬賽克的方法:
Mosaic create(Supplier<? extends Tile> tileFactory) { ... }
您可能希望將資源工廠發送到將使用資源工廠創建對象的對象。 現在,假設您要發送不同的資源工廠,每個資源工廠在繼承樹的不同位置生成對象,但接收資源工廠的對象在任何情況下都必須能夠接收它們。
然后,您可以實現一個供應商,該供應商可以接受返回使用有界通配符擴展/實現某個類/接口的對象的方法,並將供應商用作資源工廠。
閱讀本書的第 5 項可能有助於完全理解用法。
這個例子說明了如何使用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
而是它被如何使用。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.