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


当前回答

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

其他回答

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

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

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

免责声明:我在下面写的并不是对最初问题的回答,而是我对这个话题的想法。它的唯一来源是我的想法和经验(使用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.

首先,如果你使用方法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;

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

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

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