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


当前回答

这对我来说似乎有点傻,但我能想到的唯一原因是方法参数中的对象参数在某种程度上已经是可选的——它们可以为空。因此,强迫某人使用现有对象并将其包装为可选对象是毫无意义的。

也就是说,将可选的take/return方法链接在一起是一件合理的事情,例如monad。

其他回答

几乎没有理由不使用Optional作为参数。反对这一点的论点依赖于权威的论点(见Brian Goetz -他的论点是我们不能强制非空可选参数)或可选参数可能为空(本质上是相同的论点)。当然,Java中的任何引用都可以为空,我们需要鼓励规则由编译器执行,而不是由程序员的内存执行(这是有问题的,并且不能扩展)。

函数式编程语言鼓励可选参数。使用它的最佳方法之一是有多个可选参数,并使用liftM2来使用一个函数,假设参数不是空的并返回一个可选参数(参见http://www.functionaljava.org/javadoc/4.4/functionaljava/fj/data/Option.html#liftM2-fj.F-)。不幸的是,Java 8实现了一个非常有限的库,支持可选功能。

作为Java程序员,我们应该只使用null来与遗留库交互。

也许我会引起一堆反对票和负面评论,但是……我站不起来了。

免责声明:我在下面写的并不是对最初问题的回答,而是我对这个话题的想法。它的唯一来源是我的想法和经验(使用Java和其他语言)。

首先让我们看看,为什么会有人喜欢使用可选?

对我来说,原因很简单:与其他语言不同,java没有内置的功能来定义变量(或类型)为空或非空。所有“object”变量都是可空的,而所有基元类型都不是。为了简单起见,在进一步的讨论中不要忽略原始类型,因此我将简单地声明所有变量都是可空的。

为什么需要将变量声明为可空/非可空?我的理由是:显式总是比隐式好。此外,显式修饰(例如注释或类型)可以帮助静态分析器(或编译器)捕获一些与空指针相关的问题。

Many people argue in the comments above, that functions do not need to have nullable arguments. Instead overloads should be used. But such statement is only good in a school-book. In real life there are different situations. Consider class, which represents settings of some system, or personal data of some user, or in fact any composite data-structure, which contains lots of fields - many of those with repeated types, and some of the fields are mandatory while others are optional. In such cases inheritance/constructor overloads do not really help.

随机的例子:假设,我们需要收集关于人的数据。但有些人不想提供所有的数据。当然这是POD,基本上是type with value-semantics,所以我希望它或多或少是不可变的(没有setter)

class PersonalData {
    private final String name; // mandatory
    private final int age; // mandatory
    private final Address homeAddress; // optional
    private final PhoneNumber phoneNumber; // optional. Dedicated class to handle constraints
    private final BigDecimal income; // optional.
    // ... further fields

    // How many constructor- (or factory-) overloads do we need to handle all cases
    // without nullable arguments? If I am not mistaken, 8. And what if we have more optional
    // fields?

    // ...
}

因此,IMO上面的讨论表明,即使大多数情况下我们可以在没有可空参数的情况下生存,但有时它实际上是不可行的。

现在我们来看看问题:如果一些参数是可空的,而另一些不是,我们怎么知道是哪个?

方法1:所有参数都是可空的(根据java标准,除了基本类型)。所以我们检查了所有的。

结果:代码中充斥着检查,而这些检查大多是不需要的,因为正如我们上面所讨论的,几乎所有时候我们都可以使用可空变量,只有在极少数情况下才需要“可空变量”。

方法2:使用文档和/或注释来描述,哪些参数/字段是可空的,哪些不是。

结果:它实际上不起作用。人们懒得写和读文档。此外,最近的趋势是,我们应该避免编写文档,而倾向于使代码本身自描述。此外,所有关于修改代码和忘记修改文档的理由仍然有效。

Approach 3: @Nullable @NonNull etc... I personally find them to be nice. But there are certain disadvantages : (e.g. they are only respected by external tools, not the compiler), the worst of which is that they are not standard, which means, that 1. I would need to add external dependency to my project to benefit from them, and 2. The way they are treated by different systems are not uniform. As far as I know, they were voted out of official Java standard (and I don't know if there are any plans to try again).

方法4:可选<>。缺点已经在其他评论中提到,其中最糟糕的是(IMO)性能惩罚。它还添加了一些样板文件,尽管我个人认为,使用Optional.empty()和Optional.of()并不是那么糟糕。优点是显而易见的:

它是Java标准的一部分。 对于代码的读者(或API的用户)来说,这些参数可能是空的。此外,它还强制API的用户和方法的开发人员通过显式地包装/展开值来确认这一事实(当使用@Nullable等注释时,情况并非如此)。

所以在我看来,任何方法论都不存在非黑即白的问题,包括这个方法。我个人总结了以下准则和约定(仍然不是严格的规则):

在我自己的代码中,所有变量都必须为非空(但可能是Optional<>)。 如果我有一个方法与一个或两个可选参数,我尝试重新设计它使用重载,继承等。 如果我不能在合理的时间内找到解决方案,我就会开始考虑性能是否关键(即是否有数百万个对象需要处理)。通常情况并非如此。 如果不是,我使用Optional作为参数类型和/或字段类型。

但仍有一些灰色地带,在那里这些惯例并不奏效:

我们需要高性能(例如,处理大量数据,因此总执行时间非常长,或者吞吐量非常关键的情况)。在这种情况下,由Optional引入的性能惩罚可能真的是不需要的。 我们在代码的边界上,这些代码是我们自己写的,例如:我们从DB、Rest Endpoint、解析文件等读取。 或者我们只是使用一些外部库,它们不遵循我们的约定,所以,我们应该再次小心……

顺便说一下,后两种情况也可以作为可选字段/参数的需求来源。例如,当数据的结构不是我们自己开发的,而是由一些外部接口,db-schema等强加的……

最后,我认为,人们应该思考正在解决的问题,并尝试找到合适的工具。如果Optional<>是合适的,那么我认为没有理由不使用它。

Edit: Approach 5: I used this one recently, when I could not use Optional. The idea is simply to use naming convention for method arguments and class variables. I used "maybe"-prefix, so that if e.g. "url" argument is nullable, then it becomes maybeUrl. The advantage is that it slightly improves understandability of the intent (and does not have disadvantages of other approaches, like external dependencies or performance penalty). But there are also drawbacks, like: there is no tooling to support this convention (your IDE will not show you any warning, if you access "maybe"-variable without first checking it). Another problem is that it only helps, when applied consistently by all people working on the project.

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

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

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

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

// 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丑陋,至少可以说。

我的看法是,可选的应该是一个单子,这些在Java中是不可想象的。

In functional programming you deal with pure and higher order functions that take and compose their arguments only based on their "business domain type". Composing functions that feed on, or whose computation should be reported to, the real-world (so called side effects) requires the application of functions that take care of automatically unpacking the values out of the monads representing the outside world (State, Configuration, Futures, Maybe, Either, Writer, etc...); this is called lifting. You can think of it as a kind of separation of concerns.

混合使用这两种抽象级别不利于可读性,所以最好避免使用。