Java Stream Library (Middle Part - Optional, Element Collection)

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 Integerwrapper object Optional; the second usage ofNullable(T val)uses this method to create Integera wrapper object in the example Optional, but it is different from the first method in that if you pass If the value entered nullis 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 Optionalmethods for obtaining the elements in the middle, among which getthe method is not commonly used, and an exception will be thrown directly if the element does not exist NoSuchElementException. orElseThe method provides a default value, which is returned if the element is empty. orElseGetThe parameter of is a Supplierfunctional interface, which can return any type of value, and returns the result of this method if the element is empty. The last one orElseThrowcan 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 Optionalwhether the value in exists. If there is Consumeran instance method that passes the wrapped value to the interface in the parameter, this Consumerwill 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方法

这个方法的作用是按给定的条件将结果按truefalse分别分组。它们的键就是truefalse

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]}

可以看到,符合条件的元素都被映射到了keytruevalue中,不符合条件的则映射到keyfalsevalue中。

处理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]}

Guess you like

Origin juejin.im/post/7234894309995921469