我在许多网站上读到过,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的设计者只打算将其用作返回类型,但我找不到在这种情况下不使用它的任何逻辑理由)。


当前回答

不管Java 8,使用老派的方法重载技术带来清晰和灵活性,假设你有以下两个参数的方法

public void doSomething(arg1,arg2);

如果你想添加额外的可选参数,那么重载方法

public void doSomething(arg1,arg2,arg3) {
Result result = doSomething(arg1,arg2);
// do additional working
}

其他回答

这个建议是“对输入尽可能不具体,对输出尽可能具体”经验法则的一种变体。

通常,如果您有一个接受普通非空值的方法,您可以将其映射到Optional上,因此普通版本对于输入严格来说更加不特定。然而,有很多可能的原因可以解释为什么你需要一个Optional参数:

您希望您的函数与另一个返回Optional的API一起使用 如果给定值为空,函数应该返回一个空可选值以外的值 你认为Optional是如此棒,以至于任何使用你的API的人都应该被要求学习它;-)

在一些涉及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);
    }
...
}

起初,我也倾向于将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();

正如Brian Goetz所解释的那样,可选选项并不是为此目的而设计的。

你总是可以使用@Nullable来表示方法参数可以为空。使用可选方法并不能让你的方法逻辑写得更整齐。

接受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