一、Lambda表达式

1、简介

由编译器推断并帮你转换包装为一段可以传递的代码(将代码像数据一样进行传递)。
可以写出更简洁 、更灵活的代码。
作为 一种更紧凑的代码风格,使java的语言表达能力得到了提升。

2、lambda六大基础语法

  • Java8中引入了一个新的操作符”->”,该操作符称为箭头操作符或lambda操作符
  • 操作符左侧:lambda表达式的参数列表
  • 操作符右侧:lambda表达式中所需执行的功能,即lambda体
  • 使用lambda的前提:依赖于函数式接口,lambda表达式即对函数式接口的实现

    1. /**
    2. * 语法1:无参数,无返回值
    3. */
    4. @Test
    5. public void test() {
    6. Runnable runnable1 = ()->{
    7. System.out.println("lambda");
    8. };
    9. runnable1.run(); // 输出:lambda
    10. }
    11. /**
    12. * 语法2:有一个 参数,无返回值
    13. */
    14. @Test
    15. public void test1() {
    16. Consumer consumer = (x) -> {
    17. System.out.println(x);
    18. };
    19. consumer.accept("哈哈哈哈"); // 输出:哈哈哈哈
    20. }
    21. /**
    22. * 语法3:当只有一个参数时,可以省略括号
    23. */
    24. @Test
    25. public void test2() {
    26. Consumer consumer = x -> {
    27. System.out.println(x);
    28. };
    29. consumer.accept("哈哈哈哈"); // 输出:哈哈哈哈
    30. }
    31. /**
    32. * 语法4:有两个及两个以上参数,有返回值,并且lambda体有多语句时
    33. */
    34. @Test
    35. public void test3() {
    36. Comparator<Integer> comparator = (x, y) -> {
    37. System.out.println(x+"------"+y);
    38. return Integer.compare(x,y);
    39. };
    40. int compare = comparator.compare(1, 2);
    41. System.out.println(compare); // 输出:-1
    42. }
    43. /**
    44. * 语法5:当lambda体只有一条语句时,可以省略大括号 和 return
    45. */
    46. @Test
    47. public void test4() {
    48. Comparator<Integer> comparator = (x, y) -> Integer.compare(x,y);
    49. int compare = comparator.compare(2, 1);
    50. System.out.println(compare); // 输出:1
    51. }
    52. /**
    53. * 语法6:类型推断(编译器上下文推断)
    54. * lambda表示的参数列表的数据类型可以省略不写,JVM编译器通过上下文推断数据类型
    55. */
    56. @Test
    57. public void test39() {
    58. Comparator<Integer> comparator = (x, y) -> Integer.compare(x,y);
    59. int compare = comparator.compare(2, 1);
    60. System.out.println(compare); // 输出:1
    61. }

    3、四大内置核心函数式接口

    image.png

    3.1 Supplier接口

    java.util.function.Supplier()接口仅包含一个无参的方法:T get()。用来获取一个泛型参数指定类型的对象数据。由于这是一个函数式接口,这也就意味着对应的lambda表达式需要“对外提供”一个符合泛型类型的对象数据

    1. public class SupplierDemo {
    2. // 定义一个方法,方法的参数传递Supplier,泛型指定String类型,所以get()方法会返回一个String类型的数据
    3. public static void getString(Supplier<String> supplier) {
    4. String s = supplier.get();
    5. System.out.println(s);
    6. }
    7. public static void main(String[] args) {
    8. getString(() -> "韩广凯"); // 韩广凯
    9. }
    10. }

    3.2 Consumer接口

    1. public class ConsumerDemo {
    2. // Consumer是一个消费型接口,泛型指定什么类型,accept()就可以消费什么类型的数据
    3. // 至于怎么消费(使用),则需要自定义
    4. public static void consumerTest(String str,Consumer<String> consumer) {
    5. consumer.accept(str);
    6. }
    7. public static void main(String[] args) {
    8. consumerTest("周杰伦",k -> System.out.println(k)); // 周杰伦
    9. }
    10. }

    3.2.1 默认方法:andThen()

    如果一个方法的参数和返回值全都是Consumer类型,那么就可以实现效果:消费数据的时候,首先做一个操作,然后再做一个操作,实现组合,而这个方法就是Consumer接口中的default方法andThen(),下面是JDK源码:

    1. default Consumer<T> andThen(Consumer<? super T> after) {
    2. Objects.requireNonNull(after);
    3. return (T t) -> { accept(t); after.accept(t); };
    4. }

    说明:andThen()需要要两个Consumer接口,可以把两个Consumer接口组合到一起,再对数据进行消费。

    1. public class ConsumerTestAndThen {
    2. // 定义一个方法,传递一个String类型的参数,再传递两个Consumer类型的参数,泛型使用String
    3. public static void test(String str, Consumer<String> consumer,Consumer<String> consumer1) {
    4. consumer.accept(str);
    5. consumer1.accept(str);
    6. }
    7. public static void main(String[] args) {
    8. test("周杰伦",k -> System.out.println(k+"方文山"),v -> System.out.println(v+"昆凌") );
    9. // 输出:
    10. // 周杰伦方文山
    11. // 周杰伦昆凌
    12. }
    13. }
    1. public class ConsumerTestAndThen {
    2. // 定义一个方法,传递一个String类型的参数,再传递两个Consumer类型的参数,泛型使用String
    3. public static void test(String str, Consumer<String> consumer,Consumer<String> consumer1) {
    4. // 使用andThen()方法,把两个Consumer接口连在一起,再消费数据
    5. consumer.andThen(consumer1).accept(str);
    6. }
    7. public static void main(String[] args) {
    8. test("周杰伦",k -> System.out.println(k+"方文山"),v -> System.out.println(v+"昆凌") );
    9. // 输出:
    10. // 周杰伦方文山
    11. // 周杰伦昆凌
    12. }
    13. }

    3.3 Predicate接口

    有时候我们需要对某种类型的数据进行判断,从而得到一个 boolean值结果,这时可以使用java.util.function.Predicate接口。
    Predicate接口中包含一个抽象方法:boolean test(T t)。用于条件判断的场景。 ```java public class PredicateDemo {

    public static boolean pre(String str,Predicate predicate) {

    1. return predicate.test(str);

    }

    public static void main(String[] args) {

    1. // 定义一个字符串
    2. String str = "abcd";
    3. boolean pre = pre(str, k ->
    4. // 判断传入字符串长度是否大于5
    5. k.length() > 5
    6. );
    7. System.out.println(pre); //false

    }

}

  1. <a name="jw61Z"></a>
  2. #### 3.3.1 Predicate接口中的三个默认方法——and()
  3. 既然是条件判断,就会存在与、或、非三种常见的逻辑关系,其中将两个Predicate条件使用“与”逻辑连接起来实现“并且”的效果时,可以使用default方法and。
  4. ```java
  5. public class PredicateDemo_and {
  6. /**
  7. * 定义一个方法
  8. * 传递两个参数
  9. * 传递一个String类型的参数
  10. * 传递两个Predicate
  11. * 第一个Predicate判断字符串长度大于2
  12. * 第二个Predicate判断字符串包含“H”
  13. * 两个条件必须同时满足
  14. */
  15. public static boolean checkString(String str, Predicate<String> predicate,Predicate<String> predicate2) {
  16. return predicate.test(str) && predicate2.test(str);
  17. }
  18. // 测试
  19. public static void main(String[] args) {
  20. boolean b = checkString("HGK", k -> k.length() > 2, k -> k.contains("H"));
  21. System.out.println(b);
  22. }
  23. }
  1. public class PredicateDemo_and {
  2. /**
  3. * 定义一个方法
  4. * 传递两个参数
  5. * 传递一个String类型的参数
  6. * 传递两个Predicate
  7. * 第一个Predicate判断字符串长度大于2
  8. * 第二个Predicate判断字符串包含“H”
  9. * 两个条件必须同时满足
  10. */
  11. public static boolean checkString(String str, Predicate<String> predicate,Predicate<String> predicate2) {
  12. return predicate.and(predicate2).test(str);
  13. }
  14. // 测试
  15. public static void main(String[] args) {
  16. boolean b = checkString("HGK", k -> k.length() > 2, k -> k.contains("H"));
  17. System.out.println(b);
  18. }
  19. }

3.3.2 Predicate接口中的三个默认方法——or()

  1. public class PredicateDemo_or {
  2. /**
  3. * 定义一个方法
  4. * 传递两个参数
  5. * 传递一个String类型的参数
  6. * 传递两个Predicate
  7. * 第一个Predicate判断字符串长度大于5
  8. * 第二个Predicate判断字符串包含“H”
  9. * 两个条件满足一个即可
  10. */
  11. public static boolean checkString(String str, Predicate<String> predicate,Predicate<String> predicate2) {
  12. return predicate.or(predicate2).test(str);
  13. }
  14. // 测试
  15. public static void main(String[] args) {
  16. boolean b = checkString("HGK", k -> k.length() > 5, k -> k.contains("H"));
  17. System.out.println(b);
  18. }
  19. }

3.3.3 Predicate接口中的三个默认方法——negate():取反

  1. public class Predicate_negate {
  2. /**
  3. * 字符串大于5,返回false否则返回true
  4. * @param str
  5. * @param predicate
  6. * @param
  7. * @return
  8. */
  9. public static boolean checkString(String str, Predicate<String> predicate) {
  10. return predicate.negate().test(str);
  11. }
  12. // 测试
  13. public static void main(String[] args) {
  14. boolean b = checkString("HGK", k -> k.length() > 5);
  15. System.out.println(b);
  16. }
  17. }

3.4、Function接口

java.util.function.Function接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。

3.4.1 抽象方法:apply

Function接口中最主要的抽象方法为:R apply(T t),根据类型T的参数获取类型R的结果。
使用场景例如:将String类型转换为Integer类型

  1. public class FunctionDemo {
  2. /**
  3. * 定义一个方法
  4. * 方法的参数传递一个字符串类型的整数
  5. * 方法的参数传递一个Function接口,泛型使用<String,Integer>
  6. * 使用Function接口中的方法apply(),把字符串类型的整数,转换为Integer类型的整数
  7. */
  8. public static void change(String s, Function<String,Integer> function) {
  9. Integer apply = function.apply(s);
  10. System.out.println(apply);
  11. }
  12. public static void main(String[] args) {
  13. change("123",k -> Integer.parseInt(k)); // 123
  14. }
  15. }

3.4.2 Function接口中的默认方法——andThen()

用来进行组合操作,该方法同样用于“先做什么,再做什么”的场景,和Consumer中的andThen差不多。

  1. public class FunctionDemo_andThen {
  2. /**
  3. * 把String类型“123”,转换成Integer类型,把转换后的结果加10
  4. * 把增加后的Integer类型的数据,转换为String类型
  5. */
  6. public static void change(String str, Function<String, Integer> function,Function<Integer,String> function2) {
  7. String apply = function.andThen(function2).apply(str);
  8. System.out.println(apply);
  9. }
  10. public static void main(String[] args) {
  11. change("10",k -> Integer.parseInt(k)+10,k -> k.toString());
  12. }
  13. }

4、方法引用、构造器引用的使用

若lambda体中的内容有方法已经实现了,我们可以使用方法引用
可以理解为方法引用是lambda表达式的另外一种表现形式
对象名::实例方法名
类::静态方法名
类::实例方法名

5、lambda的延迟执行

有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费,而lambda表达式是延迟执行的,这正好可以作为解决方案,提升性能。
lambda使用前提:必须存在函数式接口

6、使用lambda作为参数和返回值

如果抛开实现原理不说,java中的lambda表达式可以被当做是匿名内部类的替代品,如果方法的参数是一个函数式接口类型,那么就可以使用lambda表达式进行 替代,使用lambda表达式作为方法参数,其实就是使用函数式接口作为方法参数

二、stream

Stream是java8中处理集合的关键抽象概念,他可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API对集合数据进行操作,就类似于使用SQL执行的数据库查询,也可以使用Stream API来并行执行操作,
简而言之,StreamAPI提供了一种高效且易于使用的处理数据的方式。

1、引入

  1. /**
  2. * 传统方式
  3. */
  4. @Test
  5. public void test() {
  6. List<String> list = Arrays.asList("张强", "周芷若", "张三丰", "张无忌", "周杰伦");
  7. // 1.过滤,只要以张开头的集合
  8. ArrayList<String> list1 = new ArrayList<>();
  9. for (String s : list) {
  10. if (s.startsWith("张")) {
  11. list1.add(s);
  12. }
  13. }
  14. // 2.过滤,要名字长度为3的集合
  15. ArrayList<String> list2 = new ArrayList<>();
  16. for (String s : list1) {
  17. if (s.length() == 3) {
  18. list2.add(s);
  19. }
  20. }
  21. // 3.遍历集合
  22. Iterator<String> iterator = list2.iterator();
  23. while (iterator.hasNext()) {
  24. String next = iterator.next();
  25. System.out.println(next);
  26. }
  27. }
  28. /**
  29. * 使用stream方式
  30. */
  31. @Test
  32. public void test1() {
  33. List<String> list = Arrays.asList("张强", "周芷若", "张三丰", "张无忌", "周杰伦");
  34. list.stream()
  35. .filter(name -> name.startsWith("张"))
  36. .filter(name -> name.length() == 3)
  37. .forEach(System.out::println);
  38. }

2、流式思想概述

Stream(流)是一个来自数据源的元素队列

  • 元素是特定类型的对象,形成一个对类,Java中的Stream并不会存储元素,而是按需计算
  • 数据源的来源,可以是集合、数组等

和以前的Collection操作不同,Stream操作还有两个基础的特征:

  • Pipelining:中间操作都会返回流对象本身,这样多个操作可以串联成一个管道,如同流式风格(fluentstyle)。这样做可以对操作进行优化,比如延迟执行和短路。
  • 内部迭代:以前对集合遍历都是通过iterator或者增强for的方式 。显示的在集合外部进行迭代,这叫做外部迭代,Stream提供了内部迭代的方式,流可以直接调用遍历的方法。

当使用一个流的时候,通常包括三个基本步骤:获取一个数据源——>数据转换——>执行操作获取想要的结果,每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换),这就允许对其操作可以项链条一样排列,形成一个管道。

2.1 获取流(获取Stream流的两种方式)

java.util.stream.Stream是java 8新加入的最常用的流接口(这并不是一个函数式接口)
获取一个流非常简单,有以下几种常用的方式:

  • 所有的Collection集合都可以通过stream默认方式获取流
  • stream接口的静态方法of()可以获取数组对应流

    1. public class StreamDemo1 {
    2. public static void main(String[] args) {
    3. /**
    4. * 把集合转换成stream流
    5. */
    6. ArrayList<String> list = new ArrayList<>();
    7. Stream<String> stream = list.stream();
    8. Set<String> set = new HashSet<>();
    9. Stream<String> stream1 = set.stream();
    10. Map<Integer,String> map = new HashMap<>();
    11. // 获取键,存到一个set中
    12. Set<Integer> keySet = map.keySet();
    13. Stream<Integer> stream2 = keySet.stream();
    14. // 获取值,存储到Collection集合中
    15. Collection<String> valuesKey = map.values();
    16. Stream<String> stream3 = valuesKey.stream();
    17. // 获取键值对,即键与值的映射关系
    18. Set<Map.Entry<Integer, String>> entries = map.entrySet();
    19. Stream<Map.Entry<Integer, String>> stream4 = entries.stream();
    20. /**
    21. * 把数组转换成stream流
    22. */
    23. Stream<Integer> stream5 = Stream.of(1,2,3,4,5);
    24. // 可变参数可以传递数组
    25. Integer[] arr = {6,3,4,5,6,6};
    26. Stream<Integer> stream6 = Stream.of(arr);
    27. }
    28. }

    3、Stream()流中的常用方法

    流模型的操作很丰富,这里介绍一些常用API,这些方法可以被分为两种:
    延迟方法:返回值类型仍然是Stream接口自身类型的方法,因此支持链式调用(除了终结方法外 ,其余方法均为延迟方法)
    终结方法:返回值类型不再是Stream接口自身类型的方法,因此不再支持类似StringBuilder那样的链式调用。

    Stream的操作三个步骤

  1. 创建Stream
    1. 一个数据源(如:集合,数组),获取一个流
  2. 中间操作
    1. 一个中间操作链,对数据源的数据进行处理
  3. 终止操作(终端操作)

    1. 一个终止操作,执行中间操作链,并产生结果

      简介:

      可以通过Collection系列集合提供的stream()或parallelStream()获取流(第一个获取的是串行流,第二个获取的是并行流)

      1.逐一处理:forEach

      虽然方法名字叫forEach,但是与for循环中的“for-each”昵称不同

      1. void forEach(Consumer<? super T> action);

      该方法接收一个Consumer接口函数,会将每一个 流元素交给该函数进行处理

      1. public class StreamForEach {
      2. /**
      3. * forEach()
      4. * void forEach(Consumer<? super T> action);
      5. * 该方法接收一个Consumer接口函数,会将每一个 流元素交给该函数进行处理
      6. * 简单记:
      7. * forEach方法,用来遍历流中的数据
      8. * 是一个终结方法,遍历之后就不能继续调用Stream流中的其他方法
      9. */
      10. public static void main(String[] args) {
      11. Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
      12. stream.forEach(k -> System.out.println(k));
      13. }
      14. }

      2、过滤方法:filter

      可以通过filter方法将一个流转换成另一个子集流。方法签名:

      1. Stream<T> filter(Predicate<? super T> predicate)

      该接口接收一个Predicate函数式接口参数(可以是一个Lambda或方法引用)作为 筛选条件。
      该方法将会产生一个boolean值结果,代表指定的条件是否满足,如果结果为true,那么Stream流的filter方法将会留用元素,如果为false,那么filter方法将会舍弃元素。

      1. public class StreamFilter {
      2. public static void main(String[] args) {
      3. Stream<String> stream = Stream.of("周杰伦", "周芷若", "周杰", "方文山");
      4. Stream<String> stream1 = stream.filter(name -> name.startsWith("周"));
      5. stream1.forEach(name -> System.out.println(name));
      6. }
      7. }

      3、映射:map

      如果需要将流中的云宿舍映射到另一个流中,可以使用map方法,方法签名:

      1. <R> Stream<R> map(Function<? super T,? extends R> mapper);

      该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流

      1. public class StreamMap {
      2. public static void main(String[] args) {
      3. Stream<String> stream = Stream.of("1", "2", "3", "4");
      4. stream.map(str -> Integer.parseInt(str) + 1).forEach(System.out::println);
      5. }
      6. }

      4、统计个数:count

      正如旧集合Collection当中的size方法一样,流提供count方法来数一数其中的元素个数

      1. long count();

      该方法返回一个long值,代表元素个数(不再像旧集合那样是Int值)

      1. public class StreamCount {
      2. public static void main(String[] args) {
      3. Stream<String> stream = Stream.of("1", "2", "3", "4");
      4. long count = stream.count();
      5. System.out.println(count); // 4
      6. }
      7. }

      5、取用前几个:limit

      limit()方法可以对流进行截取,只取用前n个,方法签名:

      1. Stream<T> limit(long maxSize);

      参数是一个long型,如果集合当前长度大于参数则进行截取,否则不进行操作。

      1. public class StreamLimit {
      2. public static void main(String[] args) {
      3. Stream<String> stream = Stream.of("1", "2", "3", "4");
      4. stream.limit(2).forEach(System.out::println);
      5. }
      6. }

      6、跳过前几个:skip

      如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流:

      1. Stream<T> skip(long n);

      参数是一个long型,如果流的长度大于n,则跳过前n个,否则将会得到一个长度为0的空流。

      1. public class StreamSkip {
      2. public static void main(String[] args) {
      3. Stream<String> stream = Stream.of("1", "2", "3", "4");
      4. stream.skip(2).forEach(System.out::println);
      5. }
      6. }

      7、组合 :concat

      如果有两个流,希望合并成为一个流,那么可以使用Stream接口的静态方法concat()

      1. static <T> Stream<T> concat(Stream<? extends T> a,Stream<? extends T> b);

      备注:这是一个 静态方法,与java.lang.String当中的concat方法是不同的

      1. public class StreamConcat {
      2. public static void main(String[] args) {
      3. Stream<String> stream = Stream.of("1", "2", "3", "4");
      4. Stream<String> stream1 = Stream.of("周杰伦", "方文山", "昆凌");
      5. Stream.concat(stream, stream1).forEach(System.out::println);
      6. // 1
      7. // 2
      8. // 3
      9. // 4
      10. // 周杰伦
      11. // 方文山
      12. // 昆凌
      13. }
      14. }

      三、方法引用

      在使用Lambda表达式的时候,我们实际上传递进行的代码就是一种解决方案:拿什么参数做什么操作,那么考虑一种情况:如果我们在Lambda中所指定的操作方案,已经有地方存在相同方案,那么是否还有必要再写重复逻辑。