我在许多网站上读到过,Optional应该只用作返回类型,而不能用于方法参数中。我在努力寻找一个合乎逻辑的原因。例如,我有一个逻辑,它有两个可选参数。因此,我认为这样写我的方法签名是有意义的(解决方案1):

public int calculateSomething(Optional<String> p1, Optional<BigDecimal> p2) {
    // my logic
}

许多网页指定Optional不应该用作方法参数。考虑到这一点,我可以使用下面的方法签名,并添加一个明确的Javadoc注释来指定参数可能为null,希望未来的维护者能够读取Javadoc,因此在使用参数之前总是执行null检查(解决方案2):

public int calculateSomething(String p1, BigDecimal p2) {
    // my logic
}

或者,我可以用四个公共方法替换我的方法,以提供更好的接口,并使p1和p2是可选的(解决方案3):

public int calculateSomething() {
    calculateSomething(null, null);
}

public int calculateSomething(String p1) {
    calculateSomething(p1, null);
}

public int calculateSomething(BigDecimal p2) {
    calculateSomething(null, p2);
}

public int calculateSomething(String p1, BigDecimal p2) {
    // my logic
}

现在,我尝试编写为每种方法调用这段逻辑的类的代码。我首先从另一个返回Optionals的对象检索两个输入参数,然后调用calculatessomething。因此,如果使用解决方案1,调用代码将看起来像这样:

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result = myObject.calculateSomething(p1, p2);

如果使用解决方案2,调用代码看起来像这样:

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result = myObject.calculateSomething(p1.orElse(null), p2.orElse(null));

如果应用解决方案3,我可以使用上面的代码,也可以使用下面的代码(但它的代码明显更多):

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result;
if (p1.isPresent()) {
    if (p2.isPresent()) {
        result = myObject.calculateSomething(p1, p2);
    } else {
        result = myObject.calculateSomething(p1);
    }
} else {
    if (p2.isPresent()) {
        result = myObject.calculateSomething(p2);
    } else {
        result = myObject.calculateSomething();
    }
}

所以我的问题是:为什么使用可选项作为方法参数被认为是不好的做法(参见解决方案1)?对我来说,这看起来是最易读的解决方案,并且对于未来的维护者来说,参数可以是空的/null是最明显的。(我知道Optional的设计者只打算将其用作返回类型,但我找不到在这种情况下不使用它的任何逻辑理由)。


当前回答

起初,我也倾向于将optional作为参数传递,但如果从API-Designer透视图切换到API-User透视图,就会看到缺点。

对于你的例子,每个参数都是可选的,我建议将计算方法更改为一个自己的类,如下所示:

Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();

MyCalculator mc = new MyCalculator();
p1.map(mc::setP1);
p2.map(mc::setP2);
int result = mc.calculate();

其他回答

带有Optional的模式是为了避免返回null。将null传递给方法仍然是完全可能的。

虽然这些注释还不是正式的,但您可以使用JSR-308样式注释来指示是否接受函数中的空值。请注意,您必须有正确的工具来实际识别它,它将提供更多的静态检查而不是可执行的运行时策略,但它将有所帮助。

public int calculateSomething(@NotNull final String p1, @NotNull final String p2) {}

接受Optional作为参数会在调用者级别引起不必要的包装。

例如:

public int calculateSomething(Optional<String> p1, Optional<BigDecimal> p2 {}

假设您有两个非空字符串(即。从其他方法返回):

String p1 = "p1"; 
String p2 = "p2";

即使您知道它们不是空的,也必须将它们包装在Optional中。

当你必须与其他“可映射”结构组合时,情况会变得更糟。要么:

Either<Error, String> value = compute().right().map((s) -> calculateSomething(
< here you have to wrap the parameter in a Optional even if you know it's a 
  string >));

ref:

方法不应该期望Option作为参数,这几乎总是一个 指示从调用方到的控制流泄漏的代码气味 对于被呼叫者,应由呼叫者负责检查 期权内容

引用https://github.com/teamdigitale/digital-citizenship-functions/pull/148 # discussion_r170862749

首先,如果你使用方法3,你可以用下面的代码替换最后14行代码:

int result = myobject . calculatessomething (p1.orElse(null), p2.orElse(null));

您编写的四个变体是方便的方法。你应该只在更方便的时候使用它们。这也是最好的方法。这样,API就非常清楚哪些成员是必要的,哪些不是。如果你不想写四个方法,你可以通过参数的命名来澄清:

(String p1OrNull, BigDecimal p2OrNull)

这样,就很清楚允许空值。

p1.orElse(null)的使用说明了我们的代码在使用Optional时是多么冗长,这也是我避免使用它的部分原因。Optional是为函数式编程而编写的。流需要它。你的方法可能永远不应该返回Optional,除非在函数式编程中有必要使用它们。有一些方法,如option . flatmap()方法,需要对返回Optional的函数的引用。这是它的签名:

public <U>可选<U> flatMap(Function<?超级T, ?扩展了可选的< ?扩展U>>映射器)

这通常是编写返回Optional的方法的唯一理由。但即使在那里,也可以避免。您可以将一个不返回Optional的getter传递给flatMap()这样的方法,方法是将它包装在另一个将函数转换为正确类型的方法中。包装器方法看起来像这样:

public static <T, U> Function<? super T, Optional<U>> optFun(Function<T, U> function) {
    return t -> Optional.ofNullable(function.apply(t));
}

假设你有一个这样的getter:

你不能像这样把它传递给flatMap:

opt.flatMap(Widget::getName) //不工作!

但你可以这样传递:

opt.flatMap(小部件::getter) //工作得很好!

在函数式编程之外,应该避免使用可选项。

Brian Goetz说得很好:

将Optional添加到Java的原因是:

return Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .findFirst()
    .getOrThrow(() -> new InternalError(...));

比这个更干净:

Method matching =
    Arrays.asList(enclosingInfo.getEnclosingClass().getDeclaredMethods())
    .stream()
    .filter(m -> Objects.equals(m.getName(), enclosingInfo.getName())
    .filter(m ->  Arrays.equals(m.getParameterTypes(), parameterClasses))
    .filter(m -> Objects.equals(m.getReturnType(), returnType))
    .getFirst();
if (matching == null)
  throw new InternalError("Enclosing method not found");
return matching;

在一些涉及protobufs或在配置对象中设置字段的用例中,使用Optional作为参数可能很有用。

public void setParameters(Optional<A> op1, Optional<B> op2) {
    ProtoRequest.Builder builder = ProtoRequest.newBuilder();
    op1.ifPresent(builder::setOp1);
    op2.ifPresent(builder::setOp2);
...
}

我认为在这种情况下,使用可选参数可能会很有用。接收原型请求的API将处理不同的字段。 如果函数没有对这些参数进行额外的计算,那么使用Optional可能会更简单。

public void setParameters(A op1, B op2) {
    ProtoRequest.Builder builder = ProtoRequest.newBuilder();
    if (op1 != null) {
        builder.setOp1(op1);
    }
    if (op2 != null) {
        builder.setOp2(op2);
    }
...
}

我认为这是因为您通常编写函数来操作数据,然后使用map和类似的函数将其提升为可选。这将添加默认的可选行为。 当然,在某些情况下,有必要编写自己的可用于Optional的辅助函数。