好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Java8中Optional的一些常见错误用法总结

前言

java 8 引入的 optional 类型,基本是把它当作 null 值优雅的处理方式。其实也不完全如此,optional 在语义上更能体现有还是没有值。所以它不是设计来作为 null 的替代品,如果方法返回 null 值表达了二义性,没有结果或是执行中出现异常。

在 oracle  做  java 语言工作的  brian goetz 在 stack overflow 回复 should java 8 getters return optional type? 中讲述了引入  optional 的主要动机。

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.

说的是  optional 提供了一个有限的机制让类库方法返回值清晰的表达有与没有值,避免很多时候 null 造成的 错误 。并非有了  optional 就要完全杜绝 nullpointerexception。

在 java 8 之前一个实践是方法返回集合或数组时,应返回空集合或数组表示没有元素; 而对于返回对象,只能用 null 来表示不存在,现在可以用  optional 来表示这个意义。

自 java8 于  2014-03-18 发布后已 5 年有余,这里就列举几个我们在项目实践中使用 optional 常见的几个用法。

optional 类型作为字段或方法参数

这儿把 optional  类型用为字段(类或实例变量)和方法参数放在一起来讲,是因为假如我们使用 intellij idea 来写 java 8 代码,idea 对于  optional 作为字段和方法参数会给出同样的代码建议:

reports any uses of java.util.optional<t> , java.util.optionaldouble , java.util.optionalint , java.util.optionallong or com.google.common.base.optional as the type for a field or parameter. optional was designed to provide a limited mechanism for library method return types where there needed to be a clear way to represent "no result". using a field with type java.util.optional is also problematic if the class needs to be serializable , which java.util.optional is not.

不建议用任何的 optional 类型作为字段或参数,optional 设计为有限的机制让类库方法返回值清晰的表达 "没有值"。 optional 是不可被序列化的,如果类是可序列化的就会出问题。

上面其实重复了 java 8 引入  optional 的意图,我们还有必要继续深入理解一下为什么不该用  optional 作为字段或方法参数。

当我们选择 optional 类型而非内部包装的类型后,应该是假定了该 optional 类型不为 null,否则我们在使用 optional 字段或方法参数时就变得复杂了,需要进行两番检查。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

public class user {

  private string firstname;

  private optional<string> middlename = optional.empty();

  private string lastname;

 

  public void setmiddlename(optional<string> middlename) {

   this .middlename = middlename;

  }

 

  public string getfullname() {

   string fullname = firstname;

   if (middlename != null ) {

    if (middlename.ispresent()){

     fullname = fullname.concat( "." + middlename.get());

   }

 

   return fullname.concat( "." + lastname);

  }

}

由于 middlename 的 setter 方法,我们可能造成 middlename 变为 null 值,所以在构建 fullname 时必须两重检查。

并且在调用 setmiddlename(...) 方法时也有些累赘了

?

1

2

user.setmiddlename(optional.empty());

user.setmiddlename(optional.of( "abc" ));

而如果字段类型非 optional 类型,而传入的方法参数为 optional 类型,要进行赋值的话

?

1

2

3

4

5

6

7

8

9

private string middlename;

 

public void updatemiddlename(optional<string> middlename) {

  if (middlename != null ) {

   this .middlename = middlename.orelse( null );

  } else {

   this .middlename = null ;

  }

}

前面两段代码如果应用 optional.ofnullable(...) 包裹 optional 来替代 if(middlename != null) 就更复杂了。

对于本例直接用 string 类型的 middlename  作为字段或方法参数就行,null 值可以表达没有 middlename。如果不允许 null 值  middlename, 显式的进行入口参数检查而拒绝该输入 -- 抛出异常。

利用 optional 过度检查方法参数

这一 optional 的用法与之前的可能为 null 值的方法参数,不分清红皂白就用 if...else 检查,总有一种不安全感,步步惊心,结果可能事与愿违。

?

1

2

3

4

5

6

7

public user getuserbyid(string userid) {

  if (userid != null ) {

   return userdao.findbyid(userid);

  } else {

   return null ;

  }

}

只是到了 java 8 改成了用 optional

?

1

2

3

return if (optional.ofnullable(userid)

  .map(id -> userdao.findbyid(id))

  .orelse( null );

上面两段代码其实是同样的问题,如果输入的 userid 是 null 值不调用 findbyid(...) 方法而直接返回 null 值,这就有两个问题

?

1

2

userdao.findbyid(...)

getuserbyid(userid)

这种情况下立即抛出 nullpointerexception 是一个更好的主意,参考下面的代码

?

1

2

3

4

5

6

7

8

9

public user getuserbyid(string userid) { //抛出出 nullpointerexception 如果 null userid

  return userdao.findbyid(objects.requirenonull(userid, "invalid null userid" );

}

 

//or

public user getuserbyid(string userid) { //抛出 illegalargumentexception 如果 null userid

  preconditions.checkargument(userid != null , "invalid null userid" );

  return userdao.findbyid(userid);

}

即使用了 optional 的 orelsethrow 抛出异常也不能明确异常造成的原因,比如下面的代码

?

1

2

3

4

5

6

public user getuserbyid(string userid) {

  return optional.ofnullable(userid)

   .map(id -> userdao.findbyid(id))

   orelsethrow(() ->

    new runtimeexception( "userid 是 null 或 findbyid(id) 返回了 null 值" ));

}

纠正办法是认真的审视方法的输入参数,对不符合要求的输入应立即拒绝,防止对下层的压力与污染,并报告出准确的错误信息,以有利于快速定位修复。

optional.map(...) 中再次 null 值判断

假如有这样的对象导航关系 user.getorder().getproduct().getid() , 输入是一个  user 对象

?

1

2

3

4

5

string productid = optional.ofnullable(user)

  .map(user::getorder)

  .flatmap(order -> optional.ofnullable(order.getproduct())) //1

  .flatmap(product -> optional.ofnullable(product.getid())) //2

  .orelse( "" );

#1 和 #2 中应用 flatmap 再次用 optional.ofnullable() 是因为担心 order.getproduct() 或 product.getid() 返回了 null 值,所以又用 optional.ofnullable(...) 包裹了一次。代码的执行结果仍然是对的,代码真要这么写的话真是 oracle 的责任。这忽略了 optional.map(...) 的功能,只要看下它的源代码就知道

?

1

2

3

4

5

6

7

8

public <u> optional<u> map(function<? super t, ? extends u> mapper) {

  objects.requirenonnull(mapper);

  if (!ispresent())

   return empty();

  else {

   return optional.ofnullable(mapper.apply(value));

  }

}

map(...) 函数中已有考虑拆解后的 null 值,因此呢 flatmap 中又 optional.ofnullable 是多余的,只需简单一路用 map(...) 函数

?

1

2

3

4

5

string productid = optional.ofnullable(user)

  .map(user::getorder)

  .map(order -> order.getproduct()) //1

  .map(product -> product.getid()) //2

  .orelse( "" );

optional.ofnullable 应用于明确的非  null 值

如果有时候只需要对一个明确不为 null 的值进行 optional 包装的话,就没有必要用 ofnullable(...) 方法,例如

?

1

2

3

4

5

6

7

8

public optional<user> getuserbyid(string userid) {

  if ( "admin" .equals(userid)) {

   user adminuser = new user( "admin" );

   return optional.ofnullable(adminuser); //1

  } else {

   return userdao.findbyid(userid);

  }

}

在代码 #1 处非常明确 adminuser 是不可能为 null 值的,所以应该直接用 optional.of(adminuser) 。这也是为什么 optional 要声明 of(..) 和 ofnullable(..) 两个方法。看看它们的源代码:

?

1

2

3

4

5

6

7

public static <t> optional<t> of(t value) {

  return new optional<>(value);

}

 

public static <t> optional<t> ofnullable(t value) {

  return value == null ? empty() : of(value);

}

知道被包裹的值不可能为 null 时调用 ofnullable(value) 多了一次多余的 null 值检查。相应的对于非 null 值的字面常量

?

1

2

optional.ofnullable( 100 ); //这样不好

optional.of( 100 );   //应该这么用

小结:

要理解 optional 的设计用意,所以语意上应用它来表达有/无结果,不适于作为类字段与方法参数 倾向于方法返回单个对象,用 optional 类型表示无结果以避免 null 值的二义性 optional 进行方法参数检查不能掩盖了错误,最好是明确非法的参数输入及时抛出输入异常 对于最后两种不正确的用法应熟悉 optional 的源代码实现就能规避

链接:

java 8 optional use cases

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。

原文链接:https://yanbin.blog/java8-optional-several-common-incorrect-usages/

查看更多关于Java8中Optional的一些常见错误用法总结的详细内容...

  阅读:45次