前言

  1. 由于项目中用到了比较多有关于 Java8 中新的东西,一开始自己只是会写,但是写起来不太顺,然后就在网上找到了一个很好的关于Java8新特性的视频,所以就进行了学习了一下,以下是自己对 lambda 表达式和 Stream API 的笔记和相应的理解。 [视频地址](https://www.bilibili.com/video/av21399242),有兴趣的可以自行观看。

Java8 新特性

  1. 速度更快 更换了数据结构,内存结构(JVM)
  2. 代码更少了(Lambda表达式)
  3. 强大的Stream API
  4. 便于并行 fork join (串行切换并行)
  5. 最大化减少空指针异常 Optional

    Lambda表达式

    在说 Lambda 之前,首先要说的是函数式接口。这个可以说是为了 Lambda 表达式而存在的一个东西。那么什么是函数式接口?

    函数式接口

    定义: 接口中只有一个抽象接口。 像 java.util.function 下的所有接口都是函数式接口。Java1.8提供@FunctionalInterface检测一个接口是否是一个函数式接口。 eg: java.util.function 包下的 Consumer 接口代码

    1. @FunctionalInterface
    2. public interface Consumer<T> {
    3. void accept(T t);
    4. // jdk 1.8 接口可以有默认实现
    5. default Consumer<T> andThen(Consumer<? super T> after) {
    6. Objects.requireNonNull(after);
    7. return (T t) -> { accept(t); after.accept(t); };
    8. }
    9. }

    了解了什么是函数式接口后,lambda 表达式就很好理解了。
    “->” 是 lambda 表达式的符号 左侧表示函数式接口中抽象方法的参数列表,右侧表示你对这个方法的实现。 举个例子eg:

    1. public class Test{
    2. public static void main(String[] args){
    3. Consumer consumer = x-> System.out.println(x);
    4. consumer.accept(1);
    5. }
    6. }
    7. 复制代码

    输出 1;

    四大函数式接口

    我们一般对函数式接口的使用的时候,都会对其进行封装。

    消费性接口

    Consumer 只有一个抽象方法名为 accept,参数列表只有一个泛型t,无返回值。参数的数据类型有类决定 eg:

    1. /**
    2. * @ClassName ConsumerTest
    3. * @Description 消费型接口, 消费字符串字段 打印输出
    4. * @Author ouyangkang
    5. * @Date 2019-02-18 15:46
    6. **/
    7. public class ConsumerTest {
    8. public static void main(String[] args) {
    9. test("hello",x-> System.out.println(x));
    10. }
    11. public static <T> void test(T t, Consumer<T> consumer) {
    12. consumer.accept(t);
    13. }
    14. }
    15. 复制代码

    输出:hello 如果需要多个参数列表的话,也可以在 java.util.function 包下找到相应的函数式接口 比如 ObjLongConsumer。其他的可以自行查看

    供给型接口

    Supplier 只有一个抽象方法名为 get,参数列表为空,有返回值,返回值得数据类型为T。

    1. /**
    2. * @ClassName SupplerTest
    3. * @Description 供给型接口 字符串拼接
    4. * @Author ouyangkang
    5. * @Date 2019-02-18 15:53
    6. **/
    7. public class SupplerTest {
    8. public static void main(String[] args) {
    9. String hello = test("hello ", () -> "word!");
    10. System.out.println(hello);
    11. }
    12. public static String test(String str,Supplier<String> supplier){
    13. return str + supplier.get();
    14. }
    15. }
    16. 复制代码

    输出为:hello word! 如果需要返回得数据为基本数据类型,可以在 java.util.function 包下找到相应的函数式接口 比如:getAsLong 其他的可以自行查看

    函数型接口

    Function 只有一个抽象方法名为 apply,参数列表只有一个参数为T,有返回值,返回值的数据类型为R。

    1. /**
    2. * @ClassName FunctionTest
    3. * @Description 函数式接口 将字符串转换成大写的
    4. * @Author ouyangkang
    5. * @Date 2019-02-18 16:01
    6. **/
    7. public class FunctionTest {
    8. public static void main(String[] args) {
    9. String test = test("hello", x -> x.toUpperCase());
    10. System.out.println(test);
    11. }
    12. public static String test(String str , Function<String,String> function){
    13. return function.apply(str);
    14. }
    15. }
    16. 复制代码

    输出为:HELLO 如果需要多个入参,然后又返回值的话,可以在 java.util.function 包下找到相应的函数式接口 比如 BiFunction。其他的可以自行查看

    断言型接口

    断言型又名判断型。 Predicate 只有一个抽象方法名为 test,参数列表只有一个参数为 T,有返回值,返回值类型为 boolean。

    1. /**
    2. * @ClassName PredicateTest
    3. * @Description 断言型接口,判断字符串大小是否大于6
    4. * @Author ouyangkang
    5. * @Date 2019-02-18 16:16
    6. **/
    7. public class PredicateTest {
    8. public static void main(String[] args) {
    9. boolean hello = test("hello", x -> x.length() > 6);
    10. System.out.println(hello);
    11. }
    12. public static boolean test(String str, Predicate<String> predicate){
    13. return predicate.test(str);
    14. }
    15. }

    输出为: false

    Stream API

    Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念。Stream中间操作,多个中间操作可以连接起来形成一个流水线,除非流水线上触发了终止操作,否则中间不会执行任何处理!而终止操作时会一次性全部处理,称为惰性处理。要进行流操作首先要获取流。有4中方法可以获取流。

  6. 获取流 通过集合系列提供的stream方法和 parallelStream()(并行流)方法获取流

    1. public static void main(String[] args) {
    2. List<Integer> list = new ArrayList<>();
    3. // 常用获取流的方式
    4. Stream<Integer> stream = list.stream();
    5. }
  7. 通过Arrays.stream() 将数组转换成流

    1. public static void main(String[] args) {
    2. int[] a = new int[]{1,2,3,4};
    3. IntStream stream = Arrays.stream(a);
    4. }
  8. 通过Stream.of今天方法创建流

    1. public static void main(String[] args) {
    2. Stream<Integer> stream = Stream.of(1, 2, 3);
    3. }
  9. 创建无限流

    1. public static void main(String[] args) {
    2. Stream<Integer> iterate = Stream.iterate(0, x -> x + 2);
    3. }

    所有的对流的操作可以分为4种,分别为筛选与分片,映射,排序,终结(归约,收集)

    筛选与分片

    操作有filter,distant,limit,skip。 filter : 过滤操作,方法参数为断言型接口 eg:

    1. public static void main(String[] args) {
    2. Stream<Integer> stream = Stream.of(1, 2, 3);
    3. stream.filter(x->x != 2).forEach(x-> System.out.println(x));
    4. }
    5. 复制代码

    输出:

    1. 1
    2. 3
    3. 复制代码

    distinct : 去重操作,方法无参数 limit : 获取前几条数据,方法参数为long skip : 跳过前多少条数据,然后获取后面所有的。 方法参数为long

    映射

    常用操作有 map ,flatMap。 map: 对原数据进行处理,并返回处理后的数据。 方法参数为函数型接口。 eg:

    1. public static void main(String[] args) {
    2. Stream<Integer> stream = Stream.of(1, 2, 3);
    3. stream.map(x->x*2).forEach(System.out::println);
    4. }

    输出:

    1. 2
    2. 4
    3. 6

    flatMap : 使原来流种的原有数据一个一个整合在另一个流中。方法参数为函数型接口,但是返回值为流。 eg:

    1. public static void main(String[] args) {
    2. List<String> list = Arrays.asList("a", "b", "c");
    3. List<String> list2 = Arrays.asList("f","d");
    4. list.stream().flatMap(x->list2.stream().map(y-> x + y)).forEach(System.out::println);
    5. }

    排序

    常用操作有sort自然排序,合sort参数为排序器的定制排序 自然排序eg:

    1. public static void main(String[] args) {
    2. Stream<Integer> stream = Stream.of(1, 2, 3);
    3. stream.sorted().forEach(System.out::println);
    4. }

    输出:

    1. 1
    2. 2
    3. 3
    4. 复制代码

    定制排序

    1. public static void main(String[] args) {
    2. Stream<Integer> stream = Stream.of(1, 2, 3);
    3. stream.sorted((x,y)->-Integer.compare(x,y)).forEach(System.out::println);
    4. }
    5. 复制代码

    输出:

    1. 3
    2. 2
    3. 1

    终止操作

  • allMatch 检查是否匹配所有元素 方法参数为断言型接口
  • anyMatch 检查是否匹配所有元素 方法参数为断言型接口
  • noneMatch 检查是否没有匹配所有元素 方法参数为断言型接口
  • findFirst 返回第一个元素 无方法参数
  • findAny 返回当前流的任意元素 无方法参数
  • count 返回流中的元素总个数 无方法参数
  • max 返回流的最大值 无方法参数
  • min 返回流中的最小值 无方法参数

    归约

    reduce : 归约 — 可以将流中的元素反复结合起来,得到一个值。 eg:

    1. public static void main(String[] args) {
    2. List<Integer> list1 = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
    3. Integer reduce = list1.stream().reduce(11, (x, y) -> x + y);
    4. System.out.println(reduce);
    5. }

    输出 : 66

    收集

    这个是非常常用的一个操作。 将流装换为其他形式。接收到一个Collector接口的实现,用于给Stream中的元素汇总的方法。用collect方法进行收集。方法参数为Collector。Collector可以由Collectors中的toList(),toSet(),toMap(Function(T,R) key,Function(T,R) value)等静态方法实现。

  • toList() 返回一个 Collector ,它将输入元素 List到一个新的 List 。

  • toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper) 返回一个 Collector ,它将元素累加到一个 Map ,其键和值是将所提供的映射函数应用于输入元素的结果。
  • toSet() 返回一个 Collector ,将输入元素 Set到一个新的 Set 。 eg:

User类

  1. @Data
  2. @ToString
  3. public class User {
  4. private String name;
  5. private Integer age;
  6. private Integer salary;
  7. }
  8. 复制代码
  1. public static void main(String[] args) {
  2. List<User> users = Arrays.asList(new User("张三", 19, 1000),
  3. new User("张三", 58, 2000),
  4. new User("李四", 38, 3000),
  5. new User("赵五", 48, 4000)
  6. );
  7. List<String> collect = users.stream().map(x -> x.getName()).collect(Collectors.toList());
  8. Set<String> collect1 = users.stream().map(x -> x.getName()).collect(Collectors.toSet());
  9. Map<Integer, String> collect2 = users.stream().collect(Collectors.toMap(x -> x.getAge(), x -> x.getName()));
  10. System.out.println(collect);
  11. System.out.println(collect1);
  12. System.out.println(collect2);
  13. }

输出:

  1. [张三, 张三, 李四, 赵五]
  2. [李四, 张三, 赵五]
  3. {48=赵五, 19=张三, 38=李四, 58=张三}

分组

  1. Collectors.groupingBy()方法是 返回 Collector “由基团”上的类型的输入元件操作实现 T ,根据分类功能分组元素。这个是非常常用的操作。 比如你要对名字相同的进行分组。 groupingBy(Function<? super T,? extends K> classifier) eg:
  1. public static void main(String[] args) {
  2. List<User> users = Arrays.asList(new User("张三", 19, 1000),
  3. new User("张三", 58, 2000),
  4. new User("李四", 38, 3000),
  5. new User("赵五", 48, 4000)
  6. );
  7. Map<String, List<User>> collect3 = users.stream().collect(Collectors.groupingBy(x -> x.getName()));
  8. System.out.println(collect3);
  9. }

输出:{李四=[User{name=’李四’, age=38, salary=3000}], 张三=[User{name=’张三’, age=19, salary=1000}, User{name=’张三’, age=58, salary=2000}], 赵五=[User{name=’赵五’, age=48, salary=4000}]}
当然还有其他的一些比较复杂的分组操作,实际代码看业务来进行实现。

总结

  1. java8中的lambda表达式可能一开始用的时候还不是很熟悉,但是只要熟悉了,你会发现非常的好用,而且lambda表达式结合stream API可以进行编写自己的工具类。在平常项目中可以非常的省时间,提高写代码的效率。我现在给出一个ListMap的工具类。
  1. public class CollectionStream {
  2. public static void main(String[] args) {
  3. List<User> users = Arrays.asList(new User("张三", 19, 1000),
  4. new User("张三", 58, 2000),
  5. new User("李四", 38, 3000),
  6. new User("赵五", 48, 4000)
  7. );
  8. Map<Integer, Integer> map = listToMap(users, x -> x.getAge(), x -> x.getSalary());
  9. System.out.println(map);
  10. }
  11. /**
  12. * @Author ouyangkang
  13. * @Description list 转map key不能相同 ,如果相同会报错。 方法对 源数据,key,value过滤null。
  14. * @Date 9:27 2019/2/19
  15. * @param source 源数据
  16. * @param key
  17. * @param value
  18. * @return java.util.Map<K,V>
  19. **/
  20. public static <DTO, K, V> Map<K, V> listToMap(List<DTO> source, Function<? super DTO, ? extends K> key, Function<? super DTO, ? extends V> value) {
  21. Objects.requireNonNull(source, "source not null");
  22. Objects.requireNonNull(key, "key not null");
  23. Objects.requireNonNull(value, "value not null");
  24. Map<K, V> map = source.stream()
  25. .filter(dto -> dto != null)
  26. .filter(dto -> key.apply(dto) != null)
  27. .filter(dto -> value.apply(dto) != null)
  28. .collect(Collectors.toMap(key, value));
  29. return map;
  30. }
  31. }

Java8的Stream写法

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png