我在许多网站上读到过,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本身是否为null,然后尝试计算它包装的值。太多不必要的验证。


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

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

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

哦,这些编码风格应该有所保留。

(+)传递一个可选结果给另一个方法,没有任何语义分析;把它留给方法,是很好的。 (-)在方法中使用可选参数导致条件逻辑是反作用的。 (-)需要将参数打包到Optional中,对编译器来说是次优的,并且做了不必要的包装。 (-)与可空参数相比,Optional的开销更大。 (-)有人在实际参数中传递Optional为空的风险。

总的来说:可选的统一了两个状态,这两个状态必须被分解。因此,由于数据流的复杂性,它更适合于结果而不是输入。


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

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

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


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

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


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

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


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

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

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


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

// 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.

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


另一个在传递Optional作为参数时要小心的原因是方法应该做一件事…如果你传递一个可选参数,你可以做不止一件事,它可以类似于传递一个布尔参数。

public void method(Optional<MyClass> param) {
     if(param.isPresent()) {
         //do something
     } else {
         //do some other
     }
 }

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

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

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

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


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

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

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


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

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

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


让我们明确一点:在其他语言中,并没有反对将Maybe类型用作字段类型、构造函数形参类型、方法形参类型或函数形参类型的一般建议。

因此,如果您“不应该”在Java中使用Optional作为参数类型,原因是特定于Optional或Java或两者。

可能适用于其他Maybe类型或其他语言的推理在这里可能是无效的。

根据Brian Goetz,

[W]e did have a clear intention when adding [Optional], and it was not to be a general purpose Maybe type, as much as many people would have liked us to do so. Our intention was to provide a limited mechanism for library method return types where there needed to be a clear way to represent "no result", and using null for such was overwhelmingly likely to cause errors. For example, you probably should never use it for something that returns an array of results, or a list of results; instead return an empty array or list. You should almost never use it as a field of something or a method parameter.

所以答案是针对Optional的:它不是“一般用途的可能类型”;因此,它是有限制的,而且限制的方式可能限制了它作为字段类型或参数类型的用处。

也就是说,在实践中,我很少发现使用Optional作为字段类型或参数类型是一个问题。如果Optional(尽管有局限性)作为参数类型或字段类型用于您的用例,请使用它。


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

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


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

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

public void doSomething(arg1,arg2);

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

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

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


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

除函数返回值外,不应使用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当然是其中之一),这个问题很久以前就解决了。