从Java8开始,在java.util包下新增了Optional类、及对应基本类型的OptionalDouble类、OptionalInt类、OptionalLong类,引入这些类的目的是为了更好的解决令人头疼的空指针异常问题。Optional类可能包含或不包含非空值的容器对象,如果值存在则调用isPresent()方法会返回true,调用get()方法会返回该对象,正是因Optional提供很多有用的方法,这样我们就不用显式进行空值检测了。
用demo演示下Optional容器的相关API用法(先定义一个Milk类):
/**
* Java8 新的容器类Optional Demo Milk实体类
*
* @createTime 2020/10/13 16:59
* @author weixiangxiang
*/
class Milk implements Serializable{
public String milkId;
public String milkName;
public String milkNbr;
public Double milkPrice;
public Milk() {}
public Milk(String milkId, String milkName, String milkNbr, Double milkPrice) {
this.milkId = milkId;
this.milkName = milkName;
this.milkNbr = milkNbr;
this.milkPrice = milkPrice;
}
/** get/set省略... */
@Override
public String toString() {
return "Milk{" +
"milkId='" + milkId + '\'' +
", milkName='" + milkName + '\'' +
", milkNbr='" + milkNbr + '\'' +
", milkPice=" + milkPrice +
'}';
}
}
1、创建Optional的实例
有三种创建方式:empty()、of()、ofNullable(),均为静态方法。
public class XxxTest {
/** 日志记录器 */
private static Logger logger = LoggerFactory.getLogger(XxxTest.class);
public static void main(String[] args) {
List<Milk> milkList = new ArrayList<>();
Milk milk1 = new Milk("1", "蒙牛纯牛奶", "mn10001", 4.2);
Milk milk2 = new Milk("2", "蒙牛儿童奶", "mn10002", 6.0);
Milk milk3 = null;
milkList.add(milk1);
// 返回一个空的 Optional实例
Optional op1 = Optional.empty();
// 返回具有 Optional的当前非空值的Optional
Optional<Milk> op2 = Optional.of(milk2);
// 返回一个 Optional指定值的Optional,如果非空,则返回一个空的 Optional
Optional<Milk> op3 = Optional.ofNullable(milk3);
}
}
2、取值、判断、默认
Optional类的取值方法get()、OptionalDouble类的取值方法getAsDouble()、OptionalInt类的取值方法getAsInt()、OptionalILong类的取值方法getAsLong(),当Optional容器有值,则返回值,反之则抛出NoSuchElementException异常。
为了防止异常,取值前应先进行值存在判断:isPresent() 或 ifPresent(Consumer<? super T> consumer) 或ifPresent(DoubleConsumer consumer) 或ifPresent(IntConsumer consumer) 或ifPresent(LongConsumer consumer)。isPresent() 返回true表示Optional容器内存在值,ifPresent则让指定的消费者Consumer去接收值(存在值去处理,不存在值不做任何处理)。
当然获取值为null时,也可以指定默认结果:orElse(默认值)、orElseGet(Supplier other) 或orElseGet(DoubleSupplier other) 或orElseGet(IntSupplier other) 或orElseGet(LongSupplier other)、orElseThrow(Supplier<X> exceptionSupplier) 。orElse()直接指定默认值,orElseGet()调用指定的支持者Supplier处理并返回其结果,orElseThrow()指定抛出的异常(这个方法是Java10新增的)。
public class XxxTest {
/** 日志记录器 */
private static Logger logger = LoggerFactory.getLogger(XxxTest.class);
public static void main(String[] args) {
// op3出现异常:NoSuchElementException
// logger.info("op2:" + op2.get() + ",op3:" + op3.get());
// 不存在值时,赋默认值null
logger.info("op2:" + op2.get() + ",op3:" + op3.orElse(null));
// 不存在值时,赋其他对象
logger.info("op2:" + op2.get() + ",op3:" + op3.orElseGet(Milk::new));
// 不存在值时,抛指定异常
logger.info("op2:" + op2.get() + ",op3:" + op3.orElseThrow(NullPointerException::new));
}
}
3、与Stream流相关的API
Optional类有三个关联的API:filter()、map()、flatMap(),OptionalDouble类、OptionalInt类、OptionalLong类则不存在!!!
- public Optional filter(Predicate predicate):根据设置的条件进行过滤操作。
- public <U> Optional<U> map(Function<? super T,? extends U> mapper):进行映射操作,元素到元素。
- public <U> Optional<U> flatMap(Function<? super T,Optional<U>> mapper):进行映射操作,元素到流。
public class XxxTest {
/** 日志记录器 */
private static Logger logger = LoggerFactory.getLogger(XxxTest.class);
public static void main(String[] args) {
List<Milk> milkList = new ArrayList<>();
Milk milk1 = new Milk("1", "蒙牛纯牛奶", "mn10001", 4.2);
Milk milk2 = new Milk("2", "伊利纯牛奶", "mn10002", 4.5);
Milk milk3 = new Milk("3", "蒙牛儿童奶", "mn10003", 5.0);
Milk milk4 = new Milk("4", "蒙牛酸奶", "mn10004", 3.5);
milkList.add(milk1);
milkList.add(milk2);
milkList.add(milk3);
milkList.add(milk4);
// 过滤操作:将milkList的milkName字段中含有"纯牛奶"的对象过滤出来
Optional<Milk> filterMilk = milkList.stream().filter(milk -> milk.getMilkName().contains("纯牛奶")).findAny();
if (filterMilk.isPresent()) {
logger.info(filterMilk.get().toString()); // Milk{milkId='1', milkName='蒙牛纯牛奶', milkNbr='mn10001', milkPice=4.2}
}
// 映射操作(元素 -> 元素):将milkList的milkPrice字段都扩大2倍后输出
Optional<Double> mapOptional = milkList.stream().map(milk -> milk.getMilkPrice() * 2).findFirst();
if (mapOptional.isPresent()) {
logger.info(mapOptional.get().toString()); // 8.4
}
// 映射操作(元素 -> 流):将milkList的milkNbr字段都追加字符串p后输出
Optional<StringBuffer> flatMapMilk = milkList.stream().flatMap(milk -> Stream.of(new StringBuffer(milk.getMilkNbr()).append("p"))).findFirst();
flatMapMilk.ifPresent(m ->logger.info(m.toString())); //mn10001p
}
}
注意的问题:
过滤操作中满足条件的对象有2个,结果却只能操作其中一个对象,原因是示例中的List集合在使用stream进行过滤,映射等操作后,直接findXxx()获取了Optional对象,这里并没有进行终端完结操作(如用.collect()方法、foreach()方法等结尾),所以只能对List集合中符合条件的第一个对象进行中间操作了!!!
4、其他
Optional类及OptionalDouble类、OptionalInt类、OptionalLong类都重写了equals()、hashCode()、toString()等方法。
小结一下:
至此,参考Optional类的API进行了上述分类和总结,可以看到Optional类极大方便了开发中对NPE问题的控制,实用而不失优雅的解决了空指针异常。不积硅步无以至千里,点滴付出终将有所收获,共同进步 ~