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


当前回答

查看JDK10中的JavaDoc, https://docs.oracle.com/javase/10/docs/api/java/util/Optional.html,添加了一个API说明:

API注意: Optional主要用于明确需要表示“没有结果”以及使用null可能导致错误的情况下作为方法返回类型使用。

其他回答

我知道这个问题更多的是意见而不是事实。但是我最近从一个。net开发人员转变为java开发人员,所以我最近才加入了可选的一方。此外,我更喜欢把它作为一个评论,但由于我的分数水平不允许我评论,我被迫把它作为一个答案。

我一直在做的,这是我的经验之谈。是使用可选项作为返回类型,并且仅使用可选项作为参数,如果我需要可选项的值,以及可选项是否在方法中有值。

如果我只关心值,我在调用方法之前检查isPresent,如果我在方法中有某种日志记录或不同的逻辑,这取决于值是否存在,那么我将愉快地传入Optional。

一个很好的例子是可选的参数会很好是JPA存储库。我喜欢做一些像findbynameand姓氏(可选,可选)。这样,如果Optional为空,则不执行WHERE param=y

这是因为我们对API用户和API开发人员有不同的要求。

开发人员负责提供精确的规范和正确的实现。因此,如果开发人员已经意识到一个参数是可选的,那么实现必须正确地处理它,无论它是null还是optional。API应该对用户尽可能简单,null是最简单的。

另一方面,结果从API开发人员传递给用户。尽管规范是完整和冗长的,但仍然有可能用户没有意识到它或只是懒得处理它。在这种情况下,Optional结果迫使用户编写一些额外的代码来处理可能为空的结果。

所以,如果你允许这个双关语,甲骨文发出了一个预言:

除函数返回值外,不应使用Optional。

我很喜欢到目前为止大多数的答案都与甲骨文的预言一致,在这个问题中提到的“许多网站”中,它在互联网上毫无疑问地重复着。这是非常典型的堆栈溢出:如果某件事据称应该是某种方式,你问为什么它应该是这样,几乎每个人都会给出原因;几乎没有人会质疑事实上是否应该如此。

所以,这里有一个不同的意见:

您可以使用Optional从代码库中完全消除null。

我曾经在一个100k行代码的项目中做到过。它工作。

If you decide to go along this path, then you will need to be thorough, so you will have a lot of work to do. The example mentioned in the accepted answer with Optional.ofNulable() should never occur, because if you are thorough, then you should never have anything returning null, and therefore no need for Optional.ofNullable(). In that 100k-lines-of-code project that I mentioned above, I have only used Optional.ofNullable() a couple of times when receiving results from external methods that I have no control over.

此外,如果您决定沿着这条路径走,您的解决方案将不是性能最好的解决方案,因为您将分配大量的可选选项。然而:

这只是运行时性能开销方面的缺点。 这并不是一个严重的劣势。 这是Java的问题,不是你的问题。

让我来解释一下最后一部分。

Java不像c#那样提供显式的引用类型可空性(从8.0版开始),所以在这方面它是劣势。(我说的是“在这方面”;在其他方面,Java更好;但现在这已经跑题了。)

对于引用类型的显式空性,唯一合适的替代方法是可选类型。

(它甚至可以说是稍微好一点,因为使用Optional你可以指明Optional -of- Optional,如果你必须,而使用显式的nullability你不能有ReferenceType??,或者至少你不能在c#中实现。)

可选的不需要增加开销,它只在Java中这样做。这是因为Java也不像c#和Scala那样支持真正的值类型。在这方面,Java远远不如这些语言。(再说一次,我说的是“在这方面”;在其他方面,Java更好;但现在这已经跑题了。)如果Java确实支持真值类型,那么Optional将被实现为单个机器单词,这意味着使用它的运行时开销将为零。

所以,问题归结起来就是:你想在你的代码中完美的清晰和类型安全,还是你更喜欢最大的性能?我相信对于高级语言(Java当然是其中之一),这个问题很久以前就解决了。

还有一种方法,你能做的是

// get your optionals first
Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();

// bind values to a function
Supplier<Integer> calculatedValueSupplier = () -> { // your logic here using both optional as state}

一旦你构建了一个函数(在本例中是供应商),你就可以将它作为任何其他变量传递,并能够使用它来调用

calculatedValueSupplier.apply();

这里的想法是,你是否有可选值将是你的函数的内部细节,而不是参数。将可选参数作为参数来考虑函数实际上是我发现的非常有用的技巧。

至于你的问题,你是否应该这样做,这是基于你的偏好,但正如其他人所说,它使你的API丑陋,至少可以说。