java8 Optional
一、前言
日常开发中,我们经常会遇到空指针异常(NPE)。可以说,NPE是我们最头疼的问题之一了。为了比变空指针,我们一般会加上判空操作。
如下:
1 class Teacher { 2 private Integer id; 3 private String name; 4 } 5 6 class Student { 7 private Integer id; 8 private String name; 9 private Teacher teacher; 10 }
当我们调用
1 student.getTeacher().getId()
上面这种写法,很容易产生NullPointerException异常的。为了解决这个问题,于是采用下面的写法:
1 if(student!=null){ 2 Teacher t = student.getTeacher(); 3 if(t!=null){ 4 Integer id = t.getId(); 5 } 6 }
为了简化上面的写法,我们可以利用Java8的新特性 Optional 来尽量简化代码同时高效处理NPE
二、Optional类使用解析
1、optional介绍
- Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
- Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
- Optional 类的引入很好的解决空指针异常。
2、构建Optional
构建一个Optional对象,有这些方法:
-
Optional(T value)
- empty()
- of(T value)
- ofNullable(T value)
先说明一下,Optional(T value)
,即构造函数,它是private权限的,不能由外部调用的。其余三个函数是public权限,供我们所调用。那么,Optional的本质,就是内部储存了一个真实的值,在构造的时候,就直接判断其值是否为空。好吧,这么说还是比较抽象。直接上Optional(T value)
构造函数的源码,如下图所示:
of(T value)的源码如下
1 public static <T> Optional<T> of(T value) { 2 return new Optional<>(value); 3 }
也就是说of(T value)函数内部调用了构造函数。根据构造函数的源码我们可以得出两个结论:
- 通过
of(T value)
函数所构造出的Optional对象,当Value值为空时,依然会报NullPointerException。 - 通过
of(T value)
函数所构造出的Optional对象,当Value值不为空时,能正常构造Optional对象。
除此之外呢,Optional类内部还维护一个value为null的对象,大概就是长下面这样的
1 public final class Optional<T> { 2 //省略.... 3 private static final Optional<?> EMPTY = new Optional<>(); 4 private Optional() { 5 this.value = null; 6 } 7 //省略... 8 public static<T> Optional<T> empty() { 9 @SuppressWarnings("unchecked") 10 Optional<T> t = (Optional<T>) EMPTY; 11 return t; 12 } 13 }
那么,empty()
的作用就是返回EMPTY对象。
好了铺垫了这么多,可以说ofNullable(T value)
的作用了,上源码
1 public static <T> Optional<T> ofNullable(T value) { 2 return value == null ? empty() : of(value); 3 }
相比较of(T value)
的区别就是,当value值为null时,of(T value)会报NullPointerException异常;ofNullable(T value)
不会throw Exception,ofNullable(T value)
直接返回一个EMPTY
对象。
那是不是意味着,我们在项目中只用ofNullable
函数而不用of函数呢?不是的,一个东西存在那么自然有存在的价值。当我们在运行过程中,不想隐藏NullPointerException
。而是要立即报告,这种情况下就用of函数。
3、Optional API 及源码注解
1 package java.util; 2 3 import java.util.function.Consumer; 4 import java.util.function.Function; 5 import java.util.function.Predicate; 6 import java.util.function.Supplier; 7 import java.util.stream.Stream; 8 9 /** 10 一个可能包含也可能不包含非null值的容器对象。 如果存在值,则isPresent()返回true 。 如果不存在任何值,则该对象被视为空,并且isPresent()返回false 。 11 提供了其他取决于所包含值是否存在的方法,例如orElse() (如果不存在值,则返回默认值)和ifPresent() (如果存在值,则执行操作)。 12 这是一个基于值的类; 在Optional实例上使用标识敏感的操作(包括引用等于( == ),标识哈希码或同步)可能会产生不可预测的结果,应避免使用 13 */ 14 public final class Optional<T> { 15 /** 16 * empty()通用实例 17 */ 18 private static final Optional<?> EMPTY = new Optional<>(); 19 20 /** 21 * 如果不为空,则为该值;否则为false。 如果为null,则表示不存在任何值 22 */ 23 private final T value; 24 25 /** 26 构造一个空实例。 27 impl注意: 28 通常,每个VM仅应存在一个空实例EMPTY 29 */ 30 private Optional() { 31 this.value = null; 32 } 33 34 /** 35 返回一个空的Optional实例。 此Optional没有值。 36 类型参数:<T> –不存在的值的类型 37 返回值:一个空的Optional 38 api注意: 39 尽管这样做可能很诱人,但应通过将==与Optional.empty()返回的实例进行比较来避免测试对象是否为空。 不能保证它是一个单例。 而是使用isPresent() 40 */ 41 public static<T> Optional<T> empty() { 42 @SuppressWarnings("unchecked") 43 Optional<T> t = (Optional<T>) EMPTY; 44 return t; 45 } 46 47 /** 48 使用描述的值构造一个实例。 49 参数:值–要描述的非null值 50 抛出:NullPointerException如果值为null 51 */ 52 private Optional(T value) { 53 this.value = Objects.requireNonNull(value); 54 } 55 56 /** 57 返回一个Optional描述给定的非null值。 58 参数:value –要描述的值,必须为非null 59 类型参数:<T> –值的类型 60 返回值:存在值的Optiona 61 */ 62 public static <T> Optional<T> of(T value) { 63 return new Optional<>(value); 64 } 65 66 /** 67 返回一个描述给定值的Optional ,如果不为null ,则返回一个空的Optional 。 68 参数:值–描述的可能为null值 69 类型参数:<T> –值的类型 70 返回值:一个Optional与如果指定值是非当前值null ,否则一个空Optional 71 */ 72 public static <T> Optional<T> ofNullable(T value) { 73 return value == null ? empty() : of(value); 74 } 75 76 /** 77 如果存在值,则返回该值,否则抛出NoSuchElementException 。 78 返回值:此Optional描述的非null值 79 抛出:NoSuchElementException如果不存在任何值 80 api注意:此方法的首选替代方法是orElseThrow() 。 81 */ 82 public T get() { 83 if (value == null) { 84 throw new NoSuchElementException("No value present"); 85 } 86 return value; 87 } 88 89 /** 90 如果存在值,则返回true ,否则返回false 。 91 92 返回值:如果存在值,则为true ,否则为false 93 */ 94 public boolean isPresent() { 95 return value != null; 96 } 97 98 /** 99 如果不存在值,则返回true ,否则返回false 。 100 101 返回值:如果不存在值,则为true ,否则为false 102 */ 103 public boolean isEmpty() { 104 return value == null; 105 } 106 107 /** 108 如果存在值,则使用该值执行给定的操作,否则不执行任何操作。 109 参数:动作–要执行的动作(如果存在值) 110 */ 111 public void ifPresent(Consumer<? super T> action) { 112 if (value != null) { 113 action.accept(value); 114 } 115 } 116 117 /** 118 如果存在值,则使用该值执行给定的操作,否则执行给定的基于空的操作。 119 参数:动作–要执行的动作(如果存在值)emptyAction –要执行的基于空的操作(如果不存在任何值) 120 抛出:NullPointerException如果存在一个值并且给定的操作为null ,或者不存在任何值并且给定的基于空的操作为null 121 @since 9 122 */ 123 public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) { 124 if (value != null) { 125 action.accept(value); 126 } else { 127 emptyAction.run(); 128 } 129 } 130 131 /** 132 如果存在一个值,并且该值与给定的谓词匹配,则返回描述该值的Optional ,否则返回一个空的Optional 。 133 134 参数:谓词–应用于值的谓词(如果存在) 135 返回值:一个Optional描述此的值Optional ,如果一个值存在并且该值给定的谓词相匹配,否则一个空Optional 136 抛出:NullPointerException如果谓词为null 137 */ 138 public Optional<T> filter(Predicate<? super T> predicate) { 139 Objects.requireNonNull(predicate); 140 if (!isPresent()) { 141 return this; 142 } else { 143 return predicate.test(value) ? this : empty(); 144 } 145 } 146 147 /** 148 如果存在值,则返回一个Optional描述(就像by ofNullable ),将给定映射函数应用于该值的结果,否则返回一个空的Optional 。 149 如果映射函数返回null结果,则此方法返回空的Optional 150 */ 151 public <U> Optional<U> map(Function<? super T, ? extends U> mapper) { 152 Objects.requireNonNull(mapper); 153 if (!isPresent()) { 154 return empty(); 155 } else { 156 return Optional.ofNullable(mapper.apply(value)); 157 } 158 } 159 160 /** 161 如果存在一个值,则返回将给定Optional -bearing映射函数应用于该值的结果,否则返回一个空的Optional 。 162 此方法类似于map(Function) ,但是映射函数是其结果已经是Optional函数,如果调用该函数,则flatMap不会将其包装在其他Optional 。 163 164 参数:mapper –应用于值的映射函数(如果存在) 165 类型参数:<U> –映射函数返回的Optional值的类型 166 返回值:施加的结果Optional荷瘤映射函数此的值Optional ,如果一个值存在,否则一个空Optional 167 抛出:NullPointerException如果映射函数为null或返回null结果 168 169 */ 170 public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) { 171 Objects.requireNonNull(mapper); 172 if (!isPresent()) { 173 return empty(); 174 } else { 175 @SuppressWarnings("unchecked") 176 Optional<U> r = (Optional<U>) mapper.apply(value); 177 return Objects.requireNonNull(r); 178 } 179 } 180 181 /** 182 如果值存在时,返回一个Optional描述的值,否则将返回一个Optional产生通过供给功能。 183 184 参数:供应商–产生要返回的Optional的供应功能 185 返回值:返回一个Optional描述此的值Optional ,如果一个值存在,否则Optional所生产的供应功能。 186 抛出:NullPointerException如果提供的函数为null或产生null结果 187 * @since 9 188 */ 189 public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier) { 190 Objects.requireNonNull(supplier); 191 if (isPresent()) { 192 return this; 193 } else { 194 @SuppressWarnings("unchecked") 195 Optional<T> r = (Optional<T>) supplier.get(); 196 return Objects.requireNonNull(r); 197 } 198 } 199 200 /** 201 如果存在值,则返回仅包含该值的顺序Stream ,否则返回空Stream 。 202 203 返回值:作为Stream的可选值 204 * @since 9 205 */ 206 public Stream<T> stream() { 207 if (!isPresent()) { 208 return Stream.empty(); 209 } else { 210 return Stream.of(value); 211 } 212 } 213 214 /** 215 如果存在值,则返回该值,否则返回other 。 216 217 参数:其他–要返回的值(如果不存在任何值)。 可以为null 。 218 返回值:值(如果存在),否则other 219 */ 220 public T orElse(T other) { 221 return value != null ? value : other; 222 } 223 224 /** 225 如果存在值,则返回该值,否则返回由供应函数产生的结果。 226 227 参数:供应商–产生要返回的值的供应函数 228 返回值:值(如果存在),否则提供功能产生的结果 229 */ 230 public T orElseGet(Supplier<? extends T> supplier) { 231 return value != null ? value : supplier.get(); 232 } 233 234 /** 235 * If a value is present, returns the value, otherwise throws 236 * {@code NoSuchElementException}. 237 * 238 * @return the non-{@code null} value described by this {@code Optional} 239 * @throws NoSuchElementException if no value is present 240 * @since 10 241 */ 242 public T orElseThrow() { 243 if (value == null) { 244 throw new NoSuchElementException("No value present"); 245 } 246 return value; 247 } 248 249 /** 250 如果存在值,则返回该值,否则抛出由异常提供函数产生的异常。 251 252 参数:exceptionSupplier –产生要抛出的异常的提供函数 253 类型参数:<X> –引发的异常类型 254 返回值:值(如果存在) 255 抛出:X –如果不存在任何值NullPointerException如果不存在任何值并且异常提供函数为null 256 api注意:带有空参数列表的对异常构造函数的方法引用可用作提供者 257 */ 258 public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X { 259 if (value != null) { 260 return value; 261 } else { 262 throw exceptionSupplier.get(); 263 } 264 } 265 }
三、测试使用
1、构建
1 public void test01() { 2 //返回一个描述给定值的Optional ,如果不为null ,则返回一个空的Optional 。 3 //参数:值–描述的可能为null值 4 //类型参数:<T> –值的类型 5 //返回值:一个Optional与如果指定值是非当前值null ,否则一个空Optional 6 Optional s1 = Optional.ofNullable(null); 7 8 // 构建一个value不可以为null的optional对象,如果of()的入参为null会报空指针异常; 9 Optional<MyUser> s2 = Optional.of(new MyUser("张三", "123456")); 10 11 // 构建一个value可以为null的optional对象; 12 Optional<MyUser> s3 = Optional.ofNullable(null); 13 14 System.out.println(s1); 15 System.out.println(s2); 16 System.out.println(s3); 17 18 /* 输出 19 * Optional.empty 20 * Optional[TestRegex.MyUser(name=阿辉2, id=123456)] 21 * Optional.empty 22 **/ 23 }
2、判断类
1 public void test02(){ 2 Optional<MyUser> myUser1 = Optional.empty(); 3 Optional<MyUser> myUser2 = Optional.of(new MyUser("李四", "123456")); 4 5 // filter传入一个lambda,lambda返回值为boolean;true:不做任何改变,false:返回一个空的optional; 6 Optional<MyUser> myUser3 = myUser2.filter(user -> "李四".equals(user.getName())); 7 8 // isPresent就是判断value是不是null;我们在调用get之前,一定要先调用isPresent,因为直接如果value是null,直接调用get会报异常; 9 if (myUser1.isPresent()) { 10 MyUser value = myUser1.get(); 11 System.out.println("optional value:" + value); 12 } else { 13 System.out.println("optional value==null"); 14 } 15 // ifPresent传入一段lambda,当value!=null时,执行里面的逻辑;当当value==null时,啥都不干; 16 myUser2.ifPresent(value -> System.out.println("optional value:" + value)); 17 18 System.out.println(myUser3); 19 20 // 输入如下: 21 // optional value==null 22 // optional value:TestOptional.MyUser(name=李四, id=123456) 23 // Optional[TestOptional.MyUser(name=李四, id=123456)] 24 }
3、获取类
1 public void test03(){ 2 Optional<MyUser> userInfoEmptyOpt = Optional.empty(); 3 Optional<MyUser> userInfoOpt = Optional.of(new MyUser("张三","123456")); 4 5 // 1、直接获取,注意如果value==null,会报NoSuchElementException异常 6 MyUser userInfo1 = userInfoOpt.get(); 7 8 // 2、orElse可以传入一个UserInfo类型的对象作为默认值; 9 // 当value!=null时,返回value值;当value==null时,返回默认值作为代替; 10 MyUser userInfo2 = userInfoEmptyOpt.orElse(new MyUser("张三1","123456")); 11 12 // 3、orElseGet和orElse不同的是orElseGet可以传入一段lambda表达式; 13 // 当value!=null时,返回value值; 14 // 当value==null时,使用该lambda返回的对象作为默认值; 15 MyUser userInfo3 = userInfoEmptyOpt.orElseGet(() -> new MyUser("张三2","123456")); 16 17 // 4、orElseThrow可以传入一段lambda表达式,lambda返回一个Exception;当value!=null时,返回value值;当value==null时,抛出该异常; 18 MyUser userInfo4 = userInfoOpt.orElseThrow(NullPointerException::new); 19 20 System.out.println(userInfo1); 21 System.out.println(userInfo2); 22 System.out.println(userInfo3); 23 System.out.println(userInfo4); 24 25 // 输出如下: 26 // TestOptional.MyUser(name=张三, id=123456) 27 // TestOptional.MyUser(name=张三1, id=123456) 28 // TestOptional.MyUser(name=张三2, id=123456) 29 // TestOptional.MyUser(name=张三, id=123456) 30 }
4、转换类
1 public void test04(){ 2 Optional<MyUser> userInfoOpt = Optional.of(new MyUser("张三","123456")); 3 4 // 原来value的类型是UserInfo,经过map转换为Optional<String> 5 Optional<String> username = userInfoOpt.map(MyUser::getName); 6 7 // 当map的入参也是一个Optional时,经过map转化后会形成Optional<Optional<String>>这种嵌套结构;但flatMap可以把这种嵌套结构打平; 8 Optional<Optional<String>> unFlatMap = userInfoOpt.map(user -> Optional.of(user.getName())); 9 Optional<String> flatMap = userInfoOpt.flatMap(user -> Optional.of(user.getName())); 10 11 System.out.println(username); 12 System.out.println(unFlatMap); 13 System.out.println(flatMap); 14 15 // 输出如下: 16 // Optional[张三] 17 // Optional[Optional[张三]] 18 // Optional[张三] 19 }
四、总结
回到前言的那个问题,我们可以用如下代码替换:
1 public String getId(Student student) throws Exception{ 2 return Optional.ofNullable(student) 3 .map(u-> u.getTeacher()) 4 .map(a->a.getId()) 5 .orElseThrow(()->new Exception("取值错误")); 6 }
其他的例子,不一一列举了。不过采用这种链式编程,虽然代码优雅了。但是,逻辑性没那么明显,可读性有所降低,大家项目中看情况酌情使用。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)