在Java 8中引入的Optional类型对许多开发人员来说是一个新事物。
Optional
一个返回Optional<Foo>类型的getter方法代替经典的Foo是一个好的实践吗?假设该值可以是null。
Optional<Foo>
Foo
null
我想说,一般来说,对于返回值可以为空的可选类型是个好主意。然而,对于框架,我认为用可选类型替换经典的getter方法会在使用依赖于getter和setter编码约定的框架(例如Hibernate)时带来很多麻烦。
在我自己做了一些研究之后,我发现了一些事情,可以建议什么时候这样做合适。最权威的是以下引用自Oracle文章:
__abc0 - __abc1
我还从Java 8可选:如何使用中找到了这个摘录
“Optional并不适用于这种情况,因为它并不能为我们买到任何东西: 在领域模型层(不可序列化) 在dto中(同样的原因) 输入方法的参数 在构造函数参数中
“Optional并不适用于这种情况,因为它并不能为我们买到任何东西:
这似乎也提出了一些有效的观点。
我没有找到任何负面含义或危险信号来建议Optional应该避免。我认为一般的想法是,如果它有帮助或提高了API的可用性,就使用它。
当然,人们会做他们想做的事。但是在添加这个特性时,我们确实有一个明确的意图,并且不是一个通用的Maybe类型,就像很多人希望我们这样做一样。我们的意图是为库方法返回类型提供一种有限的机制,其中需要一种明确的方式来表示“没有结果”,而使用null来表示这种类型极有可能导致错误。
例如,你可能永远不应该将它用于返回结果数组或结果列表的事情;而是返回一个空数组或列表。几乎不应该将它用作某个字段或方法参数。
我认为常规地使用它作为getter的返回值肯定是过度使用的。
在Optional中没有任何错误的是应该避免的,它只是不是许多人所希望的那样,因此我们相当担心过度使用的风险。
(公共服务公告:从来没有调用Optional.get,除非你能证明它永远不会为空;而是使用安全的方法,如orElse或ifPresent。回想起来,我们应该将get称为类似getOrElseThrowNoSuchElementException的东西,或者更清楚地表明这是一个非常危险的方法,它首先破坏了Optional的整个目的。教训。(更新:Java 10有Optional.orElseThrow(),它在语义上等价于get(),但它的名字更合适。))
Optional.get
orElse
ifPresent
get
getOrElseThrowNoSuchElementException
Optional.orElseThrow()
get()
如果你正在使用现代序列化器和其他理解Optional的框架,那么我发现这些的指导方针在编写Entity bean和域层时工作得很好:
Entity
FOO
BAR
Foo.getBar()
Foo.bar
private
Foo.setBar(String bar)
bar
IllegalArgumentException
为了使上面的方法更有效,你可能想要编辑你的IDE模板来生成getter和对应的toString(), equals(Obj o)等模板,或者直接使用这些字段(大多数IDE生成器已经处理空值)。
toString()
equals(Obj o)
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是为支持函数式编程而编写的,它同时被添加到Java中。(这个例子来自Brian Goetz的博客。一个更好的例子可能是使用orElse()方法,因为这段代码无论如何都会抛出异常,但你可以理解。)
orElse()
但现在,人们使用Optional的原因截然不同。他们用它来解决语言设计中的一个缺陷。缺陷在于:没有办法指定API的哪些参数和返回值允许为空。它可能会在javadocs中提到,但大多数开发人员甚至不会为他们的代码编写javadocs,也没有多少人会在编写时检查javadocs。因此,这导致许多代码在使用空值之前总是检查它们,即使它们通常不可能为空,因为它们已经在调用堆栈中重复验证了9或10次。
我认为人们确实渴望解决这个缺陷,因为很多人看到新的Optional类后都认为它的目的是为api增加清晰度。这就是为什么人们会问这样的问题:“getter应该返回optional吗?”不,它们可能不应该,除非您希望getter用于函数式编程,但这是不可能的。事实上,如果您查看Java API中使用Optional的地方,就会发现它主要在Stream类中,这是函数式编程的核心。(我没有检查得很彻底,但Stream类可能是它们使用的只有位置。)
如果您确实计划在一些函数代码中使用getter,那么使用一个标准getter和返回Optional的第二个getter可能是个好主意。
哦,如果你需要你的类是可序列化的,你绝对不应该使用Optional。
对于API缺陷来说,可选选项是一个非常糟糕的解决方案,因为a)它们非常冗长,b)它们从一开始就没有打算解决这个问题。
对API缺陷更好的解决方案是Nullness检查程序。这是一个注释处理器,允许您通过使用@Nullable注释指定哪些参数和返回值允许为空。通过这种方式,编译器可以扫描代码,并确定一个实际上可以为null的值是否被传递给了一个不允许为null的值。默认情况下,它假定任何东西都不允许为空,除非它被注释为空。这样,您就不必担心空值了。将空值传递给参数将导致编译器错误。测试一个不可能为空的对象是否为空会产生编译器警告。这样做的效果是将NullPointerException从运行时错误更改为编译时错误。
这改变了一切。
至于你的getter,不要使用Optional。试着设计你的类,让所有成员都不可能是空的。也许可以尝试添加Nullness检查器到你的项目,并声明你的getter和setter参数@Nullable如果他们需要它。我只在新项目中这样做过。它可能会在现有的项目中产生大量的警告,其中包含大量多余的null测试,因此可能很难进行改进。但它也会捕获很多bug。我很喜欢。因此,我的代码更加清晰可靠。
(还有一种新的语言可以解决这个问题。Kotlin可以编译为Java字节代码,它允许您在声明对象时指定对象是否可以为空。这是一种更干净的方法。)
原文补遗(第二版)
经过一番思考后,我勉强得出结论:在一个条件下返回Optional是可以接受的:检索到的值实际上可能是null。我见过很多代码,人们例行地从getter返回Optional,而这些getter不可能返回null。我认为这是一种非常糟糕的编码实践,只会增加代码的复杂性,从而更容易产生错误。但是当返回值实际上可能为空时,请继续并将其包装在Optional中。
请记住,为函数式编程设计的方法,以及需要函数引用的方法,将(并且应该)以两种形式编写,其中一种使用Optional。例如,Optional.map()和Optional.flatMap()都接受函数引用。第一个参数接受一个对普通getter的引用,第二个参数接受一个返回Optional的getter。因此,返回值不能为null的Optional并不是对任何人都有帮助。
Optional.map()
Optional.flatMap()
说了这么多,我仍然认为Nullness检查程序使用的方法是处理空值的最佳方法,因为它们将nullpointerexception从运行时错误转变为编译时错误。
您必须记住,经常被引用的建议来自那些在Java之外几乎没有任何经验的人,他们没有选项类型或函数式编程经验。
所以你要半信半疑。相反,让我们从“良好实践”的角度来看待它;角度:
良好的实践不仅意味着要问“我们如何编写新代码”,而且还要问“现有的代码会发生什么”。
在Optional的情况下,我的环境找到了一个很好的和容易遵循的答案:
Optional是强制性的,用于指示records中的可选值: record Pet(String name, Optional<Breed> breed, Optional<ZonedDateTime> dateOfBirth)
Optional是强制性的,用于指示records中的可选值:
records
record Pet(String name, Optional<Breed> breed, Optional<ZonedDateTime> dateOfBirth)
这意味着现有的代码是很好的,但是使用record的代码(即“新代码”)会导致围绕它的Optional被广泛采用。
record
结果在可读性和可靠性方面取得了圆满的成功。只要停止使用null即可。