From 7e7fb112038f62e5e5a2233cc967602cfbc5b372 Mon Sep 17 00:00:00 2001 From: Moilk Date: Fri, 8 Nov 2019 15:23:24 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC19=E7=AB=A0=20=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=20-=20Optional=20=E5=AE=8C=E6=88=901/2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/book/19-Type-Information.md | 128 +++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/docs/book/19-Type-Information.md b/docs/book/19-Type-Information.md index e977bb5..8909b21 100644 --- a/docs/book/19-Type-Information.md +++ b/docs/book/19-Type-Information.md @@ -1606,6 +1606,134 @@ boring3 ## Optional类 +如果你使用内置的 `null` 来表示没有对象,每次使用引用的时候就必须测试一下引用是否为 `null`,这显得有点枯燥,而且势必会产生相当乏味的代码。问题在于 `null` 没什么自己的行为,只会在你想用它执行任何操作的时候产生 `NullPointException`。`java.util.Optional`(首次出现是在[函数式编程](docs/book/13-Functional-Programming.md)这章)为 `null` 值提供了一个轻量级代理,`Optional` 对象可以防止你的代码直接抛出 `NullPointException`。 + +虽然 `Optional` 是 Java 8 为了支持流式编程才引入的,但其实它是一个通用的工具。为了证明这点,在本节中,我们会把它用在普通的类中。因为涉及一些运行时检测,所以把这一小节放在了本章。 + +实际上,在所有地方都使用 `Optional` 是没有意义的,有时候检查一下是不是 `null` 也挺好的,或者有时我们可以合理的假设不会出现 `null`,甚至有时候检查 `NullPointException` 异常也是可以接受的。`Optional` 最有用武之地的是在那些“更接近数据”的地方,在问题空间中代表实体的对象上。举个简单的例子,很多系统中都有 `Person` 类型,代码中有些情况下你可能没有一个实际的 `Person` 对象(或者可能有,但是你还没用关于那个人的所有信息)。这时,在传统方法下,你会用到一个 `null` 引用,并且在使用的时候测试它是不是 `null`。而现在,我们可以使用 `Optional`: + +```java +// typeinfo/Person.java +// Using Optional with regular classes +import onjava.*; +import java.util.*; +class Person { + public final Optional first; + public final Optional last; + public final Optional address; + // etc. + public final Boolean empty; + Person(String first, String last, String address) { + this.first = Optional.ofNullable(first); + this.last = Optional.ofNullable(last); + this.address = Optional.ofNullable(address); + empty = !this.first.isPresent() + && !this.last.isPresent() + && !this.address.isPresent(); + } + Person(String first, String last) { + this(first, last, null); + } + Person(String last) { + this(null, last, null); + } + Person() { + this(null, null, null); + } + @Override + public String toString() { + if(empty) + return ""; + return (first.orElse("") + + " " + last.orElse("") + + " " + address.orElse("")).trim(); + } + public static void main(String[] args) { + System.out.println(new Person()); + System.out.println(new Person("Smith")); + System.out.println(new Person("Bob", "Smith")); + System.out.println(new Person("Bob", "Smith", + "11 Degree Lane, Frostbite Falls, MN")); + } +} +``` + +输出结果: + +``` + +Smith +Bob Smith +Bob Smith 11 Degree Lane, Frostbite Falls, MN +``` + +`Person` 的设计有时候又叫“数据传输对象(DTO,data-transfer object)”。注意,所有域都是 `public` 和 `final` 的,所以没有 `getter` 和 `setter` 方法。也就是说,`Person` 是不可变的,你只能通过构造器给它赋值,之后就只能读而不能修改它的值(字符串本身就是不可变的,因此你无法修改字符串的内容,也无法给它的域重新赋值)。如果你想修改一个 `Person`,你只能用一个新的 `Person` 对象来替换它。`empty` 域在对象创建的时候被赋值,用于快速判断这个 `Person` 对象是不是空对象。 + +如果想使用 `Person`,就必须使用 `Optional` 接口才能访问它的 `String` 域,这样就不会意外触发 `NullPointException` 了。 + +现在假设你已经因你惊人的理念而获得了一大笔风险投资,现在你要招兵买马了,但是在虚位以待时,你可以将 `Person Optional` 对象放在每个 `Position` 上: + +```java +// typeinfo/Position.java +import java.util.*; +class EmptyTitleException extends RuntimeException { +} +class Position { + private String title; + private Person person; + Position(String jobTitle, Person employee) { + setTitle(jobTitle); + setPerson(employee); + } + Position(String jobTitle) { + this(jobTitle, null); + } + public String getTitle() { + return title; + } + public void setTitle(String newTitle) { + // Throws EmptyTitleException if newTitle is null: + title = Optional.ofNullable(newTitle) + .orElseThrow(EmptyTitleException::new); + } + public Person getPerson() { + return person; + } + public void setPerson(Person newPerson) { + // Uses empty Person if newPerson is null: + person = Optional.ofNullable(newPerson) + .orElse(new Person()); + } + @Override + public String toString() { + return "Position: " + title + + ", Employee: " + person; + } + public static void main(String[] args) { + System.out.println(new Position("CEO")); + System.out.println(new Position("Programmer", + new Person("Arthur", "Fonzarelli"))); + try { + new Position(null); + } + catch(Exception e) { + System.out.println("caught " + e); + } + } +} +``` + +输出结果: + +``` +Position: CEO, Employee: +Position: Programmer, Employee: Arthur Fonzarelli +caught EmptyTitleException +``` + +这里使用 `Optional` 的方式不太一样。请注意,`title` 和 `person` 都是普通域,不受 `Optional` 的保护。但是,修改这些域的唯一途径是调用 `setTitle()` 和 `setPerson()` 方法,这两个都借助 `Optional` 对域进行了严格的限制。 + + ## 接口和类型