​JDK1.8新特性(八):还在重复写空指针检查代码?赶紧使用Optional吧!​

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"1、前言"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"作为一名Java程序员,无论是初入茅庐的菜鸟,还是久经江湖的高手,曾经肯定遭遇过各种各样的异常错误。在国外的一篇文章中,就统计了关于异常类型的排行榜,如下图:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/63/63aa883a2b7aabf121b3101f85ac2f2a.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"是的,你没有看错,"},{"type":"codeinline","content":[{"type":"text","text":"NullPointerException"}]},{"type":"text","text":"位居榜首。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Null Reference"}]},{"type":"text","text":"的发明者Charles Antony Richard Hoare说过:“我称之为我的十亿美元错误。这是1965年发明空引用的结果……这导致了无数的错误,漏洞和系统崩溃,在最近40年中可能造成十亿美元的痛苦和破坏。”"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这看起来有些夸张,但毫无争议的是"},{"type":"codeinline","content":[{"type":"text","text":"NullPointerException"}]},{"type":"text","text":"简直就是程序员心中的痛,并不是说它有多难以解决,而是为了解决它我们需要再付出了额外代价。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"还记得当初刚入行时候的你,三天两头碰到"},{"type":"codeinline","content":[{"type":"text","text":"NullPointerException"}]},{"type":"text","text":"而引发的bug,解决完一个,又在另一个地方遇到。这也慢慢让你懂得,不要相信任何“对象”,特别是别人提供给你的,在使用的地方都加上判断,这样就放心多了。于是代码通常就变成了下面这样:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"String name = \"Unknown\";\nif (null != people) {\n\tif (null != people.getName()) {\n \tname = people.getName();\n }\n}\nreturn name;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这样处理,虽然不用担心"},{"type":"codeinline","content":[{"type":"text","text":"NullPointerException"}]},{"type":"text","text":"了,但是过多的判断语句着实让人头皮发麻,代码变得臃肿不堪。如果对象过于复杂,对象里面还有对象等等,你还要继续逐层判断么?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"令人兴奋的是,JDK1.8引入了一个新类"},{"type":"codeinline","content":[{"type":"text","text":"java.util.Optional"}]},{"type":"text","text":",凭借Optional类提供的API,我们再也不用担心"},{"type":"codeinline","content":[{"type":"text","text":"NullPointerException"}]},{"type":"text","text":"了,更不会再去写那些烦人的判断啦。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"2、Optional类"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"举例来说,使用新类意味着,如果你知道一个人可能有也可能没有车,那么Person类内部的car变量就不应该声明为Car,遭遇某人没有车时把null引用赋值给它,而是应该像下图这样直接将其声明为Optional类型。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/45/45319b0759653d515cfd10b9044f400a.png","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"变量存在时,"},{"type":"codeinline","content":[{"type":"text","text":"Optional"}]},{"type":"text","text":"类只是对类简单封装。变量不存在时,缺失的值会被建模成一个“空”的"},{"type":"codeinline","content":[{"type":"text","text":"Optional"}]},{"type":"text","text":"对象,由方法"},{"type":"codeinline","content":[{"type":"text","text":"Optional.empty()"}]},{"type":"text","text":"返回。"},{"type":"codeinline","content":[{"type":"text","text":"Optional.empty()"}]},{"type":"text","text":"方法是一个静态工厂方法,它返回Optional类的特定单一实例。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Optional"}]},{"type":"text","text":",本质上是一个容器对象,拥有一个非空值或空值,需要我们将对象实例传入该容器中。如果值存在,"},{"type":"codeinline","content":[{"type":"text","text":"Optional.isPresent()"}]},{"type":"text","text":"方法返回"},{"type":"codeinline","content":[{"type":"text","text":"true"}]},{"type":"text","text":",并通过"},{"type":"codeinline","content":[{"type":"text","text":"Optional.get()"}]},{"type":"text","text":"方法获取值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Optional"}]},{"type":"text","text":"的构造方法为"},{"type":"codeinline","content":[{"type":"text","text":"private"}]},{"type":"text","text":",无法直接使用new来创建"},{"type":"codeinline","content":[{"type":"text","text":"Optional"}]},{"type":"text","text":"对象,只能使用"},{"type":"codeinline","content":[{"type":"text","text":"Optional"}]},{"type":"text","text":"提供的静态方法创建。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Optional提供的创建方法如下:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Optional.of(obj)"}]},{"type":"text","text":":如果对象为 "},{"type":"codeinline","content":[{"type":"text","text":"null"}]},{"type":"text","text":",将会抛出"},{"type":"codeinline","content":[{"type":"text","text":"NullPointerException"}]},{"type":"text","text":"。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Optional.ofNullable(obj)"}]},{"type":"text","text":":如果对象为 null,将会创建不包含值的 "},{"type":"text","marks":[{"type":"strong"}],"text":"EMPTY"},{"type":"text","text":" "},{"type":"codeinline","content":[{"type":"text","text":"Optional"}]},{"type":"text","text":"对象实例(new Optional<>())。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Optional.empty()"}]},{"type":"text","text":" :等同于 "},{"type":"codeinline","content":[{"type":"text","text":"Optional.ofNullable(null)"}]},{"type":"text","text":"。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中,源码片段如下:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"/**\n * Constructs an instance with the value present.\n *\n * @param value the non-null value to be present\n * @throws NullPointerException if value is null\n */\nprivate Optional(T value) {\n this.value = Objects.requireNonNull(value);\n}\n\n……\n\n/**\n * Returns an {@code Optional} with the specified present non-null value.\n *\n * @param the class of the value\n * @param value the value to be present, which must be non-null\n * @return an {@code Optional} with the value present\n * @throws NullPointerException if value is null\n */\npublic static Optional of(T value) {\n return new Optional<>(value);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"/**\n * Returns an {@code Optional} describing the specified value, if non-null,\n * otherwise returns an empty {@code Optional}.\n *\n * @param the class of the value\n * @param value the possibly-null value to describe\n * @return an {@code Optional} with a present value if the specified value\n * is non-null, otherwise an empty {@code Optional}\n */\npublic static Optional ofNullable(T value) {\n return value == null ? empty() : of(value);\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"/**\n * Common instance for {@code empty()}.\n */\nprivate static final Optional> EMPTY = new Optional<>();\n\n……\n\n/**\n * Returns an empty {@code Optional} instance. No value is present for this\n * Optional.\n *\n * @apiNote Though it may be tempting to do so, avoid testing if an object\n * is empty by comparing with {@code ==} against instances returned by\n * {@code Option.empty()}. There is no guarantee that it is a singleton.\n * Instead, use {@link #isPresent()}.\n *\n * @param Type of the non-existent value\n * @return an empty {@code Optional}\n */\npublic static Optional empty() {\n @SuppressWarnings(\"unchecked\")\n Optional t = (Optional) EMPTY;\n return t;\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"强烈建议使用"},{"type":"codeinline","content":[{"type":"text","text":"Optional.ofNullable(obj)"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":"方法,来创建"},{"type":"codeinline","content":[{"type":"text","text":"Optional"}],"marks":[{"type":"strong"}]},{"type":"text","marks":[{"type":"strong"}],"text":"对象,并获取对应值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"3、Optional的使用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"到目前为止,你已经知道"},{"type":"codeinline","content":[{"type":"text","text":"Optional"}]},{"type":"text","text":"的好处了吧,但是,我们该如何使用呢?"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用可接受null的"},{"type":"codeinline","content":[{"type":"text","text":"Optional"}]},{"type":"text","text":"对象,即:使用静态工程方法"},{"type":"codeinline","content":[{"type":"text","text":"Optional.ofNullable(obj)"}]},{"type":"text","text":",创建一个可以允许null值的"},{"type":"codeinline","content":[{"type":"text","text":"Optional"}]},{"type":"text","text":"对象:"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"Optional optional = Optional.ofNullable(people);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"即使people是null,optional对象也就是个空对象。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果people不为null,根据"},{"type":"codeinline","content":[{"type":"text","text":"Optional.isPresent()"}]},{"type":"text","text":"方法返回"},{"type":"codeinline","content":[{"type":"text","text":"true"}]},{"type":"text","text":",并通过"},{"type":"codeinline","content":[{"type":"text","text":"Optional.get()"}]},{"type":"text","text":"方法获取值。"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"为了避免NPE,"},{"type":"codeinline","content":[{"type":"text","text":"Optional.isPresent()"}]},{"type":"text","text":"方法已经对null进行了判断,若存在返回true。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"People p = null;\nif (optional.isPresent()) {\n p = optional.get();\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"看到这里,你可能会发现这与null判断检查并无差异。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"后来接触到"},{"type":"codeinline","content":[{"type":"text","text":"Optional"}]},{"type":"text","text":"其他API,我才发现真正体现它价值的是下面这些API。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"3.1 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong"}],"text":"Optional.map"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"从对象中获取某个属性,是最常见的操作。比如,你可能需要从people对象中获取人名。在获取人名之前,你需要检查people对象是否为null,如下所示:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"String name = null;\nif (null != people) {\n name = people.getName();\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用"},{"type":"codeinline","content":[{"type":"text","text":"Optional.map"}]},{"type":"text","text":"方法,可以这么写:"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"Optional optional = Optional.ofNullable(people);\nOptional stringOptional = optional.map(People::getName);"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"3.2 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong"}],"text":"Optional.orElse"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当一个对象为 null 时,业务上通常可以设置一个默认值,从而使流程继续下去。"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"String name = null != people ? people.getName() : \"Unknown\";"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"或者抛出一个异常。"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"if (null != people.getName()) {\n throw new RuntimeException();\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"Optional"}]},{"type":"text","text":" 类提供两个方法 "},{"type":"codeinline","content":[{"type":"text","text":"orElse"}]},{"type":"text","text":" 与 "},{"type":"codeinline","content":[{"type":"text","text":"orElseThrow"}]},{"type":"text","text":" ,可以方便完成上面转化。"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"// 设置默认值\nString name = optional.orElse(new People(\"Unknown\")).getName();\n// 抛出异常\nString name = optional.orElseThrow(RuntimeException::new).getName();"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果 "},{"type":"codeinline","content":[{"type":"text","text":"optional"}]},{"type":"text","text":" 为空,提供默认值或抛出异常。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"3.3 "},{"type":"codeinline","content":[{"type":"text","marks":[{"type":"strong"}],"text":"Optional.filter"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"你经常需要调用某个对象的方法,查看它的某些属性。比如,你可能需要检查人名是否为“xcbeyond”。为了以一种安全的方式进行这些操作,你首先需要判断people对象是否为null,再调用它的方法getName,如下所示:"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"if (null != people && \"xcbeyond\".equals(people.getName())) {\n\tSystem.out.println(\"ok\");\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用Optional类提供的方法filter,可以很好的重构:"}]},{"type":"codeblock","attrs":{"lang":null},"content":[{"type":"text","text":"optional.filter(people1 -> \"xcbeyond\".equals(people.getName()))\n\t\t.ifPresent(x -> System.out.print(\"ok\"));"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"4、Optional重构代码"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"让我们一起再看看文章开头的代码:"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"String name = \"Unknown\";\nif (null != people) {\n\tif (null != people.getName()) {\n \tname = people.getName();\n }\n}\nreturn name;"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在知道了Optional之后,进行代码重构:"}]},{"type":"codeblock","attrs":{"lang":""},"content":[{"type":"text","text":"Optional optional = Optional.ofNullable(people);\nreturn optional.map(People::getName).orElse(\"Unknown\");"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"结合Optional、Lambda表达式,可以明显看到重构之后,使得代码更加流畅连贯,并且提高代码整体可读性。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"参考文章:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"1."},{"type":"link","attrs":{"href":"https://dzone.com/articles/the-top-10-exception-types-in-production-java-appl","title":null},"content":[{"type":"text","text":"https://dzone.com/articles/the-top-10-exception-types-in-production-java-appl"}],"marks":[{"type":"italic"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"italic"}],"text":"2.《Java 8实战》"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/22/220b80503e4a0d5ba95bb4523329a4f2.gif","alt":null,"title":"","style":[{"key":"width","value":"100%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章