Java流库(中篇——Optional、元素收集)

Optional是一个包装器对象,简单来说就是它内部包装了一个对象,这个对象要么存在那么为null,并且可以围绕这个对象做一些操作。

创建Optional

Optional提供了三种方式创建一个Optional

Optional<Integer> of = Optional.of(1);
Optional<Integer> ofNull = Optional.ofNullable(1);
Optional<Object> empty = Optional.empty();

上面的三行代码分别对应三种创建Optional对象的方式。第一种使用of(T val)方法,示例中使用这个方法创建一个包装了Integer的包装器对象Optional;第二种使用ofNullable(T val),示例中使用这个方法创建了Integer的包装器对象Optional但是它与第一个方法不同的是,如果传入的值为null则创建一个空的Optional,而第一个方法则会抛出异常;最后第三个方法表示创建一个空的包装器对象。

获取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);

上面的示例中举例了四个获取Optional中元素的方法,其中get方法不常用,如果元素不存在会直接抛出NoSuchElementException异常。orElse方法提供一个默认值,如果元素为空返回这个默认值。orElseGet的参数是一个Supplier函数式接口,可以返回任意类型的值,如果元素为空返回这个方法运行的结果。最后一个orElseThrow可以传递一个异常对象,如果元素不存在则会抛出异常。

通过ifPresent方法消费其包装的值

这个方法可以判断Optional中的值是否存在,如果存在将包装的值传递给参数中的Consumer接口的实例方法,这个Consumer会自动执行。

Optional<Integer> first = cs2().filter(x -> x>4).findFirst();
first.ifPresent(v -> System.out.println(v));

输出: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]

扫描二维码关注公众号,回复: 15156956 查看本文章
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]}

猜你喜欢

转载自juejin.im/post/7234894309995921469