Optional is a wrapper object . Simply put, it wraps an object inside. This object either exists or is null , and some operations can be performed around this object.
Create Optional
Optional provides three ways to create an Optional
Optional<Integer> of = Optional.of(1);
Optional<Integer> ofNull = Optional.ofNullable(1);
Optional<Object> empty = Optional.empty();
The above three lines of code correspond to three ways of creating Optional objects. The first usage of(T val)
method uses this method in the example to create a wrapped Integer
wrapper object Optional
; the second usage ofNullable(T val)
uses this method to create Integer
a wrapper object in the example Optional
, but it is different from the first method in that if you pass If the value entered null
is to create an empty Optional, the first method will throw an exception ; the last third method means to create an empty wrapper object.
Get the value in Optional
Optional<Integer> first = cs2().filter(x -> x>8).findFirst();
int num1 = first.get();
int num2 = first.orElse(-1);
int num3 = first.orElseGet(() -> -1);
int num4 = first.orElseThrow(IllegalStateException::new);
The above example exemplifies four Optional
methods for obtaining the elements in the middle, among which get
the method is not commonly used, and an exception will be thrown directly if the element does not exist NoSuchElementException
. orElse
The method provides a default value, which is returned if the element is empty. orElseGet
The parameter of is a Supplier
functional interface, which can return any type of value, and returns the result of this method if the element is empty. The last one orElseThrow
can be passed an exception object, which will be thrown if the element does not exist.
Consume the wrapped value through the ifPresent method
This method can determine Optional
whether the value in exists. If there is Consumer
an instance method that passes the wrapped value to the interface in the parameter, this Consumer
will be executed automatically.
Optional<Integer> first = cs2().filter(x -> x>4).findFirst();
first.ifPresent(v -> System.out.println(v));
Output: 5
除了ifPresent
方法外,还有一个方法叫做ifPresentOrElse
,这个方法有两个参数,第一个参数是Consumer
类型,第二个参数是Runnable
类型的,总体的意思是如果元素不存在执行第一个动作,否则执行第二个动作。
Optional<Integer> first = cs2().filter(x -> x>8).findFirst();
first.ifPresentOrElse(System.out::println,() -> System.out.println("不存在数据"));
输出:不存在数据
转换Optional值
实际上Optional
的转换操作跟流(Stream)的转换的转换操作是类似的,只不过Optional
只包含一个元素。
代码示例:
Optional<Integer> first = cs2().filter(x -> x>4).findFirst();
Optional<Double> aDouble = first.map(Integer::doubleValue);
System.out.println(aDouble);
输出:Optional[5.0]
flatMap
简单来说,flatMap
的作用是把多个针对Optional
的操作串联起来,当所有的操作都执行成功才算成功;任何一步失败则视为失败。
class HelloWorld {
public static Optional<String> getHello(String msg) {
// int i = 1/0;
return Optional.of(msg);
}
public static Optional<String> getWorld(String msg) {
return Optional.of(msg + "嘿嘿");
}
}
public static void main(String[] args) throws FileNotFoundException {
Optional<String> o = Optional.of("你好朋友").flatMap(HelloWorld::getHello).flatMap(HelloWorld::getWorld);
System.out.println(o);
}
输出:Optional[你好朋友嘿嘿]
上面的示例中,使用flatMap
方法将两个任务连接成一个任务,后面的任务需要依赖前面一个任务。这个任务链最终成功执行。但是如果将int i = 1/0;
这行注释去掉,则第一个任务会出错,并且任务链也会失败。
将Optional转换为流
Optional<String> o = Optional.of("你好朋友").flatMap(HelloWorld::getHello).flatMap(HelloWorld::getWorld);
Stream<String> stream = Stream.of(o.get());
通过Stream
创建流的方法,很容易就可以配合包装器的get()
完成新流的创建。
获取流的结果
以上所说的都关于如何操作流,那如果现在流已经完成了一系列任务,该怎么获取到流中的数据呢?
遍历流
可以通过forEach直接遍历流中的元素
public static void main(String[] args) throws FileNotFoundException {
Stream<String> hello = Stream.of("hello", "kitty", "hello", "kitty");
hello.map(s -> s.concat(" ")).forEach(System.out::print);
}
输出:hello kitty hello kitty
通过流得到一个数组
public static void main(String[] args) throws FileNotFoundException {
Stream<String> hello = Stream.of("hello", "kitty", "hello", "kitty");
// String[] strings = hello.toArray((x) -> new String[x]);
//等同于上面的写法
String[] strings = hello.toArray(String[]::new);
for (String s : strings) {
System.out.print(s+" ");
}
}
输出:hello kitty hello kitty
这里需要注意的是,toArray()
方法的默认数组类型为Object[]
。所以可以通过它的IntFunction
类型参数指定一个具体的类型。
通过collect收集元素
收集流中的数据到List
集合
Stream<String> hello = Stream.of("hello", "kitty", "hello", "kitty");
List<String> list = hello.collect(Collectors.toList());
收集流中的数据到Set
集合 (无序且元素不可重复)
Stream<String> hello = Stream.of("hello", "kitty", "hello", "kitty");
Set<String> set = hello.collect(Collectors.toSet());
收集流中的数据到TreeSet
集合 (有序且无重复元素)
Stream<String> hello = Stream.of("hello", "kitty", "hello", "kitty");
Set<String> set = hello.collect(Collectors.toCollection(TreeSet::new));
这里的
toCollection
方法的作用是指定获得的集合的种类
收集流中的字符串并拼接他们
Stream<String> hello = Stream.of("hello", "kitty", "hello", "kitty");
String str = hello.collect(Collectors.joining(" "));
收集元素到映射表(Map)中
Stream<String> hello = Stream.of("hello", "kitty", "hello", "kitty");
Map<String, String> map = hello.collect(Collectors.toMap(x -> x, x -> x));
这个示例代码运行时会报错,因为Map
中键不允许重复,遇到这种情况可以传入toMap
方法的第三个参数来解决:
Stream<String> hello = Stream.of("hello", "kitty", "hello", "kitty");
Map<String, String> map = hello.collect(Collectors.toMap(x -> x, x -> x,(a,b)->"newVal"));
System.out.println(map);
输出:{hello=newVal, kitty=newVal}
第三个参数是一个函数式接口类型,它的意思是说当遇到相同的key
时,需要为其指定一个值来覆盖它,可以是原值和新值,也可以是自定义的值。
默认的获取的Map
类型为HashMap
另外如果想要得到其他Map
类型,还可以传入toMap
方法的第四个参数:
Stream<String> hello = Stream.of("hello", "kitty", "hello", "kitty");
Map<String, String> map = hello.collect(Collectors.toMap(x -> x, x -> x,(a,b)->"newVal",TreeMap::new));
分组统计流的数据
假如想统计流中的所有相同元素的个数该怎么办呢?
groupingBy方法
Stream<String> hello = Stream.of("hello", "kitty", "hello", "kitty");
Map<String, List<String>> map = hello.collect(Collectors.groupingBy(x -> x));
System.out.println(map);
输出:{hello=[hello, hello], kitty=[kitty, kitty]}
groupingBy
方法的参数是一个Function
接口。他会按照这个函数式接口实例的方法返回值进行分组。比如返回hello
就按Hello
分组为hello=[hello, hello]
partitioningBy方法
这个方法的作用是按给定的条件将结果按true
和false
分别分组。它们的键就是true
或false
。
Stream<String> hello = Stream.of("hello", "kitty", "hello", "kitty");
Map<Boolean, List<String>> map = hello.collect(Collectors.partitioningBy(s -> s.equals("hello")));
System.out.println(map);
输出:{false=[kitty, kitty], true=[hello, hello]}
可以看到,符合条件的元素都被映射到了key
为true
的value
中,不符合条件的则映射到key
为false
的value
中。
处理groupingBy分组中的数据
比如这么一个分组结果{hello=[hello, hello], kitty=[kitty, kitty]}
,可不可以统计分组中元素的数量?
counting
Stream<String> hello = Stream.of("hello", "kitty", "hello", "kitty");
Map<String, Long> map = hello.collect(Collectors.groupingBy(x->x,Collectors.counting()));
System.out.println(map);
输出:{hello=2, kitty=2}
通过Collectors
的静态方法counting()
就可完成对每个分组中元素数量的统计
summingInt
这个方法可以计算出分组中数值类型的和
Stream<Integer> hello = Stream.of(2,3,4,4,2,3,5,6,4,4,7,8,8,8);
Map<Integer, Integer> map = hello.collect(Collectors.groupingBy(x -> x,Collectors.summingInt(x -> x)));
System.out.println(map);
输出:{2=4, 3=6, 4=16, 5=5, 6=6, 7=7, 8=24}
maxBy、minBy
这两个静态方法可以产生分组中的最大值和最小值。例如下面的示例中,会产生出奇数以及偶数两个分组中的最大值。
Stream<Integer> hello = Stream.of(2,3,4,4,2,3,5,6,4,4,7,8,8,8);
Map<Object, Optional<Integer>> map = hello.collect(Collectors.groupingBy(x -> x % 2,Collectors.maxBy(Comparator.comparing((x)-> x))));
System.out.println(map);
输出:{0=Optional[8], 1=Optional[7]}
minBy
不做举例,两个方法用法是一样的
collectingAndThen
如果想要先收集分组中的元素再针对每个分组做计算,就应该使用collectingAndThen
Stream<Integer> hello = Stream.of(2,3,4,4,2,3,5,6,4,4,7,8,8,8);
Map<String, Integer> map = hello.collect(Collectors.groupingBy(x -> x % 2==0?"偶数":"奇数", Collectors.collectingAndThen(Collectors.toSet(), Set::size)));
System.out.println(map);
输出:{偶数=4, 奇数=3}
mapping
mapping
方法的作用与collectingAndThen
完全相反,它是先对分组做计算后收集计算后的每组元素。例如下面的代码会先将分组中的每个Integer
类型的元素都乘以2
,再收集到Set
集合中。
Stream<Integer> hello = Stream.of(2,3,4,4,2,3,5,6,4,4,7,8,8,8);
Map<String, Set<Integer>> map = hello.collect(Collectors.groupingBy(x -> x % 2 == 0 ? "偶数" : "奇数", Collectors.mapping((x) -> x * 2, Collectors.toSet())));
System.out.println(map);
输出:{偶数=[16, 4, 8, 12], 奇数=[6, 10, 14]}
summarizingInt
这是一个非常好用的方法,他可以返回一个分组中元素的汇总对象;在之后可以通过这个对象获取到分组中数据的总和、平均值、总数量、最大值、最小值
Stream<Integer> hello = Stream.of(2,3,4,4,2,3,5,6,4,4,7,8,8,8);
Map<String, IntSummaryStatistics> map = hello.collect(Collectors.groupingBy(x -> x % 2 == 0 ? "偶数" : "奇数", Collectors.summarizingInt((x) -> x)));
Set<Map.Entry<String, IntSummaryStatistics>> entries = map.entrySet();
for (Map.Entry<String, IntSummaryStatistics> entry : entries) {
System.out.println(entry.getKey());
System.out.print("平均值:"+entry.getValue().getAverage()+"、");
System.out.print("总和:"+entry.getValue().getSum()+"、");
System.out.print("数量:"+entry.getValue().getCount()+"、");
System.out.print("最大值:"+entry.getValue().getMax()+"\n");
}
输出:偶数
平均值:5.0、总和:50、数量:10、最大值:8
奇数
平均值:4.5、总和:18、数量:4、最大值:7
filtering
还可以对分组的数据使用过滤器,下面的示例会筛选出分组中小于5
的数值
Stream<Integer> hello = Stream.of(2,3,4,4,2,3,5,6,4,4,7,8,8,8);
Map<String, List<Integer>> map = hello.collect(Collectors.groupingBy(x -> x % 2 == 0 ? "偶数" : "奇数", Collectors.filtering(x -> x < 5, Collectors.toList())));
System.out.println(map);
输出:{偶数=[2, 4, 4, 2, 4, 4], 奇数=[3, 3]}