共计 6254 个字符,预计需要花费 16 分钟才能阅读完成。
前言
Java 8 中引入了 Optional
类来解决 NullPointerException
与繁琐的 null
查看,该类首次呈现在 Guava
。Java 8 才成为类库中的一部分。
入门
Optional
是一个封装值的类,用于保留类型为 T
的值;实质上,Optional
就是一个容器。
举例来说,一个人可能有车也可能没有,那么 Person
类外部 car
变量就不应该申明为 Car
,当变量存在时,Optional
类只是对 Car
的简略封装。变量不存在时,会应用 Optional.empty()
办法返回空的 Optional
对象。如下所示:
然而 null
援用和 Optional.empty()
有什么本质区别?从语义上,它们能够当成一回事儿,但实际上差异十分大:如果尝试解援用一个 null
,肯定会触发 NullPointerException
,不过应用 Optional.empty()
是一个无效对象。
上面咱们来看一下 Optional
提供的性能。
创立
说到 Optional
的性能,咱们首先要理解 Optional
实例的创立。
空 Optional
正如前文提到,你能够通过动态工厂办法 Optional.empty
,创立一个空的 Optional
对象:
Optional<Car> option = Optional.empty();
因为 empty()
自身代表的就是空对象,所以调用 get
办法会抛出 NoSuchElementException
异样。
非空 Optional
你还能够应用动态工厂办法 Optional.of
,根据一个非空值创立一个 Optional
对象:
Optional<Car> optional = Optional.of(car);
如果 car
是一个 null
,这段代码会立刻抛出一个 NullPointerException
,而不是等到你试图拜访 car
的属性值时才返回一个谬误。
可为 null 的 Optional
最初,应用动态工厂办法 Optional.ofNullable
,你能够创立一个容许 null
值的 Optional
对象:
Optional<Car> optional = Optional.ofNullable(car);
如果 car
是 null
,那么失去的 Optional
对象就是个空对象。咱们能够查看一下它的实现原理:
public static <T> Optional<T> ofNullable(T value) {return value == null ? empty() : of(value);
}
依据它的实现形式,咱们可知,传入的值是空值时,会返回 Optional.empty()
空对象。这有利于咱们封装那些可能为 null
的值。例如,有一个 Map<String, Object>
实例,拜访 key
索引时,如果没有与 key
关联的值,则会返回一个 null
。因而,咱们能够应用 Optional.ofNullable
办法封装返回值:
Optional<Object> value = Optional.ofNullable(map.get("key"));
// 能够由上代码代替 Object value = map.get("key");
这样能够将潜在的 null
隐患替换为空的 Optional
对象。
操作
咱们创立了 Optional
实例后,须要对该实例进行操作。
isPresent & get
在 Optional
类中,isPresent
办法对 Optional
实例进行判断,是否蕴含值,如果存在值,就返回 true
,否则返回 false
;与之绝对的是 isEmpty
办法 Optional
类中还有 get
办法,它是用来获取 Optional
实例中的值。
Optional<String> optional = Optional.of("is present");
if (optional.isPresent()) {System.out.println("the value is" + optional.get());
}
isPresent
与 get
个别组合应用来防止 NullPointerException
:
public boolean isPresent() {return value != null;}
public T get() {if (value == null) {throw new NoSuchElementException("No value present");
}
return value;
}
从源码中可看出,get
办法在取值时,要进行判空操作,如果不应用 isPresent
办法,可能会呈现空指针异样。然而这种形式和在代码中 if(null != value)
没有区别,因而咱们要尽量避免应用该组合。
ifPresent
除了 isPresent
的简洁办法,Optional
还提供了接管函数式参数的接口 ifPresent
:
public void ifPresent(Consumer<? super T> action) {if (value != null) {action.accept(value);
}
}
该办法会接管一个消费型函数。如果 Optional
实例中的值不为空,则调用 Consumer
的 accept
办法对 value
进行生产,若为空则不做解决。下面的例子能够应用 ifPresent
重写:
Optional<String> optional = Optional.of("is present");
optional.isPresent((val) -> System.out.println("the value is" + val));
orElse
咱们还能够应用 orElse
办法读取 Optional
中的值。
public T orElse(T other) {return value != null ? value : other;}
应用这种形式能够定义一个默认值,这种形式当遭逢 Optional
中的变量为空时,默认值会作为该办法的返回值。
String optGet = null;
String orElse = Optional.ofNullable(optGet).orElse("Default");
/**
* 输入后果:* Default
*/
orElseGet
如果该办法不够,咱们能够应用另一种形式 orElseGet
:
public T orElseGet(Supplier<? extends T> supplier) {return value != null ? value : supplier.get();
}
该办法与 orElse
的区别就是值不存在时,调用实现 Supplier
接口的办法或 Lambda 表达式来返回默认值。
String optGet = null;
String orElse = Optional.ofNullable(optGet).orElse(() -> "Default");
/**
* 输入后果:* Default
*/
orElseThrow
orElseThrow
办法是在有值时返回其值,无值的时候会抛出由 Supplier
创立的异样。咱们看一下它的实现原理:
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {if (value != null) {return value;} else {throw exceptionSupplier.get();
}
}
在 orElseThrow
原理中,会传入一个 Lambda 表达式或办法,如果值不存在来抛出异样:
class NoValueException extends RuntimeException {public NoValueException() {super();
}
@Override
public String getMessage() {return "No value present in the Optional instance";}
}
public static Integer orElseThrow() {return (Integer) Optional.empty().orElseThrow(NoValueException::new);
}
public static void main(String[] args) {orElseThrow();
}
/**
* 控制台会抛出异样:* Exception in thread "main" xx.NoValueException: No value present in the Optional instance
*/
orElseThrow
与 orElseGet
的区别就是一个在无值的时候抛出异样,一个在无值的时候应用 Lambda 表达式来实现默认值。
orElseThrow
只是在无值的时候抛出异样,那自身会抛出异样的办法呢?
当初,咱们拿 Integer.parseInt(String)
做个例子:
public static Integer toInt(String s) {
try {
// 如果 String 能转换为对应的 Integer,将其封装在 Optional 对象中返回
return Integer.parseInt(s);
} catch (NumberFormatException e) {return null; // 返回 null 或者抛出异样}
}
在将 String
转换为 int
时,如果无奈解析到对应的整型,该办法会抛出 NumberFormatException
异样。咱们在该办法中应用 try/catch
语句捕捉了该异样,不能应用 if
条件判断来管制一个变量的值是否为空。
这时,咱们能够应用 Optional
类,来对无奈转换的 String
时返回的非法值进行建模,因而,咱们能够对上述办法进行改良:
public static Optional<Integer> toInt(String s) {
try {
// 如果 String 能转换为对应的 Integer,将其封装在 Optional 对象中返回
return Optional.of(Integer.parseInt(s));
} catch (NumberFormatException e) {return Optional.empty(); // 否则返回一个空的 Optional 对象
}
}
这种返回 Optional
的形式实用很多办法,咱们只须要获取被 Optional
包装的值的实例即可。
map
Optional
提供了 map
办法用于从对象中提取信息,它的工作原理如下:
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
操作会将提供的函数利用于流的每个元素。咱们能够把 Optional
对象看成一种非凡的汇合数据,它至少蕴含一个元素。如果 Optional
蕴含一个值,那通过实现了 Function
接口的 Lambda 表达式对值进行转换。如果不相熟 Function
接口,能够参考这篇文章。map
办法示例如下:
class Car {
private String name;
private String type;
... 省略 getter 与 setter...
}
Optional<Car> optional = Optional.ofNullable(car);
Optional<String> name = optional.map(Car::getName);
flatMap
咱们能够应用 map
办法来从被 Optional
类包装的 Person
类中获取 Car
的名称:
class Person {
private Optional<Car> car;
public Person(Car car) {this.car = Optional.of(car);
}
... 省略 getter 与 setter...
}
Person person = new Person(new Car());
Optional<Person> optPerson = Optional.of(person);
Optional<String> name = optPerson.map(Person::getCar)
.map(Car::getName);
可怜的是,这段代码无奈通过编译。为什么呢?optPerson
是 Optional<Person>
类型的变量,调用 map
办法应该没有问题。但 getCar
返回的是一个 Optional<Car>
类型的对象,这意味着 map
操作的后果是一个 Optional<Optional<Car>>
类型的对象。因而,它对 getName
的调用是非法的,因为最外层的 optional
对象蕴含了另一个 optional
对象的值,而它当然不会反对 getName
办法。
所以,咱们应用 flatMap
办法。该办法承受一个函数作为参数,这个函数的返回值是另一个流。
public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) {Objects.requireNonNull(mapper);
if (!isPresent()) {return empty();
} else {@SuppressWarnings("unchecked")
Optional<U> r = (Optional<U>) mapper.apply(value);
return Objects.requireNonNull(r);
}
}
参照 map
函数,应用 flatMap
重写上述的示例:
Optional<String> name = optPerson.flatMap(Person::getCar).map(Car::getName);
filter
有时候咱们须要对 Optional
中的值进行过滤,取得咱们须要的后果,咱们就能够应用 filter
办法:
public Optional<T> filter(Predicate<? super T> predicate) {Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();}
该办法承受 Predicate
谓词作为参数。如果 Optional
对象的值存在,并且合乎谓词的条件,即操作后果为 true
,filter
办法不做任何扭转并返回其值;否则就将该值过滤掉并返回一个空的 Optional
对象。
Optional<String> optionalS = Optional.of("13846901234");
optionalS = optionalS.filter(s -> s.contains("138"));
/**
* 上述 `filter` 办法满足条件能够返回同一个 Optional,否则返回空 Optional
*/
总结
Java 8 引入的 java.util.Optional<T>
让咱们以函数式编程的形式解决 null
,避免空指针异样;并反对多种形式用于操作值,比方:map
、flatMap
和 filter
,这样能够摈弃嵌套的 if-else
代码块,设计更好的 API,代码的可读性也大大提高,然而如果在域模型中应用 Optional
,因为没有实现 Serializable
接口,不能进行实例化,也不能作为类的字段。
如果想要查看更多文章,请关注公众号「海人的博客」