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

tech2023-08-28  96

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

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

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

Runnable runnable = new Runnable() { @Override public void run() { System.out.println("do something..."); } };

使用函数式编程之后:

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

2.Lambda表达式:

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

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

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

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

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类的使用

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

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

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

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

public class Main { public static void main(String[] args) { new Thread( () -> { System.out.println("this is thread"); System.out.println("this is sencond"); } ).start(); } }

2. 带参数的Lambda表达式

import java.util.Arrays; import java.util.Collections; import java.util.List; public class Main { public static void main(String[] args) { List<String> list = Arrays.asList("kk", "yi", "name", "opk"); Collections.sort(list, (s1, s2) -> { // 此处s1,s2可以不用自定义类型 if (s1 == null){ return -1; } if (s2 == null){ return 1; } return s1.length() - s2.length(); } ); list.forEach(System.out::println); // Lambda表达式,打印list;不用使用For循环 } }

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

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

也可以使用下列的方法:

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

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

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

1)removeIf() 方法

删除满足于表达式的条件

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

2)replaceAll() 方法

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

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

3)sort() 方法

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

import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.ArrayList; public class Main { public static void main(String[] args) { List<String> list = new ArrayList(Arrays.asList("kk", "yiyyy", "name", "opk")); list.sort((str1, str2) -> str1.length() - str2.length() ); list.forEach(System.out::println); } }

 

 

3.Stream流处理:

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

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

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

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

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

无存储。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

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

2)Stream 中提供的新方法 filter

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

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

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

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

import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.ArrayList; import java.util.Comparator; import java.util.stream.Collectors; public class Main { public static void main(String[] args) { List<String> teamIndia = Arrays.asList("Virat", "Dhoni", "Jadeja"); List<String> teamAustralia = Arrays.asList("Warner", "Watson", "Smith"); List<String> teamEngland = Arrays.asList("Alex", "Bell", "Broad"); List<String> teamNewZeland = Arrays.asList("Kane", "Nathan", "Vettori"); List<String> teamSouthAfrica = Arrays.asList("AB", "Amla", "Faf"); List<String> teamWestIndies = Arrays.asList("Sammy", "Gayle", "Narine"); List<String> teamSriLanka = Arrays.asList("Mahela", "Sanga", "Dilshan"); List<String> teamPakistan = Arrays.asList("Misbah", "Afridi", "Shehzad"); List<List<String>> playersInWorldCup2016 = new ArrayList<>(); playersInWorldCup2016.add(teamIndia); playersInWorldCup2016.add(teamAustralia); playersInWorldCup2016.add(teamEngland); playersInWorldCup2016.add(teamNewZeland); playersInWorldCup2016.add(teamSouthAfrica); playersInWorldCup2016.add(teamWestIndies); playersInWorldCup2016.add(teamSriLanka); playersInWorldCup2016.add(teamPakistan); List<String> list = playersInWorldCup2016.stream() .flatMap(pList -> pList.stream()) .collect(Collectors.toList()); System.out.println(list); } } 输出结果:​​​​​[Virat, Dhoni, Jadeja, Warner, Watson, Smith, Alex, Bell, Broad, Kane, Nathan, Vettori, AB, Amla, Faf, Sammy, Gayle, Narine, Mahela, Sanga, Dilshan, Misbah, Afridi, Shehzad]

 

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

Stream API 中的 max 和 min 操作足以解决这一问题

获取最大值最小值

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

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

找到数组中最小的数字

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

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

 

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

 或者:

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

5)数据并行化处理

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

使用 parallelStream()

 

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

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

而是乱序的数字

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

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

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

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

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

 

---------------------------  end  ---------------------------

项目推荐:

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

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

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

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

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

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

-----------------------------------------------------------------------------

最新回复(0)