When we should use Supplier in Java 8?

21,035

Solution 1

It definitely doesn't improve the performance. Your question is similar to this one: Why are we using variables? We could simply just recalculate everything every time we need it. Right?

If you need to use a method a lot of times, but it has a wordy syntax.

Let's assume you have a class named MyAmazingClass, and you have a method in it with the name MyEvenBetterMethod (which is static), and you need to call it 15 times at 15 different positions in your code. Of course, you can do something like...

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

...but you can also do

Supplier<MyAmazingClass> shorter = MyAmazingClass::MyEvenBetterMethod;

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

Solution 2

I'll go through a scenario where we should use Supplier<LocalDate> instead of LocalDate.

Code that directly makes calls to static methods like LocalDate.now() is very difficult to unit test. Consider a scenario where we want to unit test a method getAge() that calculates a person's age:

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

This works fine in production. But a unit test would either have to set the system's date to a known value or be updated every year to expect the returned age to be incremented by one, both pretty aweful solutions.

A better solution would be for the unit test to inject in a known date while still allowing the production code to use LocalDate.now(). Maybe something like this:

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

}

Consider a scenario where the person's birthday has passed since the object was created. With this implementation, getAge() will be based on when the Person object was created rather than the current date. We can solve this by using 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());
    }
}

The output would correctly be:

John Doe is 5
John Doe is 6

Our unit test can inject the "now" date like this:

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

Solution 3

Does the Supplier improve performance or maybe the benefits on abstraction level?

No, it's not meant to improve performance. The Supplier is used for a deferred execution i.e. you specify a functionality (code) that will run whenever used. The following example demonstrates the difference:

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
    }
}

Output of a sample run:

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

Another useful case:

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

Output:

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)

The output is self-explanatory. An ugly workaround could be creating a new Stream each time as shown below:

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

Now, see how cleanly you can do it using a 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))));
    }
}

Output:

true
true

Solution 4

You are confusing functional interfaces and method references. Supplier is just an interface, similar to Callable, which you should know since Java 5, the only difference being that Callable.call is allowed to throw checked Exceptions, unlike Supplier.get. So these interfaces will have similar use cases.

Now, these interface also happen to be functional interfaces, which implies that they can be implemented as a method reference, pointing to an existing method that will be invoked when the interface method is invoked.

So before Java 8, you had to write

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

and now, you can write

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

or

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

The question when to use Callable hasn’t changed at all.

Similarly, the question when to use Supplier is not dependent on how you implement it, but which API you use, i.e.

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

Solution 5

I will add my view as I am not satisfied by the answers: Supplier is useful when you want to delay the execution.

Without supplier

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

Before getLocalValue gets called getFromInternet will be called, but the value of getFromInternet() will be used only if local value is absent.

Now, if config.getLocalValue can accept supplier, we can delay this execution, plus we won't execute if local value is present.

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

Difference Supplier makes it possible: execute only when and if needed

Share:
21,035

Related videos on Youtube

badCoder
Author by

badCoder

Updated on April 12, 2021

Comments

  • badCoder
    badCoder about 3 years

    What difference between this code?

    Supplier<LocalDate> s1 = LocalDate::now;
    LocalDate s2 = LocalDate.now();
    
    System.out.println(s1.get()); //2016-10-25
    System.out.println(s2); //2016-10-25
    

    I start learning functional interfaces in Java 8 and don't understand the benefit of the Supplier. When and how, exactly, should use them. Does the Supplier improve performance or maybe the benefits on abstraction level?

    Thanks for your answers! And it isn't duplicate question because I used search and didn't find what I need.

    UPDATE 1: You mean this?

        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
    
    • khelwood
      khelwood over 7 years
      s2 will give you "now" at the point in time where s2 was assigned. s1.get() will give you "now" at the point when you call get(). That might not be the same if you have passed the Supplier somewhere to be used when required. Then if you call s1.get() again later, you will get a different time again.
    • badCoder
      badCoder over 7 years
      @khelwood, I didn't get it and updated question, please check.
    • Louis Wasserman
      Louis Wasserman over 7 years
      Use it when you need to pass the ability to get multiple values into another method. That's pretty much 100% of the valid use cases.
  • biziclop
    biziclop over 7 years
    Surely that'd be Supplier<Integer>. And it's got nothing to do with performance or how wordy a method is. There is a semantic difference.
  • Mushtaq Jameel
    Mushtaq Jameel almost 6 years
    So the gist is delayed execution
  • Tungata
    Tungata about 3 years
    Your Examples are very concise thanks, I was trying out these functions. Their Benefits are still eluding me I know they are there you just helped show some light. Much appreciated.
  • Rany Albeg Wein
    Rany Albeg Wein about 2 years
    That's a great example showing how by using a Supplier<T> we can defer then execution of some function of T, or use it on demand. Great one!