转载

Java Optional使用的最佳实践

这是 piotr szybicki 4年来为了解正确使用Optional类型而努力的结果。

Optional隐藏了可能存在空指针的不确定性,比如:

List<String> numbers= ImmutableList.of(<font>"ONE"</font><font>, </font><font>"TWO"</font><font>, </font><font>"THREE"</font><font>);

<b>return</b> numbers.stream()
 .filter(number -> </font><font>"FOUR"</font><font>.equals(number))
 .findAny()
 .toLoweCase();
</font>

结果会导致NullPointerExceptions空指针错误,而使用if else进行判断是一种切割器cutter味道:

List<String> numbers= ImmutableList.of(<font>"ONE"</font><font>, </font><font>"TWO"</font><font>, </font><font>"THREE"</font><font>);
String numberThatImLookingFour = 
numbers.stream()
 .filter(number -> </font><font>"FOUR"</font><font>.equals(number))
 .findAny();
<b>if</b>(numberThatImLookingFour != <b>null</b>){
 <b>return</b> numberThatImLookingFour.toLowerCase();
}<b>else</b>{
 <b>return</b> </font><font>"not found"</font><font>;
}
</font>

所以Brian Goetz和Steward Marks(Java语言架构师)聚在一起写下了以下段落:

我们的目的是为库方法的返回类型提供一种有限的机制,其中需要一种明确的方式来表示“无结果”,并且对于这样的方法使用null绝对可能导致错误。

因此添加了Optional <T>,这不是一个真正的新概念,历史可以追溯到Haskell的Maybe monad。突然间,我们获得了JDK批准的表示可能存在或不存在的值的方式,和往常一样,在各地使用新功能的开发人员抓狂了。

空引用空指针是无数个错误的来源,通常也不能很好地标识“不存在”这个概念。

应该如何处理这个问题的重要部分来自DDD。在有界上下文的入口处我们说:' 你不能通过 ':

传入领域的所有变量是执行业务逻辑所需的,我们必须减少编写if(obj == null)检查判断的代码行数量。我们仍然需要与外部世界(数据库查询,REST端点等)进行交互,并根据执行的逻辑交互输出。如果使用Optional 得当则可以提供帮助。

什么是Optional?

  • 它是box类型,保持对另一个对象的引用。
  • 是不可变的,不可序列化的
  • 没有公共构造函数
  • 只能是present 或absent
  • 通过of(), ofNullable(), empty() 静态方法创建。

从这个盒子box中如何获取值?

  • get()
  • orElse()
  • orElseGet()
  • orElseThrow()

有一种诱惑是调用get()来获取其中的值。我们都知道普通JavaBean的getter / setters :)。并且期望如果我们调用get ...()我们就会得到一些东西。当调用普通bean的getter时,你永远不会得到任何抛出的异常。但是,如果调用在optional上调用get方法,并且该选项内部为空时,则会抛出异常NoSuchElementException。

这些方法应该被称为getOrThorwSomeHorribleError(),因此第一和第二条规则:

#1不要将null赋给Optional

#2避免使用Optional.get()。如果你不能证明存在可选项,那么永远不要调用get()。

使用orElse(), orElseGet(), orElseThrow().获得你的结果。

可以重构以下代码:

String variable = fetchSomeVaraible();
<b>if</b>(variable == <b>null</b>){
 1. <b>throw</b> <b>new</b> IllegalStateException(<font>"No such variable"</font><font>);
 2. <b>return</b> createVariable();
 3. <b>return</b> </font><font>"new variable"</font><font>;
} <b>else</b> { 
 ... 
 100 lines of code
 ...
}
</font>

重构到:

1. 
Optional<String> variableOpt = fetchOptionalSomeVaraible();
String variable = variableOpt.orElseThrow(() -> <b>new</b> Exeption(<font>""</font><font>)) 
... 100 lines of code ...
2.
Optional<String> variableOpt = fetchOptionalSomeVaraible();
String variable = variableOpt.orElseGet(() -> createVariable()) 
... 100 lines of code ...
3.
Optional<String> variableOpt = fetchOptionalSomeVaraible();
String variable = variableOpt.orElse(</font><font>"new variable"</font><font>) 
... 100 lines of code ...
</font>

注意,orElse(..)是急切计算,意味着下面代码:

Optional<Dog> optionalDog = fetchOptionalDog();
optionalDog
 .map(<b>this</b>::printUserAndReturnUser)
 .orElse(<b>this</b>::printVoidAndReturnUser)

如果值存在则将执行两个方法,如果值不存在,则仅执行最后一个方法。为了处理这些情况,我们可以使用方法orElseGet(),它将supplier 作为参数,并且是惰性计算的。

#3不要在字段,方法参数,集合中使用Optional。

下面是将thatField直接赋值给了类字段:

<b>public</b> <b>void</b> setThatField(Optional <ThatFieldType> thatField){ 
  <b>this</b>.thatField = thatField; 
} 

.

改为:

setThatField(Optional.ofNullable(thatField));

#4只有每当结果不确定时,使用Optional作为返回类型。。

说实话,这是使用 Optional 的唯一好地方。我将复制粘贴前面的话:

我们的目的是为库方法的返回类型提供一种有限的机制,其中需要一种明确的方式来表示“无结果”,并且对于这样的方法使用null 绝对可能导致错误。

#5不要害怕使用map和filter。

有一些值得遵循的一般开发实践称为SLA-p:Single Layer of Abstraction字母的第一个大写。

下面是需要被重构代码:

Dog dog = fetchSomeVaraible();
String dogString = dogToString(dog);
<b>public</b> String dogToString(Dog dog){
 <b>if</b>(dog == <b>null</b>){
   <b>return</b> <font>"DOG'd name is : "</font><font> + dog.getName();
 } <b>else</b> { 
   <b>return</b> </font><font>"CAT"</font><font>;
 }
}
</font>

重构到:

Optional<Dog> dog = fetchDogIfExists();
String dogsName = dog
 .map(<b>this</b>::convertToDog)
 .orElseGet(<b>this</b>::convertToCat)
<b>public</b> <b>void</b> convertToDog(Dog dog){
   <b>return</b> <font>"DOG'd name is : "</font><font> + dog.getName();
}
<b>public</b> <b>void</b> convertToCat(){
   <b>return</b> </font><font>"CAT"</font><font>;
}
</font>

Filter是有用的折叠语法:

Dog dog = fetchDog();
<b>if</b>(optionalDog != <b>null</b> && optionalDog.isBigDog()){
  doBlaBlaBla(optionalDog);
}

上面代码可以被重构为:

Optional<Dog> optionalDog = fetchOptionalDog();
optionalDog
 .filter(Dog::isBigDog)
 .ifPresent(<b>this</b>::doBlaBlaBla)

#6不要为了链方法而使用optional 。

使用 optional  时要注意的一件事是链式方法的诱惑。当我们像构建器模式一样链接方法时,事情可能看起来很漂亮:)。但并不总是等于更具可读性。所以不要这样做:

Optional
 .ofNullable(someVariable)
 .ifPresent(<b>this</b>::blablabla)

它对性能不利,对可读性也不好。我们应尽可能避免使用null引用。

#7使所有表达式成为单行lambda

这是更普遍的规则,我认为也应该应用于流。但这篇文章是关于 optional  。使用Optional 重要点是记住等式左边和右边一样重要:

Optional
 .ofNullable(someVariable)
 .map(variable -> {
   <b>try</b>{
      <b>return</b> someREpozitory.findById(variable.getIdOfOtherObject());
   } <b>catch</b> (IOException e){
     LOGGER.error(e); 
     <b>throw</b> <b>new</b> RuntimeException(e); 
   }})
 .filter(variable -> { 
   <b>if</b>(variable.getSomeField1() != <b>null</b>){
     <b>return</b> <b>true</b>;
   } <b>else</b> <b>if</b>(variable.getSomeField2() != <b>null</b>){
     <b>return</b> false;   
   } <b>else</b> { 
     <b>return</b> <b>true</b>;
   }
  })
 .map((variable -> {
   <b>try</b>{
      <b>return</b> jsonMapper.toJson(variable);
   } <b>catch</b> (IOException e){
     LOGGER.error(e); 
     <b>throw</b> <b>new</b> RuntimeException(e); 
   }}))
 .map(String::trim)
 .orElseThrow(() -> <b>new</b> RuntimeException(<font>"something went horribly wrong."</font><font>))
</font>

上面那么冗长代码块可以使用方法替代:

Optional
 .ofNullable(someVariable)
 .map(<b>this</b>::findOtherObject)
 .filter(<b>this</b>::isThisOtherObjectStale)
 .map(<b>this</b>::convertToJson)
 .map(String::trim)
 .orElseThrow(() -> <b>new</b> RuntimeException(<font>"something went horribly wrong."</font><font>));
</font>
原文  https://www.jdon.com/52008
正文到此结束
Loading...