概述
Optional 类主要解决的问题是臭名昭著的空指针异常(NullPointerException),提供了一些的方法代替过去的if-else处理逻辑,并与Stream流结合提供一致性的函数式编程.
注意: Optional 不支持Java本身提供的序列化与反序列化机制,也就是RPC不能使用Java提供的序列化机制
官方介绍
/**
* A container object which may or may not contain a non-null value.
* If a value is present, {@code isPresent()} will return {@code true} and
* {@code get()} will return the value.
*
* <p>Additional methods that depend on the presence or absence of a contained
* value are provided, such as {@link #orElse(java.lang.Object) orElse()}
* (return a default value if value not present) and
* {@link #ifPresent(java.util.function.Consumer) ifPresent()} (execute a block
* of code if the value is present).
*
* <p>This is a <a href="../lang/doc-files/ValueBased.html">value-based</a>
* class; use of identity-sensitive operations (including reference equality
* ({@code ==}), identity hash code, or synchronization) on instances of
* {@code Optional} may have unpredictable results and should be avoided.
*
* @since 1.8
一个容器对象,可能有也可能没有非空值,如果值存在,isPresent()返回true,如果没有值,则对象被当成空,isPresent()返回false;
更多的方法依赖于容器中是否含有值,比如orElse(返回一个默认值当没有值)
ifPresent(Consumer c) 是当值存在的时候,执行一个动作;
这是一个基于值的类,使用标识敏感的操作,包含 比较引用的 == , hashcode , synchronization 针对一个Optional对象,可能有无法预料的结果,然后应该避免这类操作。
编写API的注意点:
Optional最初被用来设计为方法的返回值,当明确需要代表没有值的情况。
返回null,可能出错;而返回Optional对象不是一个null对象,它总是指向一个Optional对象实例。
*/
方法概览
源码
public final class Optional<T> {
/**
* Common instance for {@code empty()}.
*/
private static final Optional<?> EMPTY = new Optional<>();
/**
* If non-null, the value; if null, indicates no value is present
*/
private final T value;
/**
* Constructs an empty instance.
*
* @implNote Generally only one empty instance, {@link Optional#EMPTY},
* should exist per VM.
*/
private Optional() {
this.value = null;
}
/**
* Returns an empty {@code Optional} instance. No value is present for this
* Optional.
*
* @apiNote Though it may be tempting to do so, avoid testing if an object
* is empty by comparing with {@code ==} against instances returned by
* {@code Option.empty()}. There is no guarantee that it is a singleton.
* Instead, use {@link #isPresent()}.
*
* @param <T> Type of the non-existent value
* @return an empty {@code Optional}
*/
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
/**
* Constructs an instance with the value present.
*
* @param value the non-null value to be present
* @throws NullPointerException if value is null
*/
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
/**
* Returns an {@code Optional} with the specified present non-null value.
*
* @param <T> the class of the value
* @param value the value to be present, which must be non-null
* @return an {@code Optional} with the value present
* @throws NullPointerException if value is null
*/
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
/**
* Returns an {@code Optional} describing the specified value, if non-null,
* otherwise returns an empty {@code Optional}.
*
* @param <T> the class of the value
* @param value the possibly-null value to describe
* @return an {@code Optional} with a present value if the specified value
* is non-null, otherwise an empty {@code Optional}
*/
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
/**
* If a value is present in this {@code Optional}, returns the value,
* otherwise throws {@code NoSuchElementException}.
*
* @return the non-null value held by this {@code Optional}
* @throws NoSuchElementException if there is no value present
*
* @see Optional#isPresent()
*/
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
/**
* Return {@code true} if there is a value present, otherwise {@code false}.
*
* @return {@code true} if there is a value present, otherwise {@code false}
*/
public boolean isPresent() {
return value != null;
}
/**
* If a value is present, invoke the specified consumer with the value,
* otherwise do nothing.
*
* @param consumer block to be executed if a value is present
* @throws NullPointerException if value is present and {@code consumer} is
* null
*/
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}
/**
* If a value is present, and the value matches the given predicate,
* return an {@code Optional} describing the value, otherwise return an
* empty {@code Optional}.
*
* @param predicate a predicate to apply to the value, if present
* @return an {@code Optional} describing the value of this {@code Optional}
* if a value is present and the value matches the given predicate,
* otherwise an empty {@code Optional}
* @throws NullPointerException if the predicate is null
*/
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
/**
* If a value is present, apply the provided mapping function to it,
* and if the result is non-null, return an {@code Optional} describing the
* result. Otherwise return an empty {@code Optional}.
*
* @apiNote This method supports post-processing on optional values, without
* the need to explicitly check for a return status. For example, the
* following code traverses a stream of file names, selects one that has
* not yet been processed, and then opens that file, returning an
* {@code Optional<FileInputStream>}:
*
* <pre>{@code
* Optional<FileInputStream> fis =
* names.stream().filter(name -> !isProcessedYet(name))
* .findFirst()
* .map(name -> new FileInputStream(name));
* }</pre>
*
* Here, {@code findFirst} returns an {@code Optional<String>}, and then
* {@code map} returns an {@code Optional<FileInputStream>} for the desired
* file if one exists.
*
* @param <U> The type of the result of the mapping function
* @param mapper a mapping function to apply to the value, if present
* @return an {@code Optional} describing the result of applying a mapping
* function to the value of this {@code Optional}, if a value is present,
* otherwise an empty {@code Optional}
* @throws NullPointerException if the mapping function is null
*/
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));
}
}
/**
* If a value is present, apply the provided {@code Optional}-bearing
* mapping function to it, return that result, otherwise return an empty
* {@code Optional}. This method is similar to {@link #map(Function)},
* but the provided mapper is one whose result is already an {@code Optional},
* and if invoked, {@code flatMap} does not wrap it with an additional
* {@code Optional}.
*
* @param <U> The type parameter to the {@code Optional} returned by
* @param mapper a mapping function to apply to the value, if present
* the mapping function
* @return the result of applying an {@code Optional}-bearing mapping
* function to the value of this {@code Optional}, if a value is present,
* otherwise an empty {@code Optional}
* @throws NullPointerException if the mapping function is null or returns
* a null result
*/
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Objects.requireNonNull(mapper.apply(value));
}
}
/**
* Return the value if present, otherwise return {@code other}.
*
* @param other the value to be returned if there is no value present, may
* be null
* @return the value, if present, otherwise {@code other}
*/
public T orElse(T other) {
return value != null ? value : other;
}
/**
* Return the value if present, otherwise invoke {@code other} and return
* the result of that invocation.
*
* @param other a {@code Supplier} whose result is returned if no value
* is present
* @return the value if present otherwise the result of {@code other.get()}
* @throws NullPointerException if value is not present and {@code other} is
* null
*/
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
/**
* Return the contained value, if present, otherwise throw an exception
* to be created by the provided supplier.
*
* @apiNote A method reference to the exception constructor with an empty
* argument list can be used as the supplier. For example,
* {@code IllegalStateException::new}
*
* @param <X> Type of the exception to be thrown
* @param exceptionSupplier The supplier which will return the exception to
* be thrown
* @return the present value
* @throws X if there is no value present
* @throws NullPointerException if no value is present and
* {@code exceptionSupplier} is null
*/
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
/**
* Indicates whether some other object is "equal to" this Optional. The
* other object is considered equal if:
* <ul>
* <li>it is also an {@code Optional} and;
* <li>both instances have no value present or;
* <li>the present values are "equal to" each other via {@code equals()}.
* </ul>
*
* @param obj an object to be tested for equality
* @return {code true} if the other object is "equal to" this object
* otherwise {@code false}
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Optional)) {
return false;
}
Optional<?> other = (Optional<?>) obj;
return Objects.equals(value, other.value);
}
/**
* Returns the hash code value of the present value, if any, or 0 (zero) if
* no value is present.
*
* @return hash code value of the present value or 0 if no value is present
*/
@Override
public int hashCode() {
return Objects.hashCode(value);
}
/**
* Returns a non-empty string representation of this Optional suitable for
* debugging. The exact presentation format is unspecified and may vary
* between implementations and versions.
*
* @implSpec If a value is present the result must include its string
* representation in the result. Empty and present Optionals must be
* unambiguously differentiable.
*
* @return the string representation of this instance
*/
@Override
public String toString() {
return value != null
? String.format("Optional[%s]", value)
: "Optional.empty";
}
}
方法介绍
1.构造Optional
//构造方法1
//获取一个空容器empty()
@Test(expected = NoSuchElementException.class)
public void whenCreateEmptyOptional_thenNull() {
Optional<User> emptyOpt = Optional.empty();
emptyOpt.get();// 没有值 将会抛出异常
}
//构造方法2
//需要提前检查NPE 使用of()方法
@Test(expected = NullPointerException.class)
public void test_ofNullable() {
String name = "John";
Optional<Object> opt = Optional.ofNullable(name);
}
//构造方法3
//不管情况直接构造 使用ofNullable()方法
@Test
public void test_ofNullable() {
String name = "John";
Optional<Object> opt = Optional.ofNullable(name);
}
2.取值
//get()
@Test(expected = NoSuchElementException.class)
public void whenCreateEmptyOptional_thenNull() {
Optional<User> emptyOpt = Optional.ofNullable(null);
emptyOpt.get();// 没有值 将会抛出异常
}
//orElse() 如果为空返回默认值 传null就返回null
@Test
public void test_orElse() {
String name = "1";
String name1 = "John";
String s = Optional.ofNullable(name).orElse(name1);
System.out.println(s);
}
//orElseGet() 支持函数式编程
//这个方法会在有值的时候返回值,如果没有值,它会执行作为参数传入的 Supplier(供应者) 函数式接口,并将返回其执行结果
@Test
public void test_orElseGet() {
User u1 = new User("张三", 11);
User u2 = null;
User s = Optional.ofNullable(u2).orElseGet(() -> u1);
System.out.println(s);
}
orElse() 和 orElseGet() 的不同之处
@Test
public void givenEmptyValue_whenCompare_thenOk() {
User user = null;
System.out.println("Using orElse");
User result = Optional.ofNullable(user).orElse(createNewUser());
System.out.println("Using orElseGet");
User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
}
private User createNewUser() {
System.out.println("Creating New User");
return new User("extra@gmail.com", 123);
}
执行结果:
Using orElse
Creating New User
Using orElseGet
Creating New User
@Test
public void givenPresentValue_whenCompare_thenOk() {
User user = new User("john@gmail.com", 123);
System.out.println("Using orElse");
User result = Optional.ofNullable(user).orElse(createNewUser());
System.out.println("Using orElseGet");
User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
}
执行结果:
Using orElse
Creating New User
Using orElseGet
返回异常
@Test(expected = IllegalArgumentException.class)
public void whenThrowException_thenOk() {
User user = null;
User result = Optional.ofNullable(user)
.orElseThrow( () -> new IllegalArgumentException());
}
检查处理
isPresent()
@Test
public void test_isPresent(){
Optional<String> s = Optional.ofNullable(null);
System.out.println(s.isPresent());
}
ifPresent()
//该方法除了执行检查,还接受一个Consumer(消费者) 参数,如果对象不是空的,就对执行传入的 Lambda 表达式
@Test
public void test_ifPresent(){
String s1 = "q";
Optional<String> s = Optional.ofNullable(s1);
s.ifPresent(value -> System.out.println(value = value.toUpperCase()));
System.out.println(s.get());
}
转换值
//map()
如果有值返回Optional对象,并执行函数体
@Test
public void test_map() {
User user = new User();
Optional<User> s = Optional.of(user).map((x) -> {
x.getName().equals("zhangsn");
return x;
});
System.out.println(s.get());
}
//flatMap()
@Test
public void test_flatMap() {
User user = new User();
Address address = Optional.ofNullable(user).flatMap(x -> x.getAddress()).orElse(new Address());
System.out.println(address);
}
过滤
//除了转换值之外,Optional 类也提供了按条件“过滤”值的方法。
//filter() 接受一个 Predicate 参数,返回测试结果为 true 的值。如果测试结果为 false,会返回一个空的 Optional。
User user = new User("anna@gmail.com", 123);
user = null;
Optional<User> result = Optional.ofNullable(user)
.filter(u -> u.getName() != null && u.getName().contains("@"));
System.out.println(result.get());
最佳实践
有一种诱惑是调用get()来获取其中的值。我们都知道普通JavaBean的getter / setters。并且期望如果我们调用get...()我们就会得到一些东西。当调用普通bean的getter时,你永远不会得到任何抛出的异常。但是,如果调用在optional上调用get方法,并且该选项内部为空时,则会抛出异常NoSuchElementException。
这些方法应该被称为getOrThorwSomeHorribleError(),因此第一和第二条规则:
1、不要将null赋给Optional
2、避免使用Optional.get()。如果你不能证明存在可选项,那么永远不要调用get()。
使用orElse(), orElseGet(), orElseThrow().获得你的结果。
可以重构一下代码
String variable = fetchSomeVaraible();
if(variable == null){
1. throw new IllegalStateException("No such variable");
2. return createVariable();
3. return "new variable";
} else {
...
100 lines of code
...
}
重构到
1.
Optional<String> variableOpt = fetchOptionalSomeVaraible();
String variable = variableOpt.orElseThrow(() -> new Exeption(""))
... 100 lines of code ...
2.
Optional<String> variableOpt = fetchOptionalSomeVaraible();
String variable = variableOpt.orElseGet(() -> createVariable())
... 100 lines of code ...
3.
Optional<String> variableOpt = fetchOptionalSomeVaraible();
String variable = variableOpt.orElse("new variable")
... 100 lines of code ...
注意,orElse(..)是急切计算,意味着下面代码:
Optional<Dog> optionalDog = fetchOptionalDog();
optionalDog
.map(this::printUserAndReturnUser)
.orElse(this::printVoidAndReturnUser)
如果值存在则将执行两个方法,如果值不存在,则仅执行最后一个方法。为了处理这些情况,我们可以使用方法orElseGet(),它将supplier 作为参数,并且是惰性计算的。
3、不要在字段,方法参数,集合中使用Optional。
下面是将thatField直接赋值给了类字段:
public void setThatField(Optional <ThatFieldType> thatField){
this.thatField = thatField;
}
改为
setThatField(Optional.ofNullable(thatField));
4、只有每当结果不确定时,使用Optional作为返回类型。
说实话,这是使用Optional的唯一好地方。
我们的目的是为库方法的返回类型提供一种有限的机制,其中需要一种明确的方式来表示“无结果”,并且对于这样的方法使用null 绝对可能导致错误。
5、不要害怕使用map和filter。
有一些值得遵循的一般开发实践称为SLA-p:Single Layer of Abstraction字母的第一个大写。
下面是需要被重构代码:
Dog dog = fetchSomeVaraible();
String dogString = dogToString(dog);
public String dogToString(Dog dog){
if(dog == null){
return "DOG'd name is : " + dog.getName();
} else {
return "CAT";
}
}
重构到:
Optional<Dog> dog = fetchDogIfExists();
String dogsName = dog
.map(this::convertToDog)
.orElseGet(this::convertToCat)
public void convertToDog(Dog dog){
return "DOG'd name is : " + dog.getName();
}
public void convertToCat(){
return "CAT";
}
Filter是有用的折叠语法:
Dog dog = fetchDog();
if(optionalDog != null && optionalDog.isBigDog()){
doBlaBlaBla(optionalDog);
}
上面代码可以被重构为:
Optional<Dog> optionalDog = fetchOptionalDog();
optionalDog
.filter(Dog::isBigDog)
.ifPresent(this::doBlaBlaBla)
6、不要为了链方法而使用optional。
使用optional 时要注意的一件事是链式方法的诱惑。当我们像构建器模式一样链接方法时,事情可能看起来很漂亮:)。但并不总是等于更具可读性。所以不要这样做:
Optional
.ofNullable(someVariable)
.ifPresent(this::blablabla)
它对性能不利,对可读性也不好。我们应尽可能避免使用null引用。
7、使所有表达式成为单行lambda
这是更普遍的规则,我认为也应该应用于流。但这篇文章是关于optional 。使用Optional 重要点是记住等式左边和右边一样重要:
Optional
.ofNullable(someVariable)
.map(variable -> {
try{
return someREpozitory.findById(variable.getIdOfOtherObject());
} catch (IOException e){
LOGGER.error(e);
throw new RuntimeException(e);
}})
.filter(variable -> {
if(variable.getSomeField1() != null){
return true;
} else if(variable.getSomeField2() != null){
return false;
} else {
return true;
}
})
.map((variable -> {
try{
return jsonMapper.toJson(variable);
} catch (IOException e){
LOGGER.error(e);
throw new RuntimeException(e);
}}))
.map(String::trim)
.orElseThrow(() -> new RuntimeException("something went horribly wrong."))
上面那么冗长代码块可以使用方法替代:
Optional
.ofNullable(someVariable)
.map(this::findOtherObject)
.filter(this::isThisOtherObjectStale)
.map(this::convertToJson)
.map(String::trim)
.orElseThrow(() -> new RuntimeException("something went horribly wrong."));
其他
Java 9 为 Optional 类添加了三个方法:or()、ifPresentOrElse() 和 stream()。
-
or 方法 or 方法,如果值存在,返回 Optional 指定的值,否则返回一个预设的值。
-
stream 方法 stream 方法的作用就是将 Optional 转为一个 Stream,如果该 Optional 中包含值,那么就返回包含这个值的 Stream,否则返回一个空的 Stream。
-
ifPresentOrElse 方法 在 java8 中,使用 ifPresent 方法来替代传统的 if(user != null)判断,未提供支持 if (user != null) else { // xxx}的操作,也就是在Java8中Optional 类 ifPresent 方法没有对else的操作提供支持。 在java9 中 ifPresentOrElse 方法就相当于是 if (user != null) else { // xxx}的操作。