简体   繁体   中英

Why would one use a `java.util.function.supplier`, when one can just call a method?

I saw some example of of using supplier interface at https://dzone.com/articles/supplier-interface .

My question is, if in the above example I could do something as easy as :

driveVehicle(new Vehicle());
driveVehicle(new Car());

Why would one want to use supplier interface, if all it does is call a method, without taking in any parameters.

I guess Optional might be perfect example. Consider the following snippet:

final Product firstProduct = Optional.ofNullable(product)
        .orElse(productDao.findProductById(id));

final Product secondProduct = Optional.ofNullable(product)
        .orElseGet(() -> productDao.findProductById(id));

You're getting a product that may be null. In order to determine firstProduct java will have to call expression in orElse method so no matter product is null or not you always have to determine value that will be returned in case product is null.

In order to determine secondProduct database doesn't have to be queried in case product is not null because you're passing a Supplier that will be called only if product is null.

Suppose you have parameters stored in database that you want to keep in constant all over your app

// Assume retrieveSystemParameter query database which allows to change parameters
public static String SYSTEM_PARAMETER = StaticUtilities.retrieveSystemParameter();

That value will be initialized once and won't change untill a redeployment. That being said, if instead you use a supplier :

public static Supplier<String> SYSTEM_PARAMETER_SUPPLIER = StaticUtilities::retrieveSystemParameter;

When you need the value somewhere you will call SYSTEM_PARAMETER_SUPPLIER.get() which will retrieve parameter in the database when needed - that way if you change a parameter in database, you won't have to redeploy.

As you can see, Suppliers are lazy. They do the work when you ask them to work (by calling .get()) - that may allow you some performance gain if you deal with them wisely. Sometimes you will call a method which expect a variable X passing in method retrieveX and then end up not needing X in the method because some conditions were not met. In that case you will lose performance as you will execute the code to retrieve X while a supplier that retrieve X would only execute it when calling .get and you would only do that call if the conditions were met.


Disclaimer : the system parameter constant is just the first example that came to my mind, but considering it query the database on each .get() you'd rather cache the parameter and have the cache call .get() at a specific interval.

Another example is when your method that accepts a supplier is not pure (ie, it has side effect), and the side effect happens before calling the lambda, and the behaviour of the lambda is affected by the side effect.

Consider, for instance, this example:

public class TestClass {

    private String field;

    public String getField() {
        return field;
    }

    public void method(Supplier<String> supplier) {
        field = "This is";
        System.out.println(supplier.get() + " a test");
    }

    public static void main(String[] args) {
        TestClass c = new TestClass();
        c.method(() -> c.getField()); 
    }
}

Here, method() is not pure, as it changes the value of field , which is used later in the lambda (through calling the getField() method). As the lambda is called in place (ie, when get() is called), calling getField() will happen after setting the field. In other words, method() accepts a Supplier<String> instead of a String in an attempt to let the clients safely call the getField() method.

Of course, having side effects should be avoided wherever possible, and this is just a toy example, but it shows a potential place where a supplier can be used.

Supplier adds one more level of indirection.

Given that "All problems in computer science can be solved by another level of indirection" , it's likely that there are some problems that can be solved by using a Supplier.

Beware, however, of the corollary "...except for the problem of too many layers of indirection."

So, if there's no problem to solve, then Supplier is overkill and you should stick to directly invoking new .

Put differently: mistrust any "pattern" or "best practice" that doesn't start by explaining a problem (your question shows, you actually do mistrust, so just keep on asking this kind of questions).

I use it to avoid the unnecessary creation of additional states:

private Supplier<Boolean> detach = () -> false;
private Supplier<Boolean> isAttached = () -> false;
private Supplier<Integer> index = () -> null;

private final Function<List<ObserverWrapper<X, Y>>, Boolean> attachFun = observers -> {
    isAttached = () -> observers.contains(this);
    detach = () -> observers.remove(this);
    index = () -> observers.indexOf(this);
    return observers.add(this);
};

public boolean attach(List<ObserverWrapper<X, Y>> observers) {
    return attachFun.apply(observers);
}

public boolean isAttached() {
    return isAttached.get();
}

public Integer observerIndex() {
    return index.get();
}

Which some would say is unnecessary in itself, but then it becomes a philosophical problem.

A problem which would not exist if computers didn't exist, and then it becomes a real world problem of indirection.

I may admit that suppliers for me may have become an addiction, but in my mind they feel like the natural extrapolation and extension of all the programming axioms and principles.

You could use a Supplier in a map based factory class

public class StackService {
    final static String INTEGERS = "Integers";
    final static String DOUBLES = "Doubles";
    final static String STRINGS = "Strings";

    final static Map<String, Supplier<Stack>> stackType;

    static {
        stackType = new HashMap<>();
        stackType.put(INTEGERS, Stack<Integer>::new);
        stackType.put(DOUBLES, Stack<Double>::new);
        stackType.put(STRINGS, Stack<String>::new);
    }

    public Stack<?> createStackOfType(String stackType) {
        return stackType.get(stackType).get();
    }
}

Here if you were to just use new Stack() you would be returning a reference to the same object rather than a new one.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM