Optional的使用场景
从个人角度来说其实使用不是很方便,下面说下Optional的工具使用场景:
1. 封装可能为Null的值
还是和所说的Map类似,当我们使用下面的操作获取值的时候,就很容易引发null:
User user = (User)map.get("user"); 复制代码
于是,Optional的用处就派上了:
Optional<Object> value = Optional.ofNullable(map.get("user")); 复制代码
2. 异常和optional的对比
通常情况下我们会使用捕获异常的方式进行异常的处理,下面是一个常见的字符串转Int的方法,一般情况下我们都会用try/catch
防止空指针或者转化异常,除非我们可以保证数据的准确性:
String str = "s"; try{ Integer.parseInt(str); }catch(Exception e){ // .... } 复制代码
使用Optional之后可以进行如下的改造,我们将得到一个安全的Optional进行操作,而不是一个可能存在隐式的NullPointException问题代码:
public static Optional<Integer> str2Int(String str) { try { return Optional.of(Integer.parseInt(str)); } catch (NumberFormatException e) { return Optional.empty(); } } 复制代码
3. 建议将Optional封装到一个工具类当中:
比如封装成下面这种的简单方法:
private static Optional<Integer> str2Int(String str) { try { return Optional.of(Integer.parseInt(str)); } catch (NumberFormatException e) { return Optional.empty(); } } 复制代码
4. 实战:读取Properties值
这里直接把书里的案例拿来用了,这个方法主要作用是读取一个配置文件的int
值,当读取不到内容的时候,自动给默认值0。
private static Optional<Integer> str2Int(String str) { try { return Optional.of(Integer.parseInt(str)); } catch (NumberFormatException e) { return Optional.empty(); } } public static int read(Properties properties, String name) { return Optional.ofNullable(properties.getProperty(name)) .flatMap(OptionalTest::str2Int) .filter(i -> i > 0) .orElse(0); } 复制代码
5. 其他测试:
下面是个人学习的时候一些简单案例尝试:
/** * 实际使用场景 * 1. 我们要将一个对象的名称全部统一为大写,防止空指针. 但是实际使用来看还是遇到了不少的问题 */ @Test public void actualUse() { User user = new User(); // java.lang.NullPointerException 如果编程习惯不好,这种工具类其实并不能解决问题 // Optional.ofNullable(user).ifPresent(u->{ // String toLowerCase = u.getName().toLowerCase(); // u.setName(toLowerCase); // }); // 正确的使用方式应该是如下的形式: // 下面的语句放开注释运行打印结果为: SSS // user.setName("sss"); // 如果为null会抛出 java.lang.RuntimeException // String s = Optional.ofNullable(user).map(User::getName).map(String::toUpperCase).orElseThrow(RuntimeException::new); // System.out.println(s); // 我们也可以用另一种方式 String s2 = Optional.ofNullable(user).map(User::getName).map(String::toUpperCase).orElse("默认值"); System.out.println(s2); // 我们要对一个前端传入的值进行split或者substring的时候 // 案例数据除开分隔符有差异之外无任何差异 String tags1 = "标签1,标签2,标签3,标签4"; String tags2 = "标签1,标签2,标签3,标签4"; String[] strings1 = Optional.of(tags1).map(tg -> tags1.split(",")).get(); System.out.println(strings1[0]); String[] strings2 = Optional.of(tags2).map(tg -> tags2.split(",")).get(); String[] strings3 = Optional.ofNullable(tags2).map(StringUtils::isNotBlank).map(tg -> tags2.split(",")).orElse(new String[]{"ss"}); System.out.println(strings2[0]); System.out.println(Arrays.toString(strings3)); // 运行结果,如果此时有值,基本无问题 // 标签1 // 标签1,标签2,标签3,标签4 // 如果上面的案例当中,传入的为null会如何? // 所以我们需要修改上面的格式,确保不管tag的值是否存在,都可以只关心我们具体需要操作的数据 // 如果为null,则没有任何结果处理。我们可以使用map进行各种操作 // String tags3 = "null,222"; String tags3 = "a,b|C|d"; Optional.ofNullable(tags3).map(tg -> tags3.split(",")).map(tg -> { for (int i = 0; i < tg.length; i++) { tg[i] = tg[i].toUpperCase(); } return tg; }).ifPresent(item -> { for (String s : item) { System.out.println(s); } }); // JSON解析的数据失败或者传入的格式不对导致的NULL System.out.print("\nJSON解析的数据失败或者传入的格式不对导致的NULL"); Object parse = JSON.parseObject("{name:\"13\"}", User.class); System.out.println(parse); Optional.ofNullable("{age:\"1\"}") .map(obj -> JSON.parseObject(obj, User.class)) .ifPresent(System.out::println); }/*运行结果 默认值 标签1 标签1,标签2,标签3,标签4 A B|C|D JSON解析的数据失败或者传入的格式不对导致的NULLcom.xxx.interview.jdk8.OptionalTest$User@548b7f67 com.xxx.interview.jdk8.OptionalTest$User@1810399e */ 复制代码
《Effective java》第55条建议
感兴趣的可以直接跳转下面这些链接(在线阅读网站貌似点不进去,所以只有在线PDF网址了...)
以防万一这里再补一个百度链接(如果公众号无法点击,请阅读原文获取)
链接:pan.baidu.com/s/1kQ8EHTn7…提取码:vkv3
既然提到了Optional的用法,这里也一并谈谈关于《Effective java》是如何看待这一个工具类的,这一条的标题是:谨慎返回Optional。
从个人的角度来看,Optional的本质作用是:提供了结果的可扩展性以及提供给调用方更多的可操作性,比如调用方可以使用此来按照以前的判断null方式处理if(obj.isPresent())
,或者使用新式的Lambda操作,比如像下面这样,如果存在值则进行打印的操作,否则什么事情都不会发生:
Optional.ofNullable("{age:\"1\"}") .map(obj -> JSON.parseObject(obj, User.class)) .ifPresent(System.out::println); 复制代码
还有一点需要注意的是get()
这个方法,如果不能确保值确实存在,建议谨慎或者避开这个方法,因为一旦为null此方法会抛出一个空指针异常。
另外,在之前讲述的方法:orElseThrow
这个方法传入的是一个异常工厂而不是真正的异常。
后面主要提到的是一些Java9的操作,由于本文只涉及Java8的版本,所以更高版本的内容可以从《Effective Java》这本书里面看到。
下面是作者关于optional的一些个人建议以及编程禁忌:
几点警告
1. 永远不要使用Optional返回Null
首先,该书作者也是提到了Optional在日常的编码工作当中如何使用它来规避一些可能存在的null对象操作,同时提出一个重要的禁忌:永远不要用Optional的返回值返回null,比如我们将上面练习当中的代码改为下面的方式:
private static Optional<Integer> str2Int(String str) { try { return Optional.of(Integer.parseInt(str)); } catch (NumberFormatException e) { // 千万不要这么做 return null; } } 复制代码
2. 不要使用包装基本类型的Optional
设计Optional的设计师在考虑的时候,为基础类型也设置了专属的Optional类,然而作者认为这三个类的设计很垃圾,并且建议永远不要返回基本包装类型,这里验证了一下,发现少了确实少了不少方法,比如:ofNullable
这个方法,这会直接导致你在编写代码的时候增加不必要的判断,并且无法互相兼容,我想这也是作者不推荐的原因之一吧。
- OptionalDouble
- OptionalInt
- OptionalLong
3. 不要把optional作为映射或者键值
这也很好理解,比如像下面这样:
Map<Optional<String>, Optional<Object>> map = // ..... 复制代码
这并不会让你少些代码,反而会增加代码的理解难度和程序的复杂度,所以不建议把Optional用作任何的键值对或者集合的元素当中。
什么时候应该使用?
一句话:**如果无法返回结果并且返回结果的客户端必须处理的时候,就应该声明Optional\<T\>。** 复制代码
哪些情况不适用?
其实上面的警告也提到了一部分内容:
- 非常注重性能的场合:因为Optional的封装以及类似“流”的操作需要额外的内存开销,所以不适合一些十分注重性能的情况。
- 需要使用键值对或者集合元素的场合:原因在上文说了,这里不再赘述。
- 包装基本类型的Optional:设计缺陷,对比源代码就知道了。
总结:
总之Optional这个工具还是具备一定的实用价值的,这里非常喜欢《Effective Java》作者对于这个工具类的一些建议,可以说是一针见血,果然大神的眼光和经验是非常独到的。