什么是Optional【JDK 8特性】

码上飞
• 阅读 1207

一、基础铺垫

我们都知道JDK 8最重要的新特性是Lambda表达式,这个可以让我们简化非常多的代码编写,不知道大家会使用了没有。这里我简单跟大家来回顾一下~

1.1Lambda简化代码例子

下面就以几个例子来看看Lambda表达式是怎么简化我们代码的编写的。

首先我们来看看创建线程

public static void main(String\[\] args) {  
 // 用匿名内部类的方式来创建线程  
 new Thread(new Runnable() {  
 @Override  
 public void run() {  
 System.out.println("公众号:Java3y---回复1进群交流");  
 }  
 });  
  
 // 使用Lambda来创建线程  
 new Thread(() -> System.out.println("公众号:Java3y---回复1进群交流"));  
}

再来看看遍历Map集合:

public static void main(String\[\] args) {  
 Map<String, String> hashMap = new HashMap<>();  
 hashMap.put("公众号", "Java3y");  
 hashMap.put("交流群", "回复1");  
  
 // 使用增强for的方式来遍历hashMap  
 for (Map.Entry<String, String> entry : hashMap.entrySet()) {  
 System.out.println(entry.getKey()+":"+entry.getValue());  
 }  
  
 // 使用Lambda表达式的方式来遍历hashMap  
 hashMap.forEach((s, s2) -> System.out.println(s + ":" + s2));  
}

在List中删除某个元素

public static void main(String\[\] args) {  
  
 List<String> list = new ArrayList<>();  
 list.add("Java3y");  
 list.add("3y");  
 list.add("光头");  
 list.add("帅哥");  
  
 // 传统的方式删除"光头"的元素  
 ListIterator<String> iterator = list.listIterator();  
 while (iterator.hasNext()) {  
 if ("光头".equals(iterator.next())) {  
 iterator.remove();  
 }  
 }  
  
 // Lambda方式删除"光头"的元素  
 list.removeIf(s -> "光头".equals(s));  
  
 // 使用Lambda遍历List集合  
 list.forEach(s -> System.out.println(s));  
}

从上面的例子我们可以看出,Lambda表达式的确是可以帮我们简化代码的。

1.1函数式接口

使用Lambda表达式,其实都是建立在函数式接口上的。我们看看上面的代码的接口:

创建多线程的Runnable接口:

@FunctionalInterface  
public interface Runnable {  
 public abstract void run();  
}

遍历HashMap的BiConsumer接口:

@FunctionalInterface  
public interface BiConsumer<T, U\> {  
 void accept(T t, U u);  
 default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after) {  
 Objects.requireNonNull(after);  
 return (l, r) -> {  
 accept(l, r);  
 after.accept(l, r);  
 };  
 }  
}

在List中删除元素的Predicate接口:

@FunctionalInterface  
public interface Predicate<T\> {  
  
 boolean test(T t);  
  
 default Predicate<T> and(Predicate<? super T> other) {  
 Objects.requireNonNull(other);  
 return (t) -> test(t) && other.test(t);  
 }  
 default Predicate<T> negate() {  
 return (t) -> !test(t);  
 }  
 default Predicate<T> or(Predicate<? super T> other) {  
 Objects.requireNonNull(other);  
 return (t) -> test(t) || other.test(t);  
 }  
 static <T> Predicate<T> isEqual(Object targetRef) {  
 return (null == targetRef)  
 ? Objects::isNull  
 : object -> targetRef.equals(object);  
 }  
}

函数式接口的特点:由@FunctionalInterface注解标识,接口有且仅有一个抽象方法!

1.2Lambda简单讲解

或许我们一开始看到Lambda的时候,发现Lambda表达式的语法有点奇葩,甚至有点看不懂。没事,这里3y给大家用图的形式画一画:

什么是Optional【JDK 8特性】

Lambda表达式组成

以Runnable接口来举例:

什么是Optional【JDK 8特性】

Lambda表达式很简单!

再不济,我们在用IDE的时候,可以提示出Lambda表达式的语法的,这样可以帮我们快速上手Lambda表达式:

什么是Optional【JDK 8特性】

IDEA提示Lambda表达式

说白了,我们使用Lambda表达式的架子是这样的()->{},具体的时候看看函数式接口的抽象方法要求就可以了,再不济就使用IDE智能提示。

1.3泛型回顾

比如说public<U> Optional<U> map(Function<? super T, ? extends U> mapper)这个声明,你看懂了吗?

// 接口@FunctionalInterfacepublic interface Function<T, R> {    R apply(T t);}

在泛型的上限和下限中有一个原则:PECS(Producer Extends Consumer Super)

  • 带有子类限定的可以从泛型读取【也就是--->(? extend T)】-------->Producer Extends
  • 带有超类限定的可以从泛型写入【也就是--->(? super T)】-------->Consumer Super

解析:传入的参数是泛型 T 或者其父类,返回值是U或其子类。

具体可参考:

二、Optional类

一句话介绍Optional类:使用JDK8的Optional类来防止NPE(空指针异常)问题。

接下来我们看看文档是怎么说的:

A container object which may or may not contain a non-null value.Additional methods that depend on the presence or absence of a contained value are provided

它是一个容器,装载的对象不能是null,,可以是非null,提供了一系列的方法供我们判断该容器里的对象是否存在(以及后续的操作)。

Optional类的方法结构图:

什么是Optional【JDK 8特性】

Optional类的方法结构图

2.1创建Optional容器

我们先来看看Optional的属性以及创建Optional容器的方法:

    // 1、创建出一个Optional容器,容器里边并没有装载着对象  
 private static final Optional<?> EMPTY = new Optional<>();  
  
 // 2、代表着容器中的对象  
 private final T value;  
  
 // 3、私有构造方法  
 private Optional() {  
 this.value = null;  
 }  
  
 // 4、得到一个Optional容器,Optional没有装载着对象  
 public static<T> Optional<T> empty() {  
 @SuppressWarnings("unchecked")  
 Optional<T> t = (Optional<T>) EMPTY;  
 return t;  
 }  
  
 // 5、私有构造方法(带参数),参数就是具体的要装载的对象,如果传进来的对象为null,抛出异常  
 private Optional(T value) {  
 this.value = Objects.requireNonNull(value);  
 }  
  
 // 5.1、如果传进来的对象为null,抛出异常  
 public static <T> T requireNonNull(T obj) {  
 if (obj == null)  
 throw new NullPointerException();  
 return obj;  
 }  
  
  
 // 6、创建出Optional容器,并将对象(value)装载到Optional容器中。  
 // 传入的value如果为null,抛出异常(调用的是Optional(T value)方法)  
 public static <T> Optional<T> of(T value) {  
 return new Optional<>(value);  
 }  
  
 // 创建出Optional容器,并将对象(value)装载到Optional容器中。  
 // 传入的value可以为null,如果为null,返回一个没有装载对象的Optional对象  
 public static <T> Optional<T> ofNullable(T value) {  
 return value == null ? empty() : of(value);  
 }

所以可以得出创建Optional容器有两种方式:

  • 调用ofNullable()方法,传入的对象可以为null
  • 调用of()方法,传入的对象不可以为null,否则抛出NullPointerException

下面我们简单就可以看看用法了:

现在我有一个User对象,这里用到了Lombok,有兴趣的同学可去学学了解一下:两个月的Java实习结束,继续努力

import lombok.Data;  
@Data  
public class User {  
  
 private Integer id;  
 private String name;  
 private Short age;  
}

测试:

public static void main(String\[\] args) {  
  
 User user = new User();  
 User user1 = null;  
  
 // 传递进去的对象不可以为null,如果为null则抛出异常  
 Optional<User> op1 = Optional.of(user1);  
  
 // 传递进去的对象可以为null,如果为null则返回一个没有装载对象的Optional容器  
 Optional<User> op2 = Optional.ofNullable(user);  
}

什么是Optional【JDK 8特性】

结果

2.2Optional容器简单的方法

// 得到容器中的对象,如果为null就抛出异常  
public T get() {  
 if (value == null) {  
 throw new NoSuchElementException("No value present");  
 }  
 return value;  
}  
  
// 判断容器中的对象是否为null  
public boolean isPresent() {  
 return value != null;  
}  
  
// 如果容器中的对象存在,则返回。否则返回传递进来的参数  
public T orElse(T other) {  
 return value != null ? value : other;  
}

这三个方法是Optional类比较常用的方法,并且是最简单的。(因为参数不是函数式接口)

下面我们继续看看用法:

public static void main(String\[\] args) {  
  
 User user = new User();  
 User user1 = null;  
  
 Optional<User> op1 = Optional.ofNullable(user);  
 System.out.println(op1.isPresent());  
 System.out.println(op1.get());  
 System.out.println(op1.orElse(user1));  
  
 }

结果很明显,因为我们的user是不为null的:

什么是Optional【JDK 8特性】

结果

我们调换一下顺序看看:

public static void main(String\[\] args) {  
  
 User user = new User();  
 User user1 = null;  
  
 Optional<User> op1 = Optional.ofNullable(user1);  
 System.out.println(op1.isPresent());  
 System.out.println(op1.orElse(user));  
 System.out.println(op1.get());  
  
}

什么是Optional【JDK 8特性】

结果

2.3Optional容器进阶用法

当然了,我们到目前为止看起来Optional类好像就这么一回事了,这样代码写起来还不如我自己判断null呢…

我们对比一下:

对比

我们可以发现,手动判断是否为null好像还更方便简洁一点呢。

所以,我们带函数式接口的方法登场了!

2.3.1ifPresent方法

首先来看看ifPresent(Consumer<? super T> consumer)方法

public void ifPresent(Consumer<? super T> consumer) {  
 if (value != null)  
 consumer.accept(value);  
}  
  
@FunctionalInterface  
public interface Consumer<T\> {  
 void accept(T t);  
}

如果容器中的对象存在,则调用accept方法,比如说:

public static void main(String\[\] args) {  
  
 User user = new User();  
 user.setName("Java3y");  
 test(user);  
}  
  
public static void test(User user) {  
  
 Optional<User> optional = Optional.ofNullable(user);  
  
 // 如果存在user,则打印user的name  
 optional.ifPresent((value) -> System.out.println(value.getName()));  
  
 // 旧写法  
 if (user != null) {  
 System.out.println(user.getName());  
 }  
}

2.3.2orElseGet和orElseThrow方法

直接看源码:

// 如果对象存在,则直接返回,否则返回由Supplier接口的实现用来生成默认值  
public T orElseGet(Supplier<? extends T> other) {  
 return value != null ? value : other.get();  
}  
  
  
@FunctionalInterface  
public interface Supplier<T\> {  
 T get();  
}  
  
  
// 如果存在,则返回。否则抛出supplier接口创建的异常  
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {  
 if (value != null) {  
 return value;  
 } else {  
 throw exceptionSupplier.get();  
 }  
}

例子:

public static void main(String\[\] args) {  
  
 User user = new User();  
 user.setName("Java3y");  
 test(user);  
}  
  
public static void test(User user) {  
  
 Optional<User> optional = Optional.ofNullable(user);  
  
 // 如果存在user,则直接返回,否则创建出一个新的User对象  
 User user1 = optional.orElseGet(() -> new User());  
  
 // 旧写法  
 if (user != null) {  
 user = new User();  
 }  
}

总的来说跟我们上面所讲的orElse()差不多,只不过它可以通过Supplier接口的实现来生成默认值。

2.3.3filter方法

直接看源码:

// 如果容器中的对象存在,并且符合过滤条件,返回装载对象的Optional容器,否则返回一个空的Optional容器  
public Optional<T> filter(Predicate<? super T> predicate) {  
 Objects.requireNonNull(predicate);  
 if (!isPresent())  
 return this;  
 else  
 return predicate.test(value) ? this : empty();  
}  
  
  
// 接口  
@FunctionalInterface  
public interface Predicate<T\> {  
  
 boolean test(T t);  
}

返回Optional对象我们就可以实现链式调用了!

例子:

public static void test(User user) {  
  
 Optional<User> optional = Optional.ofNullable(user);  
  
 // 如果容器中的对象存在,并且符合过滤条件,返回装载对象的Optional容器,否则返回一个空的Optional容器  
 optional.filter((value) -> "Java3y".equals(value.getName()));  
}

2.3.4map方法

直接看源码:

// 如果容器的对象存在,则对其执行调用mapping函数得到返回值。然后创建包含mapping返回值的Optional,否则返回空Optional。  
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));  
 }  
}  
  
  
// 接口  
@FunctionalInterface  
public interface Function<T, R\> {  
 R apply(T t);  
}

例子:

public static void test(User user) {  
  
 Optional<User> optional = Optional.ofNullable(user);  
  
 // 如果容器的对象存在,则对其执行调用mapping函数得到返回值。然后创建包含mapping返回值的Optional,否则返回空Optional。  
 optional.map(user1 -> user1.getName()).orElse("Unknown");  
}  
  
// 上面一句代码对应着最开始的老写法:  
  
public String tradition(User user) {  
 if (user != null) {  
 return user.getName();  
 }else{  
 return "Unknown";  
 }  
}

2.3.5flatMap方法

直接看源码:

// flatMap方法与map方法类似,区别在于apply函数的返回值不同。map方法的apply函数返回值是? extends U,而flatMap方法的apply函数返回值必须是Optional  
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));  
 }  
}

2.3.6总结

再来感受一下Optional的魅力

public static void main(String\[\] args) {  
 User user = new User();  
 user.setName("Java3y");  
 System.out.println(test(user));  
}  
  
// 以前的代码v1  
public static String test2(User user) {  
 if (user != null) {  
 String name = user.getName();  
 if (name != null) {  
 return name.toUpperCase();  
 } else {  
 return null;  
 }  
 } else {  
 return null;  
 }  
}  
  
// 以前的代码v2  
public static String test3(User user) {  
 if (user != null && user.getName() != null) {  
 return user.getName().toUpperCase();  
 } else {  
 return null;  
 }  
}  
  
// 现在的代码  
public static String test(User user) {  
 return Optional.ofNullable(user)  
 .map(user1 -> user1.getName())  
 .map(s -> s.toUpperCase()).orElse(null);  
}

Optional总结:

filter,map或flatMap一个函数,函数的参数拿到的值一定不是null。所以我们通过filter,map 和 flatMap之类的函数可以将其安全的进行变换,最后通过orElse系列,get,isPresent 和 ifPresent将其中的值提取出来。

其实吧,用Optional类也没有简化很多的代码,只是把NPE异常通过各种方法隐藏起来(包装了一层)。通过Lambda表达式可以让我们处理起来更加"优雅"一些。

三、最后

之前在初学的时候没在意JDK8的特性,其实JDK更新很多时候都能给我们带来不少好处的(简化代码编写,提高性能等等),所以作为一名Java程序员,还是得多学学新特性。(话说JDK9该类又有新特性了…)

点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
3年前
java 8 lambda表达式中的异常处理
java8lambda表达式中的异常处理简介java8中引入了lambda表达式,lambda表达式可以让我们的代码更加简介,业务逻辑更加清晰,但是在lambda表达式中使用的FunctionalInterface并没有很好的处理异常,因为JDK提供的这些FunctionalInterface通常都是没有抛出异常的,这意味着需要我们自
待兔 待兔
4年前
Java8 几个很好用的方法,真的很香
JDK8应该是Java中最坚挺一个版本,这个版本新增很多特性,让我们开发起来多了很多便利。不过最近Review项目代码的时候发现,虽然很多项目工程已经使用了JDK8,但是工程代码却很少使用到JDK8新特性、新方法。如果单从代码正确性上来说,老方式写法写当然没有什么问题,那唯一的缺点其实就是代码行数比较多,比较繁琐。那同样的需求,使
Stella981 Stella981
3年前
Lambda表达式中Collections的接口有哪些变化?
我们先从最熟悉的\Java集合框架(JavaCollectionsFramework,JCF)\开始说起。为引入Lambda表达式,Java8新增了java.util.function包,里面包含常用的函数接口,这是Lambda表达式的基础,Java集合框架也新增部分接口,以便与Lambda表达式对接。首先回顾一下Java集合框架的接口继承
Wesley13 Wesley13
3年前
JAVA之forEach遍历集合
JAVA之forEach遍历集合在JDK8中,根据Lambda表达式的特性还增加了一个forEach(Consumeraction)方法来遍历集合,该方法所需要的参数是一个函数式接口importjava.util.ArrayList;importjava.util.List;
Wesley13 Wesley13
3年前
Java8特性
Java8又称jdk1.8。主要新特性:Lambda表达式 −Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中。方法引用 −方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
Wesley13 Wesley13
3年前
Java8 新增特性 Lambda表达式
               聊聊Lambda  背景:    早在2014年oracle发布了jdk8,在里面增加了lambda模块。于是java程序员们又多了一种新的编程方式:函数式编程,也就是lambda表达式。    以下整理关于Lambda表达式资料(转载地址:https:/
Wesley13 Wesley13
3年前
Java 8中你可能没听过的10个新特性
lambda表达式,lambda表达式,还是lambda表达式。一提到Java8就只能听到这个,但这不过是其中的一个新功能而已,Java8还有许多新的特性——有一些功能强大的新类或者新的用法,还有一些功能则是早就应该加到Java里了。这里我准备介绍它的10个我个人认为非常值得了解的新特性。总会有一款适合你的,开始来看下吧。default方法
Stella981 Stella981
3年前
Lombok经常用,但是你知道它的原理是什么吗?
相信大家在项目中都使用过Lombok,因为能够简化我们许多的代码,但是该有的功能一点也不少。那么lombok到底是个什么呢,lombok是一个可以通过简单的注解的形式来帮助我们简化消除一些必须有但显得很臃肿的Java代码的工具,简单来说,比如我们新建了一个类,然后在其中写了几个字段,然后通常情况下我们需要手动去建立getter和setter方法啊,构造函
Wesley13 Wesley13
3年前
Java8的自定义收集器与并行
Lambda表达式是Java8最重要的新特性,基础的内容这里就不说了,让我们从收集器开始。什么是收集器就是用于收集流运算后结果的角色。例如:List<String collect  list.stream().map(TestBean::getName).collect(Collectors.toList());以上
Wesley13 Wesley13
3年前
Java笔记——Java8特性之Lambda、方法引用和Streams
转自: https://www.cnblogs.com/Fndroid/p/6087380.htmlJava8已经推出了好一段时间了,而掌握Java8的新特性也是必要的,如果要进行Spring开发,那么可以发现Spring的官网已经全部使用Java8来编写示例代码了,所以,不学就看不懂。这里涉及三个重要特性:Lambda方法引用
Wesley13 Wesley13
3年前
Java8
JDK8自带的函数式接口Function有两个默认方法andThen和compose,它们都返回Function的一个实例,可以用这两个方法把Function接口所代表的的Lambda表达式复合起来。先看个简单的例子:Function<Integer,Integerfxx1;Function<Integer