java.util.optional
前言
我们必须考虑在一个空流中获取元素会发生什么,我们总是习惯流是不会中断的。然而,在流中放置null
却会轻易令其中断,那么是否可以有一种对象,可以使我们即在持有流元素的同时,也可以在元素不存在时也向我们进行友好提示呢(不会产生异常)?
一、 Optional 类
Optional 可以实现这样的功能。标准流操作中的一些可以返回 Optional 对象,因为它们并不能保证预期结果一定存在。
-
findFirst()
:返回包含第一个元素的 Optional 对象,如果流为空则返回Optional.empty
。 -
findAny()
:返回包含任意元素的 Optional 对象,如果流为空则返回Optional.empty
。 -
max()
和min()
:返回一个包含最大值或是最小值的 Optional 对象,如果流为空则返回Optional.empty
。
reduce()
不再以identity
形式开头,而是将其返回值包含在 Optional 中。(identity 对象成为其他形式的reduce()
的默认结果,因此不存在空结果的风险)对于数字流 IntStream 等,average()
会将结果包装在 Optional 以防止流为空。
public class OptionalsFromEmptyStreams {
public static void main(String[] args) {
System.out.println(Stream.<String>empty()
.findFirst());
System.out.println(Stream.<String>empty()
.findAny());
System.out.println(Stream.<String>empty()
.max(String.CASE_INSENSITIVE_ORDER));
System.out.println(Stream.<String>empty()
.min(String.CASE_INSENSITIVE_ORDER));
System.out.println(Stream.<String>empty()
.reduce((s1, s2) -> s1 + s2));
System.out.println(IntStream.empty()
.average());
}
}
/*
output:
Optional.empty
Optional.empty
Optional.empty
Optional.empty
Optional.empty
OptionalDouble.empty
*/
当流为空的时候你会获得一个Optional.empty 对象,而不是抛出异常。Optional 拥有toString()
方法可以用于展示有用信息。
注意:空流是通过Stream.<String>empty()
创建的。如果在没有任何上下文环境的情况下调用Stream.empty()
,Java 并不知道它的数据类型。
下面是Optional 的基本用法:
public class OptionalBasics {
static void test(Optional<String> optString) {
if (optString.isPresent())
System.out.println(optString.get());
else
System.out.println("Nothing inside!");
}
public static void main(String[] args) {
test(Stream.of("Epithets").findFirst());
test(Stream.<String>empty().findFirst());
}
}
/*
output:
Epithets
Nothing inside!
*/
当你接收到Optional 对象时,应首先调用isPresent()
检查其中是否包含元素,如果存在,可使用get()
获取。
二、 便利函数
有许多便利函数可以解包 Optional ,
-
isPresent(Consumer)
:当值存在时调用 Consumer,否则什么也不做。 -
orElse(otherObject)
:如果值存在则直接返回,否则生成 otherObject。 -
orElseGet(Supplier)
:如果只存在则直接返回,否则使用Supplier 函数生成一个可替代对象。 -
orElseThrow(Supplier)
:如果值存在直接返回,否则使用Supplier 函数生成一个异常。
public class Optionals {
static void basics(Optional<String> optString) {
if (optString.isPresent()) {
System.out.println(optString.get());
} else System.out.println("Nothing Inside!");
}
static void ifPresent(Optional<String> optString) {
optString.ifPresent(System.out::println);
}
static void orElse(Optional<String> optString) {
System.out.println(optString.orElse("Nada"));
}
static void orElseGet(Optional<String> optString) {
System.out.println(optString.orElseGet(() -> "Generated"));
}
static void orElseThrow(Optional<String> optString) {
try {
System.out.println(optString.orElseThrow(() -> new Exception("Supplied")));
} catch (Exception e) {
System.out.println("Caught " + e);
}
}
static void test(String testName, Consumer<Optional<String>> cos) {
System.out.println("===" + testName + "=====");
cos.accept(Stream.of("Epithets").findFirst());
cos.accept(Stream.<String>empty().findFirst());
}
public static void main(String[] args) {
test("basics", Optionals::basics);
test("ifPresent", Optionals::ifPresent);
test("orElse", Optionals::orElse);
test("orElseGet", Optionals::orElseGet);
test("orElseThrow", Optionals::orElseThrow);
}
}
/*
output:
===basics=====
Epithets
Nothing Inside!
===ifPresent=====
Epithets
===orElse=====
Epithets
Nada
===orElseGet=====
Epithets
Generated
===orElseThrow=====
Epithets
Caught java.lang.Exception: Supplied
*/
三、 创建 Optional
当我们在自己的代码块中加入 Optional 时,可以使用以下三个静态方法:
-
empty()
:生成一个空 Optional。 -
of(value)
:将一个非空值包装到 Optional 中。 -
ofNullable()
:针对一个可能为空的值,为空时自动生成 Optional.empty,否则将值包装在 Optional 中。
下面是示例:
public class CreatingOptionals {
static void test(String testName, Optional<String> optional) {
System.out.println("====" + testName + "====");
System.out.println(optional.orElse("NULL"));
}
public static void main(String[] args) {
test("empty", Optional.empty());
test("of", Optional.of("Howdy()"));
try {
test("of", Optional.of(null));
} catch (Exception e) {
System.out.println(e);
}
test("ofNullable", Optional.ofNullable("Hi"));
test("ofNullable", Optional.ofNullable(null));
}
}
/*
output:
====empty====
NULL
====of====
Howdy()
java.lang.NullPointerException
====ofNullable====
Hi
====ofNullable====
NULL
*/
我们不能通过传递 null 到 of() 来创建 Optional 对象,最安全的方法是:使用 ofNullable() 来优雅的处理 null。
四、 Optional 对象操作:filter、map、flatMap
当我们的流管道生产了 Optional 对象,下面三个方法可使得 Optional 的后续能做更多的操作:
-
filter(Predicate)
:对 Optional 中的内容应用 Predicate 并将结果返回。如果 Optional 不满足 Predicate ,将 Optional 转化为空 Optional。如果 Optional 为空,则直接返回空 Optional。 -
map(Function)
:如果 Optional 不为空,应用 Function 于 Optional 中的内容,并返回结果。否则直接返回 Optional.empty。 -
flatMap(Function)
:同map(),但是提供的映射函数(就是Function)将结果包装在 Optional 对象中,因此 flatMap() 不会在最后进行任何包装。
public class OptionalFilter {
static String[] elements = {"Foo", "Bar", "Baz", "Bingo"};
static Stream<String> testStream() {
return Arrays.stream(elements);
}
static void test(String descr, Predicate<String> predicate) {
System.out.println("---(" + descr + ")----");
for (int i = 0; i <= elements.length; i++) {
System.out.println(testStream()
.skip(i)
.findFirst()
.filter(predicate));
}
}
public static void main(String[] args) {
test("true", s -> true);
test("false", s -> false);
test("s != \"\"", s -> s != "");
//此处的s.length() 是传入的单个流元素
test("s.length() == 3", s -> s.length() == 3);
test("startsWith(\"B\")", s -> s.startsWith("B"));
}
}
/*
output:
---(true)----
Optional[Foo]
Optional[Bar]
Optional[Baz]
Optional[Bingo]
Optional.empty
---(false)----
Optional.empty
Optional.empty
Optional.empty
Optional.empty
Optional.empty
---(s != "")----
Optional[Foo]
Optional[Bar]
Optional[Baz]
Optional[Bingo]
Optional.empty
---(s.length() == 3)----
Optional[Foo]
Optional[Bar]
Optional[Baz]
Optional.empty
Optional.empty
---(startsWith("B"))----
Optional.empty
Optional[Bar]
Optional[Baz]
Optional[Bingo]
Optional.empty
*/
即使输出看起来像流,要特别注意 test() 中的for循环:1.每次for循环都操作的是新生成的流,2.跳过指定数量的元素,3.通过调用findFirst()
获取剩余元素中的第一个元素,并将其包装在 Optional 对象中。
注意:这里不同于普通for 循环,这里的 等于 号实际上使得最后一位超出了数组转化为的流的范围,但是并不会报错。
同map()
一样,Optional.map()
仅仅在 Optional 不为空时才执行这个映射函数,并将 Optional 的内容提取出来,传递给映射函数。
public class OptionalMap {
static String[] elements = {"12", "", "23", "45"};
static Stream<String> stream() {
return Arrays.stream(elements);
}
static void test(String descr, Function<String,String> func) {
System.out.println("---(" + descr + ")----");
for (int i = 0; i <= elements.length; i++) {
System.out.println(stream()
.skip(i)
.findFirst()//此处产生一个optional
.map(func));
}
}
public static void main(String[] args) {
test("Add brackets", s -> "[" + s + "]");
test("Increment", s -> {
try {
return Integer.parseInt(s) + 1 + "";
} catch (Exception e) {
return s;
}
});
test("Replace", s -> s.replace("2", "9"));
//记得每个s是单个流元素
test("Take last digit", s -> s.length() > 0 ? s.charAt(s.length() - 1) + "" : s);
}
}
/*
output:
---(Add brackets)----
Optional[[12]]
Optional[[]]
Optional[[23]]
Optional[[45]]
Optional.empty
---(Increment)----
Optional[13]
Optional[]
Optional[24]
Optional[46]
Optional.empty
---(Replace)----
Optional[19]
Optional[]
Optional[93]
Optional[45]
Optional.empty
---(Take last digit)----
Optional[2]
Optional[]
Optional[3]
Optional[5]
Optional.empty
*/
映射函数的返回结果会自动包装成为 Optional。Optional.empty()
(空)会被直接跳过。
public class OptionalFlatMap {
static String[] elements = {"12","", "23", "45"};
static Stream<String> stream() {
return Arrays.stream(elements);
}
static void test(String descr, Function<String, Optional<String>> func) {
System.out.println("---(" + descr + ")----");
for (int i = 0; i <= elements.length; i++) {
System.out.println(stream()
.skip(i)
.findFirst()
.flatMap(func));//这里的参数变成了Optional类型
}
}
public static void main(String[] args) {
test("Add brackets", new Function<String, Optional<String>>() {
@Override
public Optional<String> apply(String s) {
return Optional.of("[" + s + "]");
}
});
test("Increment",s -> {
try {
return Optional.of(Integer.parseInt(s) + 1 + "");
} catch (Exception e) {
return Optional.of(s);
}
});
test("Replace", s -> Optional.ofNullable(s.replace("2", "9")));
test("Take last digit",s->Optional.of(s.length()>0?s.charAt(s.length()-1)+"":s));
}
}
/*
output:
---(Add brackets)----
Optional[[12]]
Optional[[]]
Optional[[23]]
Optional[[45]]
Optional.empty
---(Increment)----
Optional[13]
Optional[]
Optional[24]
Optional[46]
Optional.empty
---(Replace)----
Optional[19]
Optional[]
Optional[93]
Optional[45]
Optional.empty
---(Take last digit)----
Optional[2]
Optional[]
Optional[3]
Optional[5]
Optional.empty
*/
Optional 的 flatMap()
应用于已生成 Optional 的映射函数,所以它不会像 map()
那样将结果封装在 Optional 里。
同map()
,flatMap()
将提取非空的Optional 内容将其应用在映射函数。唯一的区别就是它不再将结果包装在 Optional 中,因为已经包装过了。
五、 Optional 流
假设你的生成器可能产生 null 值。那么当用它来创建流时,用 Optional 来包装元素更好。
public class Signal {
private final String msg;
public Signal(String msg) {
this.msg = msg;
}
public String getMsg() {
return msg;
}
@Override
public String toString() {
return "Signal{" +
"msg='" + msg + '\'' +
'}';
}
static Random rand = new Random(47);
public static Signal morse() {
switch (rand.nextInt(4)) {
case 1:
return new Signal("dot");
case 2:
return new Signal("dash");
default:
return null;
}
}
public static Stream<Optional<Signal>> stream() {
return Stream.generate(Signal::morse)
.map(signal -> Optional.ofNullable(signal));
}
}
当我们使用这个流的时候,必须要弄清楚如何解包 Optional 。代码如下:
public class StreamOfOptionals {
public static void main(String[] args) {
Signal.stream()
.limit(10)
.forEach(System.out::println);
System.out.println("-----------");
Signal.stream()
.limit(10)
.filter(Optional::isPresent)
.map(Optional::get)
.forEach(System.out::println);
}
}
/*
output:
Optional[Signal{msg='dash'}]
Optional[Signal{msg='dot'}]
Optional[Signal{msg='dash'}]
Optional.empty
Optional.empty
Optional[Signal{msg='dash'}]
Optional.empty
Optional[Signal{msg='dot'}]
Optional[Signal{msg='dash'}]
Optional[Signal{msg='dash'}]
-----------
Signal{msg='dot'}
Signal{msg='dot'}
Signal{msg='dash'}
Signal{msg='dash'}
*/
在这里我们使用filter()
来保留那些非空 Optional, 然后在 map()
中使用get()
获取元素。由于每种情况都需要定义“空值”的含义,所以通常我们要为每个应用程序采用不同的方法。