Opcional es un objeto contenedor . En pocas palabras, envuelve un objeto dentro. Este objeto existe o es nulo , y algunas operaciones se pueden realizar alrededor de este objeto.
Crear Opcional
Opcional proporciona tres formas de crear un Opcional
Optional<Integer> of = Optional.of(1);
Optional<Integer> ofNull = Optional.ofNullable(1);
Optional<Object> empty = Optional.empty();
Las tres líneas de código anteriores corresponden a tres formas de crear objetos opcionales. El primer of(T val)
método de uso usa este método en el ejemplo para crear un Integer
objeto contenedor envuelto Optional
; el segundo uso ofNullable(T val)
usa este método para crear Integer
un objeto contenedor en el ejemplo Optional
, pero es diferente del primer método en que si pasa Si el valor ingresado null
es para crear un Opcional vacío, el primer método arrojará una excepción ; el último tercer método significa crear un objeto contenedor vacío.
Obtener el valor en Opcional
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);
El ejemplo anterior ejemplifica cuatro Optional
métodos para obtener los elementos en el medio, entre los cuales get
el método no se usa comúnmente, y se lanzará una excepción directamente si el elemento no existe NoSuchElementException
. orElse
El método proporciona un valor predeterminado, que se devuelve si el elemento está vacío. orElseGet
El parámetro de es una Supplier
interfaz funcional, que puede devolver cualquier tipo de valor y devuelve el resultado de este método si el elemento está vacío. Al último orElseThrow
se le puede pasar un objeto de excepción, que se lanzará si el elemento no existe.
Consumir el valor envuelto a través del método ifPresent
Este método puede determinar Optional
si el valor en existe.Si hay Consumer
un método de instancia que pasa el valor envuelto a la interfaz en el parámetro, este Consumer
se ejecutará automáticamente.
Optional<Integer> first = cs2().filter(x -> x>4).findFirst();
first.ifPresent(v -> System.out.println(v));
Salida: 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]}