【问题标题】:When we should use Supplier in Java 8?我们什么时候应该在 Java 8 中使用供应商?
【发布时间】:2017-03-07 18:35:20
【问题描述】:

这段代码有什么区别?

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

【问题讨论】:

  • s2 将在分配s2 的时间点为您提供“现在”。当您致电 get() 时,s1.get() 将给您“现在”。如果您在某个地方传递了Supplier 以在需要时使用,那可能就不一样了。那么如果你稍后再拨打s1.get(),你将再次获得不同的时间。
  • @khelwood,我没明白,更新了问题,请检查。
  • 当您需要将获取 多个值 的能力传递给 另一个方法时使用它。 这几乎是 100% 的有效用例。

标签: java java-8 functional-interface


【解决方案1】:

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

如果您需要多次使用某个方法,但它的语法冗长。

假设您有一个名为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&lt;Integer&gt;。它与性能或方法的冗长无关。存在语义差异。
【解决方案2】:

您混淆了功能接口和方法引用。 Supplier 只是一个接口,类似于 Callable,从 Java 5 开始你就应该知道它,唯一的区别是 Callable.call 允许抛出已检查的 Exceptions,这与 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...

【讨论】:

    【解决方案3】:

    我将介绍一个我们应该使用Supplier&lt;LocalDate&gt; 而不是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&lt;LocalDate&gt;来解决这个问题:

    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());
    }
    

    【讨论】:

    • 所以要点是延迟执行
    【解决方案4】:

    我确信已经提供的答案已经回答了您的问题。这是我对供应商的理解。

    它为我提供了默认的 Java 定义接口,充当任何 lambda 方法 ref 目标返回时间的包装器。简而言之,只要您编写了一个没有参数并返回一个对象的方法,就有资格被供应商接口包装。没有它,在预泛型中,您将捕获返回作为对象类,然后进行转换,在泛型中,您将定义自己的 T(ype) 来保存返回值。它只是提供了一个统一的返回类型泛型持有者,您可以在其上调用 get() 从上面提到的无参数方法中提取返回的对象。

    【讨论】:

      【解决方案5】:

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

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

      Mosaic create(Supplier&lt;? extends Tile&gt; tileFactory) { ... }

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

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

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

      【讨论】:

        【解决方案6】:

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

        没有供应商

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

        在调用 getLocalValue 之前会调用 getFromInternet,但只有在本地值不存在时才会使用 getFromInternet() 的值。

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

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

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

        【讨论】:

          【解决方案7】:

          这个例子说明了如何使用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,而是它是如何使用的。

          【讨论】:

            【解决方案8】:

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

            不,这并不是为了提高性能。 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
            

            【讨论】:

            • 您的示例非常简洁,谢谢,我正在尝试这些功能。他们的好处仍然让我望而却步,我知道他们在那里,你只是帮助展示了一些光明。非常感谢。
            猜你喜欢
            • 2021-09-07
            • 2011-04-12
            • 1970-01-01
            • 1970-01-01
            • 2011-07-04
            • 2017-09-13
            • 2021-12-29
            • 2011-07-17
            • 2022-01-26
            相关资源
            最近更新 更多