Nei*_*ens 344 java optional java-8
我在许多网站上阅读过Optional只能用作返回类型,而不能在方法参数中使用.我很难找到合乎逻辑的原因.例如,我有一个逻辑,它有2个可选参数.因此,我认为像这样编写我的方法签名是有意义的(解决方案1):
public int calculateSomething(Optional<String> p1, Optional<BigDecimal> p2 {
// my logic
}
Run Code Online (Sandbox Code Playgroud)
许多网页指定Optional不应该用作方法参数.考虑到这一点,我可以使用以下方法签名并添加一个清晰的Javadoc注释来指定参数可能为null,希望将来的维护者将读取Javadoc,因此在使用参数之前始终执行空值检查(解决方案2) :
public int calculateSomething(String p1, BigDecimal p2) {
// my logic
}
Run Code Online (Sandbox Code Playgroud)
或者,我可以用四种公共方法替换我的方法以提供更好的界面并使其更明显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
}
Run Code Online (Sandbox Code Playgroud)
现在我尝试编写类的代码,为每种方法调用这条逻辑.我首先从另一个返回Optionals的对象中检索两个输入参数然后调用calculateSomething.因此,如果使用解决方案1,则调用代码将如下所示:
Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result = myObject.calculateSomething(p1, p2);
Run Code Online (Sandbox Code Playgroud)
如果使用解决方案2,则调用代码将如下所示:
Optional<String> p1 = otherObject.getP1();
Optional<BigInteger> p2 = otherObject.getP2();
int result = myObject.calculateSomething(p1.orElse(null), p2.orElse(null));
Run Code Online (Sandbox Code Playgroud)
如果应用解决方案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();
}
}
Run Code Online (Sandbox Code Playgroud)
所以我的问题是:为什么将Optionals用作方法参数被认为是不好的做法(参见解决方案1)? 对我来说,它看起来是最易读的解决方案,并且最明显的是参数对于未来的维护者来说可能是空的/ null.(我知道设计者Optional只打算将它用作返回类型,但我找不到任何合理的理由不在这种情况下使用它).
Joo*_*gen 171
哦,那些编码风格应该用一些盐.
通常:可选统一两个状态,必须解开.因此,对于数据流的复杂性,更适合于结果而不是输入.
Gil*_*ili 144
我在这个主题上看过的最好的帖子是Daniel Olszewski写的:
虽然对于非强制性方法参数可能会考虑将Optional作为诱惑,但与其他可能的替代方案相比,这种解决方案显得苍白无力.为了说明问题,请检查以下构造函数声明:
Run Code Online (Sandbox Code Playgroud)public SystemMessage(String title, String content, Optional<Attachment> attachment) { // assigning field values }乍一看,它可能看起来是一个正确的设计决定.毕竟,我们明确将附件参数标记为可选.但是,至于调用构造函数,客户端代码可能会变得有点笨拙.
Run Code Online (Sandbox Code Playgroud)SystemMessage withoutAttachment = new SystemMessage("title", "content", Optional.empty()); Attachment attachment = new Attachment(); SystemMessage withAttachment = new SystemMessage("title", "content", Optional.ofNullable(attachment));Optional类的工厂方法不会提供清晰度,只会分散读者的注意力.请注意,只有一个可选参数,但想象有两个或三个.鲍勃叔叔绝对不会为这样的代码感到骄傲
当一个方法可以接受可选参数时,最好采用经过充分验证的方法,并使用方法重载设计这种情况.在SystemMessage类的示例中,声明两个单独的构造函数优于使用Optional.
Run Code Online (Sandbox Code Playgroud)public SystemMessage(String title, String content) { this(title, content, null); } public SystemMessage(String title, String content, Attachment attachment) { // assigning field values }这一变化使客户端代码更加简单易读.
Run Code Online (Sandbox Code Playgroud)SystemMessage withoutAttachment = new SystemMessage("title", "content"); Attachment attachment = new Attachment(); SystemMessage withAttachment = new SystemMessage("title", "content", attachment);
小智 78
几乎没有充分理由不使用Optional参数.反对此的论据依赖于来自权威的论据(参见Brian Goetz - 他的论点是我们不能强制执行非null选项)或者Optional参数可能为null(基本上是相同的参数).当然,Java中的任何引用都可以为null,我们需要鼓励编译器强制执行规则,而不是编程器内存(这是有问题的并且不能扩展).
函数式编程语言鼓励Optional参数.使用此方法的最佳方法之一是使用多个可选参数并使用liftM2函数,假设参数不为空并返回可选参数(请参阅http://www.functionaljava.org/javadoc/4.4/functionaljava/fj/ data/Option.html#liftM2-fj.F-).遗憾的是,Java 8实现了一个支持可选的非常有限的库.
作为Java程序员,我们应该只使用null来与遗留库进行交互.
Dmi*_*kin 14
也许我会招来一堆反对票和负面评论,但是……我无法忍受。
免责声明:我下面写的并不是对原始问题的真正答案,而是我对这个主题的想法。它的唯一来源是我的想法和我的经验(Java 和其他语言)。
首先让我们检查一下,为什么有人喜欢使用Optional?
对我来说,原因很简单:与其他语言不同,java 没有内置功能来将变量(或类型)定义为可空或不可空。所有“对象”变量都是可为空的,而所有基元类型都不是。为了简单起见,在进一步的讨论中不要考虑基本类型,因此我将简单地声明所有变量都是可为空的。
为什么需要将变量声明为可为空/不可为空?嗯,对我来说,原因是:显式总是比隐式更好。除了显式修饰(例如注释或类型)之外,还可以帮助静态分析器(或编译器)捕获一些与空指针相关的问题。
许多人在上面的评论中争论说,函数不需要有可为空的参数。相反,应该使用重载。但这样的说法只有在教科书中才有用。现实生活中存在不同的情况。考虑类,它代表某些系统的设置,或某些用户的个人数据,或者实际上任何包含大量字段的复合数据结构 - 其中许多字段具有重复的类型,并且某些字段是强制性的,而其他字段是可选的。在这种情况下,继承/构造函数重载并没有真正的帮助。
随机示例:假设我们需要收集有关人员的数据。但有些人不想提供所有数据。当然,这是 POD,所以基本上使用值语义进行类型,所以我希望它或多或少是不可变的(没有设置器)。
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?
// ...
}
Run Code Online (Sandbox Code Playgroud)
因此,IMO 上面的讨论表明,尽管大多数情况下我们可以在没有可空参数的情况下生存,但有时这是不可行的。
现在我们来解决这个问题:如果一些参数可以为空,而另一些则不能,我们如何知道是哪一个?
方法 1:所有参数均可为 null(根据 java 标准,基本类型除外)。所以我们检查所有这些。
结果:代码因检查而爆炸,这大多是不需要的,因为正如我们上面讨论的,几乎所有时间我们都可以继续使用可空变量,并且只有在极少数情况下才需要“可为空”。
方法 2:使用文档和/或注释来描述哪些参数/字段可以为空,哪些不能为空。
结果:它并没有真正起作用。人们懒于编写和阅读文档。此外,最近的趋势是,我们应该避免编写文档,而应该让代码本身具有自我描述性。除此之外,所有关于修改代码而忘记修改文档的推理仍然有效。
方法 3:@Nullable @NonNull 等...我个人认为它们很好。但也有一定的缺点:(例如,它们只受到外部工具的尊重,而不是编译器的尊重),最糟糕的是它们不是标准的,这意味着,1.我需要向我的项目添加外部依赖项才能受益2.不同系统对待他们的方式并不统一。据我所知,它们被投票排除在官方 Java 标准之外(我不知道是否有计划再次尝试)。
方法 4:可选<>。其他评论中已经提到了缺点,其中最糟糕的是(IMO)性能损失。它还添加了一些样板,即使我个人发现,使用Optional.empty()和Optional.of()并不是那么糟糕。优点很明显:
因此,在我看来,任何方法论(包括这一方法论)都没有非黑即白的界限。我个人最终得出以下准则和约定(仍然不是严格的规则):
仍然存在灰色区域,这些约定不起作用:
顺便说一句,最后两种情况也可能是可选字段/参数中需求的来源。即,当数据的结构不是我们自己开发的,而是由一些外部接口、数据库模式等强加的时......
最后,我认为,人们应该思考正在解决的问题,并尝试找到合适的工具。如果Optional<>是合适的,那么我认为没有理由不使用它。
编辑:方法 5:我最近使用了这个,当时我无法使用Optional. 这个想法只是对方法参数和类变量使用命名约定。我使用了“maybe”前缀,这样如果“url”参数可以为空,那么它就变成了maybeUrl。优点是它稍微提高了意图的可理解性(并且没有其他方法的缺点,例如外部依赖性或性能损失)。但也有缺点,例如:没有工具支持此约定(如果您在没有先检查的情况下访问“也许”变量,您的 IDE 不会向您显示任何警告)。另一个问题是,只有参与该项目的所有人员一致应用时,它才会有帮助。
llo*_*giq 11
这个建议是"关于输入尽可能不具体,并且尽可能具体关于输出"的经验法则的变体.
通常,如果您有一个采用普通非空值的方法,则可以将其映射到该方法Optional,因此普通版本在输入方面严格来说是非特定的.然而,尽管如此,仍有许多可能的原因可能需要Optional参数:
OptionalOptional如果给定值为空,则函数应返回空值以外的其他值Optional非常棒,无论谁使用您的API都应该被要求了解它;-)Mik*_*kis 10
所以,如果你允许双关语的话,Oracle 发布了一个预言:
\n\n\n请勿将
\nOptionalbut 用于函数返回值。
\xce\x97ere 是一个不同意见:
\n\n\n您可以
\nnull通过Optional在任何地方使用来完全消除您的代码库:不仅在函数返回值中,而且在函数参数中,在类成员中,在数组成员中,甚至在局部变量中。
我已经在一个 10 万行代码的项目中完成了它。有效。
\n如果你决定走这条路,那么你就需要彻底,所以你将有很多工作要做。接受的答案中提到的例子Optional.ofNulable()永远不应该发生,因为如果你是彻底的,那么你不应该有null任何地方,因此不需要Optional.ofNullable()。
在我上面提到的那个 10 万行代码的项目中,我只在从Optional.ofNullable()我无法控制的外部方法接收结果时使用过几次。我从来没有必要将它们Optional.ofNullable( nullablePointer )作为参数传递给函数,因为我周围没有任何可为空的指针:我总是在它们进入我的系统的几个地方将它们转换为可选值。
现在,如果您决定沿着这条道路走下去,您的解决方案将不会是性能最高的解决方案,因为您将分配大量Optional. 然而:
让我解释一下最后一点。
\nJava 不像 C# 那样提供显式的可为空性(自版本 8.0 起),因此它在这方面较差。(我说的是“在这方面”;在其他方面,Java 更好;但现在这是题外话。)
\n引用类型的显式可为空性的唯一正确替代方案是类型Optional。(而且可以说它甚至稍微好一点,因为Optional如果必须的话,您可以指示可选的可选,而使用显式可空性则不能ReferenceType??,或者至少在当前实现的 C# 中不能。)
Optional不必增加开销,它只在 Java 中这样做。这是因为 Java 也不支持真正的值类型,而 C# 和 Scala 则支持。在这方面,Java 远远不如那些语言。(再次,我说“在这方面”;在其他方面,Java 更好;但现在这是题外话。)如果 Java 确实支持真值类型,那么Optional将被实现为单个机器字,这意味着使用它的运行时开销将为零。
所以,最终归结为一个问题:
\n您希望代码具有完美的清晰度和类型安全性,还是希望获得最佳性能?
\n我相信对于高级语言(Java 无疑是其中之一)来说,这个问题很久以前就已经解决了。
\n模式Optional是为了避免返回 null.传递null给方法仍然是完全可能的.
虽然这些还不是官方的,但您可以使用JSR-308样式注释来指示您是否接受null函数中的值.请注意,您必须使用正确的工具来实际识别它,并且它提供的静态检查比可执行的运行时策略更多,但它会有所帮助.
public int calculateSomething(@NotNull final String p1, @NotNull final String p2) {}
Run Code Online (Sandbox Code Playgroud)
让我们明确一点:在其他语言中,没有反对使用 Maybe 类型作为字段类型、构造函数参数类型、方法参数类型或函数参数类型的一般建议。
因此,如果您“不应该”在 Java 中将 Optional 用作参数类型,则原因特定于 Optional、Java 或两者。
可能适用于其他 Maybe 类型或其他语言的推理在这里可能无效。
[W]e 在添加 [Optional] 时确实有一个明确的意图,它不是一个通用的 Maybe 类型,因为很多人都希望我们这样做。我们的目的是为库方法返回类型提供一种有限的机制,需要一种明确的方式来表示“无结果”,而对这种情况使用 null 极有可能导致错误。
例如,您可能永远不应该将它用于返回结果数组或结果列表的内容;而是返回一个空数组或列表。您几乎不应该将它用作某物的字段或方法参数。
所以答案是针对 Optional 的:它不是“通用的 Maybe 类型”;因此,它是有限的,并且可能以限制其作为字段类型或参数类型的有用性的方式受到限制。
也就是说,在实践中,我很少发现使用 Optional 作为字段类型或参数类型是一个问题。如果 Optional 尽管有其限制,但可以作为您用例的参数类型或字段类型,请使用它。
这对我来说似乎有点傻,但我能想到的唯一原因是方法参数中的对象参数在某种程度上已经是可选的 - 它们可以为null.因此,强迫某人获取现有对象并将其包装在可选对象中是毫无意义的.
话虽这么说,链接方法一起采取/返回选项是一个合理的事情,例如也许monad.
我的看法是 Optional 应该是一个 Monad,而这些在 Java 中是不可想象的。
在函数式编程中,您处理纯函数和高阶函数,这些函数仅根据其“业务领域类型”接受和组合其参数。组合以现实世界为基础的函数,或者其计算应该报告给现实世界(所谓的副作用)需要应用程序来自动将值从代表外部世界的 monad 中解包出来(状态、配置、 Futures、Maybe、Either、Writer 等……);这叫做提升。您可以将其视为一种关注点分离。
混合这两个抽象级别不会提高易读性,因此最好避免使用它。
接受 Optional 作为参数会导致调用者级别不必要的包装。
例如在以下情况下:
public int calculateSomething(Optional<String> p1, Optional<BigDecimal> p2 {}
Run Code Online (Sandbox Code Playgroud)
假设您有两个非空字符串(即从其他方法返回):
String p1 = "p1";
String p2 = "p2";
Run Code Online (Sandbox Code Playgroud)
即使您知道它们不是 Empty,您也不得不将它们包装在 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 >));
Run Code Online (Sandbox Code Playgroud)
参考:
方法不应该期望 Option 作为参数,这几乎总是一种代码味道,表明从调用者到被调用者的控制流泄漏,调用者应该负责检查 Option 的内容
参考 https://github.com/teamdigitale/digital-citizenship-functions/pull/148#discussion_r170862749
| 归档时间: |
|
| 查看次数: |
127967 次 |
| 最近记录: |