Biblioteca de flujo de Java (parte central: opcional, colección de elementos)

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 Integerobjeto contenedor envuelto Optional; el segundo uso ofNullable(T val)usa este método para crear Integerun objeto contenedor en el ejemplo Optional, pero es diferente del primer método en que si pasa Si el valor ingresado nulles 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 Optionalmétodos para obtener los elementos en el medio, entre los cuales getel método no se usa comúnmente, y se lanzará una excepción directamente si el elemento no existe NoSuchElementException. orElseEl método proporciona un valor predeterminado, que se devuelve si el elemento está vacío. orElseGetEl parámetro de es una Supplierinterfaz funcional, que puede devolver cualquier tipo de valor y devuelve el resultado de este método si el elemento está vacío. Al último orElseThrowse 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 Optionalsi el valor en existe.Si hay Consumerun método de instancia que pasa el valor envuelto a la interfaz en el parámetro, este Consumerse 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方法

这个方法的作用是按给定的条件将结果按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]}

Supongo que te gusta

Origin juejin.im/post/7234894309995921469
Recomendado
Clasificación