当一个人只能调用一个方法时,为什么要使用 `java.util.function.supplier`?

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

我在 https://dzone.com/articles/supplier-interface 看到了一些使用供应商界面的例子。

我的问题是,如果在上面的例子中我可以做一些简单的事情:

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

为什么要使用供应商接口,如果它所做的只是调用一个方法,而不接收任何参数。

假设您有一些参数存储在数据库中,您希望在整个应用程序中保持不变

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

该值将被初始化一次并且在重新部署之前不会更改。也就是说,如果您改为使用供应商:

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

当您在某处需要该值时,您将调用 SYSTEM_PARAMETER_SUPPLIER.get(),它将在需要时检索数据库中的参数 - 这样,如果您更改数据库中的参数,则无需重新部署。

如您所见,供应商很懒惰。当您要求他们工作时(通过调用 .get()),他们会完成工作 - 如果您明智地处理它们,这可能会让您获得一些性能提升。有时你会调用一个方法,该方法期望一个变量 X 传入方法 retrieveX,然后由于某些条件不满足而最终在方法中不需要 X。在这种情况下,您将失去性能,因为您将执行检索 X 的代码,而检索 X 的供应商只会在调用 .get 时执行它,而您只会在满足条件。


免责声明:系统参数常量只是我想到的第一个例子,但考虑到它在每个 .get() 上查询数据库,你宁愿缓存参数并让缓存调用 .get()在特定的时间间隔。

我想 Optional 可能是一个完美的例子。考虑以下片段:

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

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

您收到的产品可能为空。为了确定 firstProduct java 必须在 orElse 方法中调用表达式,因此无论产品是否为空,您始终必须确定在产品为空的情况下将返回的值。

为了确定在产品不为空的情况下不必查询 secondProduct 数据库,因为您传递的供应商只有在产品为空时才会被调用。

另一个例子是当你的接受供应商的方法不是纯粹的(即它有副作用),并且副作用发生在调用lambda之前,并且lambda的行为受到副作用的影响。

例如,考虑这个例子:

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

这里,method() 不是纯粹的,因为它改变了 field 的值,稍后在 lambda 中使用(通过调用 getField() 方法)。由于 lambda 被就地调用(即,当 get() 被调用时),调用 getField() 将发生 设置字段之后。换句话说,method() 接受 Supplier<String> 而不是 String 以尝试让客户端安全地调用 getField() 方法。

当然,应尽可能避免副作用,这只是一个玩具示例,但它显示了可以使用供应商的潜在场所。

供应商增加了一层间接寻址。

考虑到 "All problems in computer science can be solved by another level of indirection",可能有一些问题可以通过使用供应商来解决。

但是,请注意推论“...除了间接层过多的问题。”

所以,如果没有问题需要解决,那么 Supplier 就太过分了,你应该坚持直接调用 new.

换句话说:不信任任何不以解释问题开始的 "pattern" 或 "best practice"(你的问题表明,你实际上不信任,所以继续问这种问题) .

您可以在基于地图的工厂中使用供应商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();
    }
}

在这里,如果您只使用 new Stack(),您将返回对同一对象的引用,而不是新对象。

我用它来避免不必要地创建额外的状态:

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

有些人会说这本身是不必要的,但后来它就成了一个哲学问题。

如果没有计算机就不会存在的问题,然后它就变成了现实世界的间接问题。

我承认供应商对我来说可能已经上瘾了,但在我看来,他们感觉像是所有编程公理和原则的自然外推和扩展。