mirror of
https://github.com/LingCoder/OnJava8.git
synced 2026-08-23 22:43:28 +08:00
第19章 类型信息 - Optional 完成1/2
This commit is contained in:
@@ -1606,6 +1606,134 @@ boring3
|
||||
<!-- Using Optional -->
|
||||
## 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<String> first;
|
||||
public final Optional<String> last;
|
||||
public final Optional<String> 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 "<Empty>";
|
||||
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"));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
输出结果:
|
||||
|
||||
```
|
||||
<Empty>
|
||||
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: <Empty>
|
||||
Position: Programmer, Employee: Arthur Fonzarelli
|
||||
caught EmptyTitleException
|
||||
```
|
||||
|
||||
这里使用 `Optional` 的方式不太一样。请注意,`title` 和 `person` 都是普通域,不受 `Optional` 的保护。但是,修改这些域的唯一途径是调用 `setTitle()` 和 `setPerson()` 方法,这两个都借助 `Optional` 对域进行了严格的限制。
|
||||
|
||||
<!-- TODO -->
|
||||
|
||||
<!-- Interfaces and Type -->
|
||||
## 接口和类型
|
||||
|
||||
Reference in New Issue
Block a user