关于java:我是如何理解Java8-Stream

8次阅读

共计 5544 个字符,预计需要花费 14 分钟才能阅读完成。

之前看了许多介绍 Java8 Stream 的文章, 然而首次接触真的是难以了解 (我悟性比拟低), 没方法只能 ” 死记硬背 ”, 然而昨天我打王者光荣(那一局我赢了, 牛魔全场 MVP) 的时候, 忽然爆发了灵感, 感觉之前没有了解透彻的一下子就了解透彻了。所以决定用简略的形式来回顾下我认为的 java8 Stream.

lambda 表达式

语法

lambda 表达式是 Stream API 的基石,所以想要学会 Stream API 的应用,必须先要了解 lambda 表达式, 这里对 lambda 做一个简略回顾。

咱们经常会看到这样的代码

Arrays.sort(new Integer[]{1, 8, 7, 4}, new Comparator<Integer>() {
   @Override
   public int compare(Integer first, Integer second) {return first.compareTo(second);
   }
});

下面这种写法就是应用了匿名类,咱们常常会应用匿名类的形式,因为咱们只运行一次,不想它始终存在。尽管说 lambda 表达式是为了什么所谓的函数式编程,也是大家在社区千呼万唤才进去的,然而在我看来就是为了方 (偷) 便(懒)。

下面的代码写着麻烦,然而转换成上面这样的呢?

Arrays.sort(new Integer[]{1, 8, 7, 4},
    (first,second) -> first.compareTo(second));

这样看着多清新,而且把一些不必要的细节都屏蔽了。对于这种只蕴含一个形象办法的接口,你能够通过 lambda 接口来创立该接口的对象,这种接口被称为函数式接口。

lambda 表达式引入了一个新的操作符:->, 它把 lambda 表达式分为了 2 局部

(n) -> n*n

左侧指定表达式所需的参数,如果不须要参数,也能够为空。右侧是 lambda 代码块, 它指定 lambda 表达式的动作。

须要留神的是如果办法中只有一个返回的时候不必申明,默认会返回。如果有分支返回的时候须要都进行申明。

(n) -> {if( n <= 10) 
        return n*n;
    return n * 10;
}

办法援用以及结构器援用

办法援用

有些时候,先要传递给其余代码的操作曾经有实现的办法了。比方 GUI 中先要在按钮被点击时打印 event 对象,那么能够这样调用

button.setOnAction(event -> System.out.println(event));

这个时候我想偷懒,我不想写 event 参数,因为只有一个参数,jvm 不能帮帮我吗? 上面是批改好的代码


button.setOnAction(System.out::println);

表达式 System.out::println 是一个办法援用,等同于 lambda 表达式 x -> System.out.println(x):: 操作符将办法名和对象或类的名字宰割开来,以下是三种次要的应用状况:

  1. 对象:: 实例办法
  2. 类:: 静态方法
  3. 类:: 实例办法

前两种状况,办法援用等同于提供办法参数的 lambda 表达式。比方Math::pow ==== (x,y) -> Math.pow(x,y)

第三种状况,第一个参数会称为执行办法的对象。比方String::compareToIgnoreCase ==== (x,y) -> x.compareToIgnoreCase(y)

还有this::equals ==== x -> this.equals(x),super::equals ==== super.equals(x)

结构器援用

List<String> strList = Arrays.asList("1","2","3");
Stream<Integer> stream =  strList.stream().map(Integer::new);

下面代码的 Integer::new 就是结构器援用,不同的是在结构器援用中办法名是 new。如果存在多个结构器,编译器会从上下文推断并找出适合的那一个。

StreamAPI

Stream 这个单词翻译过去就是流的意思,溪流的流,水流的流。

在我看来 stream 就像是下面的图一样,最开始的数据就是小水滴,它通过各种 ” 拦截器 ” 的解决之后, 有的小水滴被抛弃,有的变大了,有的加上了色彩,有的变成了三角形。最初它们都变成了带有色彩的圆。最初被咱们放到后果集中。咱们很多时候写的代码是这样的:遍历一个汇合, 而后对汇合的元素进行判断或者转换,满足条件的退出到新的汇合外面去, 这种解决形式就和下面的图是一样的。先来看一段代码

Map<String,Map<String,Integer>> resultMap = new HashMap<>();
Map<String,Integer> maleMap = new HashMap<>();
Map<String,Integer> femaleMap = new HashMap<>();

resultMap.put("male", maleMap);
resultMap.put("female",femaleMap);

for(int i = 0; i < list.size(); i++) {Person person = list.get(i);
    String gender = person.getGender();
    String level = person.getLevel();
    switch (gender) {
        case "male":
            Integer maleCount;
            if("gold".equals(level)) {maleCount = maleMap.get("gold");
                maleMap.put("gold", null != maleCount ? maleCount + 1 : 1);
            } else if("soliver".equals(level)){maleCount = maleMap.get("soliver");
                maleMap.put("soliver", null != maleCount ? maleCount + 1 : 1);
            }
            break;

        case "female":
            Integer femaleCount;
            if("gold".equals(level)) {femaleCount = femaleMap.get("gold");
                femaleMap.put("gold", null != femaleCount ? femaleCount + 1 : 1);
            } else if("soliver".equals(level)){femaleCount = femaleMap.get("soliver");
                femaleMap.put("soliver", null != femaleCount ? femaleCount + 1 : 1);
            }
            break;

    }
}

下面的代码作用是统计不同性别的工程师职级的人数, 在 Java StreamAPI 进去之前,这样相似的业务代码在零碎中应该是随处可见的, 手打下面的代码我大略花了两分钟, 有了 Stream 之后,我偷了个懒

Map<String,Map<String,Integer>> result = list.stream().collect(
    Collectors.toMap(person -> person.getGender(),
        person -> Collections.singletonMap(person.getLevel(), 1),
        (existValue,newValue) -> {HashMap<String,Integer> newMap = new HashMap<>(existValue);
            newValue.forEach((key,value) ->{if(newMap.containsKey(key)) {newMap.put(key, newMap.get(key) + 1);
                } else {newMap.put(key, value);
                }
            });
            return newMap;
        })
);

或者改成这样的代码

Map<String,Map<String,Integer>> result =  stream.collect(
    Collectors.groupingBy(
        Person::getGender, 
        Collectors.toMap(person->person.getLevel(), 
            person -> 1,
            (existValue,newValue) -> existValue + newValue
        )
    )
);

不仅代码块缩小了许多,甚至逻辑也更清晰了。真的是用 stream 一时爽,始终用一爽快呀。

Stream 作为流,它能够是无限的能够是有限的,当然咱们用得最多的还是无限的流(for 循环就是无限的流), 如下面那张图一样,咱们能够对流中的元素做各种各样常见的解决。比方求和,过滤,分组,最大值,最小值等常见解决,所以当初就开始应用 Stream 吧

Stream 的个性

  1. Stream 本人不会存储元素, 元素可能被存储在底层汇合中,或者被生产进去。
  2. Stream 操作符不会扭转源对象, 相同,他们会返回一个持有新对象的 stream
  3. Stream 操作符是提早执行的,可能会等到须要后果的时候才去执行。

Stream API

函数式接口 参数类型 返回类型 形象办法名 形容 其余办法
Runnable void run 执行一个没有参数和返回值的操作
Supplier<T> T get 提供一个 T 类型的值
Counsumer<T> T void accept 解决一个 T 类型的值 chain
BiConsumer<T,U> T,U void accept 解决 T 类型和 U 类型的值 chain
Function<T,R> T R apply 一个参数类型为 T 的函数 compose,andThen,identity
BiFunction<T,U,R> T,U R apply 一个参数类型为 T 和 U 的函数 andThen
UnaryOperator<T> T T apply 对类型 T 进行的一元操作 compose,andThen,identity
BinaryOperator<T> T,T T apply 对类型 T 进行二元操作 andThen
Predicate<T> T boolean test 一个计算 boolean 值的函数 And,or,negate,isEqual
BiPredicate<T,U> T,U boolean test 一个含有两个参数,计算 boolean 值的函数 and,or,negate

map()和 flatMap()的区别

应用 map 办法的时候,相当于对每个元素利用一个函数,并将返回的值收集到新的 Stream 中。

Stream<String[]>    -> flatMap ->    Stream<String>
Stream<Set<String>>    -> flatMap ->    Stream<String>
Stream<List<String>>    -> flatMap ->    Stream<String>
Stream<List<Object>>    -> flatMap ->    Stream<Object>

{{1,2}, {3,4}, {5,6} } -> flatMap -> {1,2,3,4,5,6}

两头操作以及完结操作

Stream 上的所有操作分为两类:两头操作和完结操作,两头操作只是一种标记(调用到这类办法,并没有真正开始流的遍历。),只有完结操作才会触发理论计算。简略的说就是 API 返回值依然是 Stream 的就是两头操作,否则就是完结操作。

如何 debug

  1. 请应用代码段, 比方 IntStream.of(1,2,3,4,5).fiter(i -> {return i%2 == 0;}) 将断点打在代码段上即可。
  2. 援用办法也能够进行调试,在 isDouble 中打上断点比方IntStream.of(1,2,3,4,5).fiter(MyMath::isDouble)

那些不好了解的 API

  1. reduce()

咱们以前做累加是如何实现的呢?


int sum = 0;
for(int value in values) {sum = sum + value;}

当初改成 stream 的形式来实现

values.stream().reduce(Integer::sum);

这个 reduce()办法就是一个二元函数: 从流的前两个元素开始,一直将它利用到流中的其余元素上。

如何写好 Stream 代码

stream API 就是为了不便而设计的, 在 sql 层面并不不便解决的数据能够通过 stream 来实现分组,聚合,最大值,最小值,排序,求和等等操作。所以不要把它想得太简单,只管写就好了。总有那么一天你纯熟了就能够写出简洁得代码。或者从当初开始把你我的项目中的大量 for 循环革新成 stream 形式。

代码示例

原本想写大段代码来款式到 stream API 的转换,然而想了想齐全没有必要,github 上找了 hutool 工具类的局部代码来实现转换示例。(能够通过这种形式来进步 stream api 的能力)

  1. 计算每个元素呈现的次数(请先设想下 jdk7 怎么实现)
代码成果:[a,b,c,c,c]  -> a:1,b:1,c:3

Arrays.asList("a","b","c","c","c").stream().collect(Collectors.groupingBy(str->str, Collectors.counting()));
  1. 以特定分隔符将汇合转换为字符串,并增加前缀和后缀(请先设想下 jdk7 怎么实现)
List<String> myList = Arrays.asList("a","b","c","c","c");
myList.stream().collect(Collectors.joining(",","{","}"));
  1. 判断列表不全为空(请先设想下 jdk7 怎么实现)
myList.stream().anyMatch(s -> !s.isEmpty());
正文完
 0