Java 8 新特性之Lambda表达式和Stream流

红太狼 2022-12-03 12:58 425阅读 0赞

1. 为什么需要函数式编程

在很长的一段时间里,Java一直是面向对象的语言,一切皆对象,如果想要调用一个函数,函数必须属于一个类或对象,然后在使用类或对象进行调用。但是在其它的编程语言中,如js,c++,我们可以直接写一个函数,然后在需要的时候进行调用,即可以说是面向对象编程,也可以说是函数式编程。

从功能上来看,面向对象编程没什么不好的地方,但是从开发的角度来看,面向对象编程会多写很多可能是重复的代码行。比如创建一个Runnable的匿名类的时候:

  1. Runnable runnable = new Runnable() {
  2. @Override
  3. public void run() {
  4. System.out.println("do something...");
  5. }
  6. };

使用函数式编程之后:

  1. // 一行即可
  2. Runnable runnable = () -> System.out.println("do something...");

2.Lambda表达式:

Lambda表达式取代了匿名类,取消了模板,允许用函数式风格编写代码。这样的优势是:可读性更好,表达更清晰。

在Java生态系统中,函数式表达与对面向对象的全面支持是个激动人心的进步。将进一步促进并行第三方库的发展,充分利用多核CPU。

在Java 8里面,所有的Lambda的类型都是一个接口,而Lambda表达式本身,也就是”那段代码“,需要是这个接口的实现。这是理解Lambda的一个关键所在,简而言之就是,Lambda表达式本身就是一个接口的实现

  1. // 无参数, 返回1+2的结果
  2. () -> 1+2;
  3. // 接收一个参数(数字类型),返回其2倍的值
  4. x -> 2 * x;
  5. // 接收2个参数(数字),返回表达式运算的结果
  6. (x, y) -> x + y;
  7. // 多个语句要用大括号包裹, 并且返回值要用return指明
  8. (x, y) -> {
  9. int result = x + y;
  10. System.out.print(result);
  11. return result;
  12. };

Lambda表达式有如下约定:

  • 一个 Lambda 表达式可以有零个或多个参数;
  • 参数的类型既可以明确声明,也可以根据上下文来推断。例如:(int a)(a)效果相同;
  • 所有参数需包含在圆括号内,参数之间用逗号相隔。例如:(a, b)(int a, int b)(String a, int b, float c);
  • 空圆括号代表参数集为空。例如:() -> 42;
  • 当只有一个参数,且其类型可推导时,圆括号()可省略。例如:a -> return a*a;
  • Lambda 表达式的主体可包含零条或多条语句;
  • 如果 Lambda 表达式的主体只有一条语句,花括号{}可省略。匿名函数的返回类型与该主体表达式一致;
  • 如果 Lambda 表达式的主体包含一条以上语句,则表达式必须包含在花括号{}中(形成代码块)。匿名函数的返回类型与代码块的返回类型一致,若没有返回则为空。

1. 无参数的Lambda表达式

1) 如果只有一行的时候,不需要加 分号;

Thread类的使用

  1. public class Main {
  2. public static void main(String[] args) {
  3. new Thread(
  4. () -> System.out.println("this is thread")
  5. ).start();
  6. }
  7. }

Runnable接口的使用,调用的是run() 方法

  1. public class Main {
  2. public static void main(String[] args) {
  3. Runnable myrun = () -> System.out.println("this is test");
  4. myrun.run();
  5. }
  6. }

2) 如果是多行输出的话,则需要 中括号 括起来 { },并且需要加 分号

  1. public class Main {
  2. public static void main(String[] args) {
  3. new Thread(
  4. () -> {
  5. System.out.println("this is thread");
  6. System.out.println("this is sencond");
  7. }
  8. ).start();
  9. }
  10. }

2. 带参数的Lambda表达式

  1. import java.util.Arrays;
  2. import java.util.Collections;
  3. import java.util.List;
  4. public class Main {
  5. public static void main(String[] args) {
  6. List<String> list = Arrays.asList("kk", "yi", "name", "opk");
  7. Collections.sort(list,
  8. (s1, s2) -> { // 此处s1,s2可以不用自定义类型
  9. if (s1 == null){
  10. return -1;
  11. }
  12. if (s2 == null){
  13. return 1;
  14. }
  15. return s1.length() - s2.length();
  16. }
  17. );
  18. list.forEach(System.out::println); // Lambda表达式,打印list;不用使用For循环
  19. }
  20. }

除了省略了接口名和方法名,代码中把参数表的类型也省略了。这得益于javac类型推断机制,编译器能够根据上下文信息推断出参数的类型,当然也有推断失败的时候,这时就需要手动指明参数类型了。注意,Java是强类型语言,每个变量和对象都必需有明确的类型。

在 Java 8 中使用双冒号操作符(double colon operator),代表使用方法

也可以使用下列的方法:

  1. // 使用 lambda 表达式以及函数操作(functional operation)
  2. list.forEach((list) -> System.out.print(list+ "; "));

在图形用户界面程序中,匿名类可以使用lambda表达式来代替

  1. // 使用匿名内部类
  2. btn.setOnAction(new EventHandler<ActionEvent>() {
  3. @Override
  4. public void handle(ActionEvent event) {
  5. System.out.println("Hello World!");
  6. }
  7. });
  8. // 或者使用 lambda expression
  9. btn.setOnAction(event -> System.out.println("Hello World!"));

1)removeIf() 方法

删除满足于表达式的条件

  1. import java.util.Arrays;
  2. import java.util.Collections;
  3. import java.util.List;
  4. import java.util.ArrayList;
  5. public class Main {
  6. public static void main(String[] args) {
  7. List<String> list = new ArrayList(Arrays.asList("kk", "yi", "name", "opk"));
  8. list.removeIf(str -> str.length() < 3);
  9. list.forEach(System.out::println);
  10. }
  11. }

2)replaceAll() 方法

假设有一个字符串列表,将其中所有长度大于3的元素转换成大写,其余元素不变。

  1. import java.util.Arrays;
  2. import java.util.Collections;
  3. import java.util.List;
  4. import java.util.ArrayList;
  5. public class Main {
  6. public static void main(String[] args) {
  7. List<String> list = new ArrayList(Arrays.asList("kk", "yi", "name", "opk"));
  8. list.replaceAll(str -> {
  9. if (str.length() >2){ // 将字符串长度大于2的,转换成大写字母
  10. return str.toUpperCase();
  11. }
  12. return str;
  13. });
  14. list.forEach(System.out::println);
  15. }
  16. }

3)sort() 方法

假设有一个字符串列表,按照字符串长度增序对元素排序。长度越长的,排在越后面

  1. import java.util.Arrays;
  2. import java.util.Collections;
  3. import java.util.List;
  4. import java.util.ArrayList;
  5. public class Main {
  6. public static void main(String[] args) {
  7. List<String> list = new ArrayList(Arrays.asList("kk", "yiyyy", "name", "opk"));
  8. list.sort((str1, str2) ->
  9. str1.length() - str2.length()
  10. );
  11. list.forEach(System.out::println);
  12. }
  13. }

3.Stream流处理:

Stream是对集合的包装,通常和lambda一起使用。 使用lambdas可以支持许多操作,如 map, filter, limit, sorted, count, min, max, sum, collect 等等。 同样,Stream使用懒运算,他们并不会真正地读取所有数据,遇到像collect() 这样的方法就会结束链式语法

  1. 代码简洁函数式编程写出的代码简洁且意图明确,使用stream接口让你从此告别for循环。
  2. 多核友好,Java函数式编程使得编写并行程序从未如此简单,你需要的全部就是调用一下parallel()方法。

得到一个stream通常不会手动创建,而是调用对应的工具方法,比如:

  • 调用Collection.stream()或者Collection.parallelStream()方法
  • 调用Arrays.stream(T[] array)方法

虽然大部分情况下stream是容器调用Collection.stream()方法得到的,但streamcollections有以下不同:

  • 无存储stream不是一种数据结构,它只是某种数据源的一个视图,数据源可以是一个数组,Java容器或I/O channel等。
  • 为函数式编程而生。对stream的任何修改都不会修改背后的数据源,比如对stream执行过滤操作并不会删除被过滤的元素,而是会产生一个不包含被过滤元素的新stream
  • 惰式执行stream上的操作并不会立即执行,只有等到用户真正需要结果的时候才会执行。
  • 可消费性stream只能被“消费”一次,一旦遍历过就会失效,就像容器的迭代器那样,想要再次遍历必须重新生成。

这类似于 Spark 的RRD数据操作形式

下表汇总了Stream接口的部分常见方法:


















操作类型 接口方法
中间操作 concat()   distinct()   filter()   flatMap()   limit()   map()    peek()
skip()    sorted()   parallel()    sequential()    unordered()
结束操作 allMatch()     anyMatch()     collect()     count()    findAny()     findFirst()
forEach()    forEachOrdered()   max()    min()    noneMatch()    reduce()    toArray()

1)map 操作就可以使用该函数,将一个流中的值转换成一个新的流。

将数组的每项增加 10

  1. import java.util.Arrays;
  2. import java.util.Collections;
  3. import java.util.List;
  4. import java.util.ArrayList;
  5. public class Main {
  6. public static void main(String[] args) {
  7. List<Integer> list = new ArrayList(Arrays.asList(1, 2, 3, 4, 5, 6));
  8. list.stream()
  9. .map(m -> m + 10) // 将每项增加10
  10. .forEach(m -> System.out.println(m)); // 打印出数组
  11. }

format_png

2)Stream 中提供的新方法 filter

用于判断对象是否符合条件,符合条件的才保留下来

将 满足 filter 方法条件的数组保存下来

  1. import java.util.Arrays;
  2. import java.util.Collections;
  3. import java.util.List;
  4. import java.util.ArrayList;
  5. public class Main {
  6. public static void main(String[] args) {
  7. List<Integer> list = new ArrayList(Arrays.asList(1, 2, 3, 4, 5, 6));
  8. list.stream()
  9. .filter(m -> m > 4)
  10. .forEach(m -> System.out.println(m)); // 打印出数组 5,6
  11. }
  12. }

format_png 1

3)flatMap 方法可用 Stream 替换值,然后将多个 Stream 连接成一个 Stream

  1. import java.util.Arrays;
  2. import java.util.Collections;
  3. import java.util.List;
  4. import java.util.ArrayList;
  5. import java.util.Comparator;
  6. import java.util.stream.Collectors;
  7. public class Main {
  8. public static void main(String[] args) {
  9. List<String> teamIndia = Arrays.asList("Virat", "Dhoni", "Jadeja");
  10. List<String> teamAustralia = Arrays.asList("Warner", "Watson", "Smith");
  11. List<String> teamEngland = Arrays.asList("Alex", "Bell", "Broad");
  12. List<String> teamNewZeland = Arrays.asList("Kane", "Nathan", "Vettori");
  13. List<String> teamSouthAfrica = Arrays.asList("AB", "Amla", "Faf");
  14. List<String> teamWestIndies = Arrays.asList("Sammy", "Gayle", "Narine");
  15. List<String> teamSriLanka = Arrays.asList("Mahela", "Sanga", "Dilshan");
  16. List<String> teamPakistan = Arrays.asList("Misbah", "Afridi", "Shehzad");
  17. List<List<String>> playersInWorldCup2016 = new ArrayList<>();
  18. playersInWorldCup2016.add(teamIndia);
  19. playersInWorldCup2016.add(teamAustralia);
  20. playersInWorldCup2016.add(teamEngland);
  21. playersInWorldCup2016.add(teamNewZeland);
  22. playersInWorldCup2016.add(teamSouthAfrica);
  23. playersInWorldCup2016.add(teamWestIndies);
  24. playersInWorldCup2016.add(teamSriLanka);
  25. playersInWorldCup2016.add(teamPakistan);
  26. List<String> list = playersInWorldCup2016.stream()
  27. .flatMap(pList -> pList.stream())
  28. .collect(Collectors.toList());
  29. System.out.println(list);
  30. }
  31. }
  32. 输出结果:​​​​​[Virat, Dhoni, Jadeja, Warner, Watson, Smith, Alex, Bell, Broad, Kane, Nathan, Vettori, AB, Amla, Faf, Sammy, Gayle, Narine, Mahela, Sanga, Dilshan, Misbah, Afridi, Shehzad]

format_png 2

4)Stream 上常用的操作之一是求最大值和最小值。

Stream API 中的 maxmin 操作足以解决这一问题

获取最大值最小值

min可以对整型流求最小值,返回OptionalInt
max可以对整型流求最大值,返回OptionalInt
这两个方法是结束操作,只能调用一次。

min 和 max 函数需要一个 Comparator 对象为参数作为比对依据。

找到数组中最小的数字

  1. import java.util.Arrays;
  2. import java.util.Collections;
  3. import java.util.List;
  4. import java.util.ArrayList;
  5. import java.util.Comparator;
  6. public class Main {
  7. public static void main(String[] args) {
  8. List<Integer> list = new ArrayList(Arrays.asList(1, 2, 3, 4, 5, 6));
  9. System.out.println(list.stream().min(Comparator.comparing(Integer::valueOf)).get());
  10. }
  11. }

5)reduce 操作可以实现从一组值中生成一个值

format_png 3

  1. import java.util.Arrays;
  2. import java.util.Collections;
  3. import java.util.List;
  4. import java.util.ArrayList;
  5. import java.util.Comparator;
  6. public class Main {
  7. public static void main(String[] args) {
  8. List<Integer> list = new ArrayList(Arrays.asList(1, 2, 3, 4, 5, 6));
  9. int k = list.stream().
  10. reduce((n1, n2) -> n1 + n2)
  11. .get();
  12. System.out.println(k);
  13. }
  14. }

或者:

  1. import java.util.Arrays;
  2. import java.util.Collections;
  3. import java.util.List;
  4. import java.util.ArrayList;
  5. import java.util.Comparator;
  6. public class Main {
  7. public static void main(String[] args) {
  8. List<Integer> list = new ArrayList(Arrays.asList(1, 2, 3, 4, 5, 6));
  9. int k = list.stream().
  10. reduce(0, (n1, n2) -> n1 + n2);
  11. System.out.println(k);
  12. }
  13. }

5)数据并行化处理

数据并行化是指将数据分成块,为每块数据分配单独的处理单元。这样可以充分利用多核 CPU 的优势。

使用 parallelStream()

format_png 4

  1. import java.util.Arrays;
  2. import java.util.Collections;
  3. import java.util.List;
  4. import java.util.ArrayList;
  5. import java.util.Comparator;
  6. public class Main {
  7. public static void main(String[] args) {
  8. List<Integer> list = new ArrayList(Arrays.asList(1, 2, 3, 4, 5, 6));
  9. list.parallelStream()
  10. .forEach(System.out::println);
  11. }
  12. }

得到的数据不是按顺序的 1,2,3,4,5,6,

而是乱序的数字

6)limt(), skip(), distinct(),

limit() 方法,和数据库的limit 方法一样,取指定的前 n 项数字

  1. import java.util.Arrays;
  2. import java.util.Collections;
  3. import java.util.List;
  4. import java.util.ArrayList;
  5. public class Main {
  6. public static void main(String[] args) {
  7. List<Integer> list = new ArrayList(Arrays.asList(1, 2, 3, 4, 5, 6));
  8. list.stream()
  9. .limit(3) // 只打印前3 个数数字
  10. .forEach(m -> System.out.println(m)); // 打印出数组 1,2,3
  11. }
  12. }

skip() 方法:用于排除前多少个结果。

distinct() 方法,用于剔除重复,与数据库中的distinct用法一致。

-————————————— end —————————————-

项目推荐:

2000多G的计算机各行业电子资源分享(持续更新)

2020年微信小程序全栈项目之喵喵交友【附课件和源码】

Spring Boot开发小而美的个人博客【附课件和源码】

Java微服务实战296集大型视频-谷粒商城【附代码和课件】

Java开发微服务畅购商城实战【全357集大项目】-附代码和课件

最全最详细数据结构与算法视频-【附课件和源码】

在这里插入图片描述

-——————————————————————————————————————

发表评论

表情:
评论列表 (有 0 条评论,425人围观)

还没有评论,来说两句吧...

相关阅读