# 带你解惑大厂必会使用的 Stream流、方法引用🔥 ## 前言: > > ### 带你解惑大厂必会使用的 Lambda表达式、函数式接口 > > ### 带你解惑大厂必会使用的 Stream流、方法引用 > > 若对你和身边的朋友有帮助, 点赞! 点赞! 点赞! 评论!收藏! 分享给更多的朋友共同学习交流 --- # 第三章 Stream流 在Java 8中,得益于Lambda所带来的函数式编程,引入了一个**全新的Stream概念**,用于解决已有集合类库既有的弊端。 ## 3.1 引言 **传统集合的多步遍历代码** 几乎所有的集合(如`Collection`接口或`Map`接口等)都支持直接或间接的遍历操作。而当我们需要对集合中的元素进行操作的时候,除了必需的添加、删除、获取外,最典型的就是集合遍历。例如: ```java public class Demo10ForEach { public static void main(String[] args) { List list = new ArrayList<>(); list.add("张无忌"); list.add("周芷若"); list.add("赵敏"); list.add("张强"); list.add("张三丰"); for (String name : list) { System.out.println(name); } } } ``` 这是一段非常简单的集合遍历操作:对集合中的每一个字符串都进行打印输出操作。 **循环遍历的弊端** Java 8的Lambda让我们可以更加专注于**做什么**(What),而不是**怎么做**(How),这点此前已经结合内部类进行了对比说明。现在,我们仔细体会一下上例代码,可以发现: * for循环的语法就是“**怎么做**” * for循环的循环体才是“**做什么**” 为什么使用循环?因为要进行遍历。但循环是遍历的唯一方式吗?遍历是指每一个元素逐一进行处理,**而并不是从第一个到最后一个顺次处理的循环**。前者是目的,后者是方式。 试想一下,如果希望对集合中的元素进行筛选过滤: 1. 将集合A根据条件一过滤为**子集B**; 2. 然后再根据条件二过滤为**子集C**。 那怎么办?在Java 8之前的做法可能为: 这段代码中含有三个循环,每一个作用不同: 1. 首先筛选所有姓张的人; 2. 然后筛选名字有三个字的人; 3. 最后进行对结果进行打印输出。 ```java public class Demo11NormalFilter { public static void main(String[] args) { List list = new ArrayList<>(); list.add("张无忌"); list.add("周芷若"); list.add("赵敏"); list.add("张强"); list.add("张三丰"); List zhangList = new ArrayList<>(); for (String name : list) { if (name.startsWith("张")) { zhangList.add(name); } } List shortList = new ArrayList<>(); for (String name : zhangList) { if (name.length() == 3) { shortList.add(name); } } for (String name : shortList) { System.out.println(name); } } } ``` **每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。这是理所当然的么?不是**。循环是做事情的方式,而不是目的。另一方面,使用线性循环就意味着只能遍历一次。如果希望再次遍历,只能再使用另一个循环从头开始。 那,Lambda的衍生物Stream能给我们带来怎样更加优雅的写法呢? **Stream的更优写法** 下面来看一下借助Java 8的Stream API,什么才叫优雅: ```java public class Demo12StreamFilter { public static void main(String[] args) { List list = new ArrayList<>(); list.add("张无忌"); list.add("周芷若"); list.add("赵敏"); list.add("张强"); list.add("张三丰"); list.stream() .filter(s -> s.startsWith("张")) .filter(s -> s.length() == 3) .forEach(s -> System.out.println(s)); } } ``` 直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:**获取流、过滤姓张、过滤长度为3、逐一打印**。代码中并没有体现使用线性循环或是其他任何算法进行遍历,我们真正要做的事情内容被更好地体现在代码中。 ## 3.2 流式思想概述 **注意:请暂时忘记对传统IO流的固有印象!** 整体来看,流式思想类似于工厂车间的“**生产流水线**”。  当需要对多个元素进行操作(特别是多步操作)的时候,考虑到性能及便利性,我们应该首先拼好一个“模型”步骤方案,然后再按照方案去执行它。  这张图中展示了过滤、映射、跳过、计数等多步操作,这是一种集合元素的处理方案,而方案就是一种“函数模型”。图中的每一个方框都是一个“流”,调用指定的方法,可以从一个流模型转换为另一个流模型。而最右侧的数字3是最终结果。 这里的`filter`、`map`、`skip`都是在对函数模型进行操作,集合元素并没有真正被处理。只有当终结方法`count`执行的时候,整个模型才会按照指定策略执行操作。而这得益于Lambda的延迟执行特性。 > 备注:“Stream流”其实是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身并不存储任何元素(或其地址值)。 ## 3.3 获取流方式 `java.util.stream.Stream`是Java 8新加入的最常用的流接口。(这并不是一个函数式接口。) 获取一个流非常简单,有以下几种常用的方式: * 所有的`Collection`集合都可以通过`stream`默认方法获取流; * `Stream`接口的静态方法`of`可以获取数组对应的流。 **方式1 : 根据Collection获取流** 首先,`java.util.Collection`接口中加入了default方法`stream`用来获取流,所以其所有实现类均可获取流。 ```java import java.util.*; import java.util.stream.Stream; /* 获取Stream流的方式 1.Collection中 方法 Stream stream() 2.Stream接口 中静态方法 of(T...t) 向Stream中添加多个数据 */ public class Demo13GetStream { public static void main(String[] args) { List list = new ArrayList<>(); // ... Stream stream1 = list.stream(); Set set = new HashSet<>(); // ... Stream stream2 = set.stream(); } } ``` **方式2: 根据数组获取流** 如果使用的不是集合或映射而是数组,由于数组对象不可能添加默认方法,所以`Stream`接口中提供了静态方法`of`,使用很简单: ```java import java.util.stream.Stream; public class Demo14GetStream { public static void main(String[] args) { String[] array = { "张无忌", "张翠山", "张三丰", "张一元" }; Stream stream = Stream.of(array); } } ``` > 备注:`of`方法的参数其实是一个可变参数,所以支持数组。 ## 3.4 常用方法 流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种: * **终结方法**:返回值类型不再是`Stream`接口自身类型的方法,因此不再支持类似`StringBuilder`那样的链式调用。本小节中,终结方法包括`count`和`forEach`方法。 * **非终结方法**:返回值类型仍然是`Stream`接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为非终结方法。) > 备注:本小节之外的更多方法,请自行参考API文档。 ### forEach : 逐一处理 虽然方法名字叫`forEach`,但是与for循环中的“for-each”昵称不同,该方法**并不保证元素的逐一消费动作在流中是被有序执行的**。 ```java void forEach(Consumer super T> action); ``` 该方法接收一个`Consumer`接口函数,会将每一个流元素交给该函数进行处理。例如: ```java import java.util.stream.Stream; public class Demo15StreamForEach { public static void main(String[] args) { Stream stream = Stream.of("大娃","二娃","三娃","四娃","五娃","六娃","七娃","爷爷","蛇精","蝎子精"); //Stream stream = Stream.of("张无忌", "张三丰", "周芷若"); stream.forEach((String str)->{System.out.println(str);}); } } ``` 在这里,lambda表达式`(String str)->{System.out.println(str);}`就是一个Consumer函数式接口的示例。 ### filter:过滤 可以通过`filter`方法将一个流转换成另一个子集流。方法声明: ```java Stream filter(Predicate super T> predicate); ``` 该接口接收一个`Predicate`函数式接口参数(可以是一个Lambda)作为筛选条件。 **基本使用** Stream流中的`filter`方法基本使用的代码如: ```java public class Demo16StreamFilter { public static void main(String[] args) { Stream original = Stream.of("张无忌", "张三丰", "周芷若"); Stream result = original.filter((String s) -> {return s.startsWith("张");}); } } ``` 在这里通过Lambda表达式来指定了筛选的条件:必须姓张。 ### count:统计个数 正如旧集合`Collection`当中的`size`方法一样,流提供`count`方法来数一数其中的元素个数: ```java long count(); ``` 该方法返回一个long值代表元素个数(不再像旧集合那样是int值)。基本使用: ```java public class Demo17StreamCount { public static void main(String[] args) { Stream original = Stream.of("张无忌", "张三丰", "周芷若"); Stream result = original.filter(s -> s.startsWith("张")); System.out.println(result.count()); // 2 } } ``` ### limit:取用前几个 `limit`方法可以对流进行截取,只取用前n个。方法签名: ```java Stream limit(long maxSize):获取Stream流对象中的前n个元素,返回一个新的Stream流对象 ``` 参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作。基本使用: ```java import java.util.stream.Stream; public class Demo18StreamLimit { public static void main(String[] args) { Stream original = Stream.of("张无忌", "张三丰", "周芷若"); Stream result = original.limit(2); System.out.println(result.count()); // 2 } } ``` ### skip:跳过前几个 如果希望跳过前几个元素,可以使用`skip`方法获取一个截取之后的新流: ```java Stream skip(long n): 跳过Stream流对象中的前n个元素,返回一个新的Stream流对象 ``` 如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。基本使用: ```java import java.util.stream.Stream; public class Demo19StreamSkip { public static void main(String[] args) { Stream original = Stream.of("张无忌", "张三丰", "周芷若"); Stream result = original.skip(2); System.out.println(result.count()); // 1 } } ``` ### concat:组合 如果有两个流,希望合并成为一个流,那么可以使用`Stream`接口的静态方法`concat`: ```java static Stream concat(Stream extends T> a, Stream extends T> b): 把参数列表中的两个Stream流对象a和b,合并成一个新的Stream流对象 ``` > 备注:这是一个静态方法,与`java.lang.String`当中的`concat`方法是不同的。 该方法的基本使用代码如: ```java import java.util.stream.Stream; public class Demo20StreamConcat { public static void main(String[] args) { Stream streamA = Stream.of("张无忌"); Stream streamB = Stream.of("张翠山"); Stream result = Stream.concat(streamA, streamB); } } ``` ## 3.5 Stream综合案例 现在有两个`ArrayList`集合存储队伍当中的多个成员姓名,要求使用传统的for循环(或增强for循环)**依次**进行以下若干操作步骤: 1. 第一个队伍只要名字为3个字的成员姓名; 2. 第一个队伍筛选之后只要前3个人; 3. 第二个队伍只要姓张的成员姓名; 4. 第二个队伍筛选之后不要前2个人; 5. 将两个队伍合并为一个队伍; 6. 打印整个队伍的姓名信息。 两个队伍(集合)的代码如下: ```java public class Demo21ArrayListNames { public static void main(String[] args) { List one = new ArrayList<>(); one.add("迪丽热巴"); one.add("宋远桥"); one.add("苏星河"); one.add("老子"); one.add("庄子"); one.add("孙子"); one.add("洪七公"); List two = new ArrayList<>(); two.add("古力娜扎"); two.add("张无忌"); two.add("张三丰"); two.add("赵丽颖"); two.add("张二狗"); two.add("张天爱"); two.add("张三"); // .... } } ``` **传统方式** 使用for循环 , 示例代码: ```java public class Demo22ArrayListNames { public static void main(String[] args) { List one = new ArrayList<>(); // ... List two = new ArrayList<>(); // ... // 第一个队伍只要名字为3个字的成员姓名; List oneA = new ArrayList<>(); for (String name : one) { if (name.length() == 3) { oneA.add(name); } } // 第一个队伍筛选之后只要前3个人; List oneB = new ArrayList<>(); for (int i = 0; i < 3; i++) { oneB.add(oneA.get(i)); } // 第二个队伍只要姓张的成员姓名; List twoA = new ArrayList<>(); for (String name : two) { if (name.startsWith("张")) { twoA.add(name); } } // 第二个队伍筛选之后不要前2个人; List twoB = new ArrayList<>(); for (int i = 2; i < twoA.size(); i++) { twoB.add(twoA.get(i)); } // 将两个队伍合并为一个队伍; List totalNames = new ArrayList<>(); totalNames.addAll(oneB); totalNames.addAll(twoB); // 打印整个队伍的姓名信息。 for (String name : totalNames) { System.out.println(name); } } } ``` 运行结果为: ``` 宋远桥 苏星河 洪七公 张二狗 张天爱 张三 ``` **Stream方式** 等效的Stream流式处理代码为: ```java public class Demo23StreamNames { public static void main(String[] args) { List one = new ArrayList<>(); // ... List two = new ArrayList<>(); // ... // 第一个队伍只要名字为3个字的成员姓名; // 第一个队伍筛选之后只要前3个人; Stream streamOne = one.stream().filter(s -> s.length() == 3).limit(3); // 第二个队伍只要姓张的成员姓名; // 第二个队伍筛选之后不要前2个人; Stream streamTwo = two.stream().filter(s -> s.startsWith("张")).skip(2); // 将两个队伍合并为一个队伍; // 根据姓名创建Person对象; // 打印整个队伍的Person对象信息。 Stream.concat(streamOne, streamTwo).forEach(s->System.out.println(s)); } } ``` 运行效果完全一样: ``` 宋远桥 苏星河 洪七公 张二狗 张天爱 张三 ``` ## 3.6 函数拼接与终结方法 在上述介绍的各种方法中,凡是返回值仍然为`Stream`接口的为**函数拼接方法**,它们支持链式调用;而返回值不再为`Stream`接口的为**终结方法**,不再支持链式调用。如下表所示: | 方法名 | 方法作用 | 方法种类 | 是否支持链式调用 | | - | - | - | - | | count | 统计个数 | 终结 | 否 | | forEach | 逐一处理 | 终结 | 否 | | filter | 过滤 | 函数拼接 | 是 | | limit | 取用前几个 | 函数拼接 | 是 | | skip | 跳过前几个 | 函数拼接 | 是 | | concat | 组合 | 函数拼接 | 是 | # 第四章 方法引用 ## 4.1 概述和方法引用符 来看一个简单的函数式接口以应用Lambda表达式 , 在accept方法中接收字符串 , 目的就是为了打印显示字符串 , 那么通过Lambda来使用它的代码很简单: ```java public class DemoPrintSimple { private static void printString(Consumer data, String str) { data.accept(str); } public static void main(String[] args) { printString(s -> System.out.println(s), "Hello World"); } } ``` 由于lambda表达式中,调用了已经实现的println方法 ,可以使用方法引用替代lambda表达式. **符号表示 :** `::` **符号说明 :** 双冒号为方法引用运算符,而它所在的表达式被称为**方法引用**。 **应用场景 : **如果Lambda要表达的函数方案 , 已经存在于某个方法的实现中,那么则可以使用方法引用。 > 如上例中,System.out对象中有个println(String)方法 , 恰好就是我们所需要的 , 那么对于Consumer接口作为参数,对比下面两种写法,完全等效: > > * Lambda表达式写法:s -> System.out.println(s); 拿到参数之后经Lambda之手,继而传递给System.out.println方法去处理。 > * 方法引用写法:System.out::println 直接让System.out中的println方法来取代Lambda。 **推导与省略 : ** 如果使用Lambda,那么根据“**可推导就是可省略**”的原则,无需指定参数类型,也无需指定的重载形式——它们都将被自动推导。而如果使用方法引用,也是同样可以根据上下文进行推导。函数式接口是Lambda的基础,而方法引用是Lambda的简化形式。 ## 4.2 方法引用简化 只要“引用”过去就好了: ```java public class DemoPrintRef { private static void printString(Consumer data, String str) { data.accept(str); } public static void main(String[] args) { printString(System.out::println, "HelloWorld"); } } ``` 请注意其中的双冒号`::`写法,这被称为“**方法引用**”,而双冒号是一种新的语法。 ## 4.3 扩展的引用方式 ### 对象名--引用成员方法 这是最常见的一种用法,与上例相同。如果一个类中已经存在了一个成员方法,则可以通过对象名引用成员方法,代码为: ```java public class DemoMethodRef { public static void main(String[] args) { String str = "hello"; printUP(str::toUpperCase); } public static void printUP(Supplier< String> sup ){ String apply =sup.get(); System.out.println(apply); } } ``` ### 类名--引用静态方法 由于在`java.lang.Math`类中已经存在了静态方法`random`,所以当我们需要通过Lambda来调用该方法时,可以使用方法引用 , 写法是: ```java public class DemoMethodRef { public static void main(String[] args) { printRanNum(Math::random); } public static void printRanNum(Supplier sup ){ Double apply =sup.get(); System.out.println(apply); } } ``` 在这个例子中,下面两种写法是等效的: * Lambda表达式:`n -> Math.abs(n)` * 方法引用:`Math::abs` ### 类--构造引用 由于构造器的名称与类名完全一样,并不固定。所以构造器引用使用`类名称::new`的格式表示。首先是一个简单的`Person`类: ```java public class Person { private String name; public Person(String name) { this.name = name; } public String getName() { return name; } } ``` 要使用这个函数式接口,可以通过方法引用传递: ```java public class Demo09Lambda { public static void main(String[] args) { String name = "tom"; Person person = createPerson(Person::new, name); System.out.println(person); } public static Person createPerson(Function fun , String name){ Person p = fun.apply(name); return p; } } ``` 在这个例子中,下面两种写法是等效的: * Lambda表达式:`name -> new Person(name)` * 方法引用:`Person::new` ### 数组--构造引用 数组也是`Object`的子类对象,所以同样具有构造器,只是语法稍有不同。如果对应到Lambda的使用场景中时,需要一个函数式接口: 在应用该接口的时候,可以通过方法引用传递: ```java public class Demo11ArrayInitRef { public static void main(String[] args) { int[] array = createArray(int[]::new, 3); System.out.println(array.length); } public static int[] createArray(Function fun , int n){ int[] p = fun.apply(n); return p; } } ``` 在这个例子中,下面两种写法是等效的: * Lambda表达式:`length -> new int[length]` * 方法引用:`int[]::new` > 注意 : 方法引用是对Lambda表达式符合特定情况下的一种缩写,它使得我们的Lambda表达式更加的精简,也可以理解为Lambda表达式的缩写形式 , 同学们可以尝试着 , 将之前使用lambda的地方 , 改写成方法引用的形式 ,不过要注意的是方法引用只能"引用"已经存在的方法!带你解惑大厂必会使用的 Stream流、方法引用🔥前言:应广大读者的需要,霈哥给大家带来新一期的干货啦!带你解惑大厂必会使用的 Lambda表达式、函数式接口带你解惑大厂必会使用的 Stream流、方法引用若对你和身边的朋友有帮助, 抓紧关注 IT霈哥 点赞! 点赞! 点赞! 评论!收藏! 分享给更多的朋友共同学习交流, 每天持续掘金离不开你的点赞支持!第三章 Stream流在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。3.1 引言传统集合的多步遍历代码几乎所有的集合(如Collection接口或Map接口等)都支持直接或间接的遍历操作。而当我们需要对集合中的元素进行操作的时候,除了必需的添加、删除、获取外,最典型的就是集合遍历。例如:public class Demo10ForEach { > public static void main(String[] args) { > List list = new ArrayList<>(); > list.add("张无忌"); > list.add("周芷若"); > list.add("赵敏"); > list.add("张强"); > list.add("张三丰"); > for (String name : list) { > System.out.println(name); > } > } > } > 这是一段非常简单的集合遍历操作:对集合中的每一个字符串都进行打印输出操作。循环遍历的弊端Java 8的Lambda让我们可以更加专注于做什么(What),而不是怎么做(How),这点此前已经结合内部类进行了对比说明。现在,我们仔细体会一下上例代码,可以发现:for循环的语法就是“怎么做”for循环的循环体才是“做什么”为什么使用循环?因为要进行遍历。但循环是遍历的唯一方式吗?遍历是指每一个元素逐一进行处理,而并不是从第一个到最后一个顺次处理的循环。前者是目的,后者是方式。试想一下,如果希望对集合中的元素进行筛选过滤:将集合A根据条件一过滤为子集B;然后再根据条件二过滤为子集C。那怎么办?在Java 8之前的做法可能为:这段代码中含有三个循环,每一个作用不同:首先筛选所有姓张的人;然后筛选名字有三个字的人;最后进行对结果进行打印输出。public class Demo11NormalFilter { > public static void main(String[] args) { > List list = new ArrayList<>(); > list.add("张无忌"); > list.add("周芷若"); > list.add("赵敏"); > list.add("张强"); > list.add("张三丰"); ``` List zhangList = new ArrayList<>(); for (String name : list) { if (name.startsWith("张")) { zhangList.add(name); } } List shortList = new ArrayList<>(); for (String name : zhangList) { if (name.length() == 3) { shortList.add(name); } } for (String name : shortList) { System.out.println(name); } } ``` } 每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。这是理所当然的么?不是。循环是做事情的方式,而不是目的。另一方面,使用线性循环就意味着只能遍历一次。如果希望再次遍历,只能再使用另一个循环从头开始。那,Lambda的衍生物Stream能给我们带来怎样更加优雅的写法呢?Stream的更优写法下面来看一下借助Java 8的Stream API,什么才叫优雅:public class Demo12StreamFilter { public static void main(String[] args) { List list = new ArrayList<>(); list.add("张无忌"); list.add("周芷若"); list.add("赵敏"); list.add("张强"); list.add("张三丰"); ``` list.stream() .filter(s -> s.startsWith("张")) .filter(s -> s.length() == 3) .forEach(s -> System.out.println(s)); } ``` } 直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印。代码中并没有体现使用线性循环或是其他任何算法进行遍历,我们真正要做的事情内容被更好地体现在代码中。3.2 流式思想概述注意:请暂时忘记对传统IO流的固有印象!整体来看,流式思想类似于工厂车间的“生产流水线”。当需要对多个元素进行操作(特别是多步操作)的时候,考虑到性能及便利性,我们应该首先拼好一个“模型”步骤方案,然后再按照方案去执行它。这张图中展示了过滤、映射、跳过、计数等多步操作,这是一种集合元素的处理方案,而方案就是一种“函数模型”。图中的每一个方框都是一个“流”,调用指定的方法,可以从一个流模型转换为另一个流模型。而最右侧的数字3是最终结果。这里的filter、map、skip都是在对函数模型进行操作,集合元素并没有真正被处理。只有当终结方法count执行的时候,整个模型才会按照指定策略执行操作。而这得益于Lambda的延迟执行特性。备注:“Stream流”其实是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身并不存储任何元素(或其地址值)。3.3 获取流方式java.util.stream.Stream是Java 8新加入的最常用的流接口。(这并不是一个函数式接口。)获取一个流非常简单,有以下几种常用的方式:所有的Collection集合都可以通过stream默认方法获取流;Stream接口的静态方法of可以获取数组对应的流。方式1 : 根据Collection获取流首先,java.util.Collection接口中加入了default方法stream用来获取流,所以其所有实现类均可获取流。import java.util.*; import java.util.stream.Stream; /* 获取Stream流的方式 ``` 1.Collection中 方法 Stream stream() 2.Stream接口 中静态方法 of(T...t) 向Stream中添加多个数据 ``` */ public class Demo13GetStream { public static void main(String[] args) { List list = new ArrayList<>(); // ... Stream stream1 = list.stream(); ``` Set set = new HashSet<>(); // ... Stream stream2 = set.stream(); } ``` } 方式2: 根据数组获取流如果使用的不是集合或映射而是数组,由于数组对象不可能添加默认方法,所以Stream接口中提供了静态方法of,使用很简单:import java.util.stream.Stream; public class Demo14GetStream { public static void main(String[] args) { String[] array = { "张无忌", "张翠山", "张三丰", "张一元" }; Stream stream = Stream.of(array); } } 备注:of方法的参数其实是一个可变参数,所以支持数组。3.4 常用方法流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:终结方法:返回值类型不再是Stream接口自身类型的方法,因此不再支持类似StringBuilder那样的链式调用。本小节中,终结方法包括count和forEach方法。非终结方法:返回值类型仍然是Stream接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为非终结方法。)备注:本小节之外的更多方法,请自行参考API文档。forEach : 逐一处理虽然方法名字叫forEach,但是与for循环中的“for-each”昵称不同,该方法并不保证元素的逐一消费动作在流中是被有序执行的。void forEach(Consumer super T> action); 该方法接收一个Consumer接口函数,会将每一个流元素交给该函数进行处理。例如:import java.util.stream.Stream; public class Demo15StreamForEach { public static void main(String[] args) { Stream stream = Stream.of("大娃","二娃","三娃","四娃","五娃","六娃","七娃","爷爷","蛇精","蝎子精"); //Stream stream = Stream.of("张无忌", "张三丰", "周芷若"); stream.forEach((String str)->{System.out.println(str);}); } } 在这里,lambda表达式(String str)->{System.out.println(str);}就是一个Consumer函数式接口的示例。filter:过滤可以通过filter方法将一个流转换成另一个子集流。方法声明:Stream filter(Predicate super T> predicate); 该接口接收一个Predicate函数式接口参数(可以是一个Lambda)作为筛选条件。基本使用Stream流中的filter方法基本使用的代码如:public class Demo16StreamFilter { public static void main(String[] args) { Stream original = Stream.of("张无忌", "张三丰", "周芷若"); Stream result = original.filter((String s) -> {return s.startsWith("张");}); } } 在这里通过Lambda表达式来指定了筛选的条件:必须姓张。count:统计个数正如旧集合Collection当中的size方法一样,流提供count方法来数一数其中的元素个数:long count(); 该方法返回一个long值代表元素个数(不再像旧集合那样是int值)。基本使用:public class Demo17StreamCount { public static void main(String[] args) { Stream original = Stream.of("张无忌", "张三丰", "周芷若"); Stream result = original.filter(s -> s.startsWith("张")); System.out.println(result.count()); // 2 } } limit:取用前几个limit方法可以对流进行截取,只取用前n个。方法签名:Stream limit(long maxSize):获取Stream流对象中的前n个元素,返回一个新的Stream流对象 参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作。基本使用:import java.util.stream.Stream; public class Demo18StreamLimit { public static void main(String[] args) { Stream original = Stream.of("张无忌", "张三丰", "周芷若"); Stream result = original.limit(2); System.out.println(result.count()); // 2 } } skip:跳过前几个如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流:Stream skip(long n): 跳过Stream流对象中的前n个元素,返回一个新的Stream流对象 如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。基本使用:import java.util.stream.Stream; public class Demo19StreamSkip { public static void main(String[] args) { Stream original = Stream.of("张无忌", "张三丰", "周芷若"); Stream result = original.skip(2); System.out.println(result.count()); // 1 } } concat:组合如果有两个流,希望合并成为一个流,那么可以使用Stream接口的静态方法concat:static Stream concat(Stream extends T> a, Stream extends T> b): 把参数列表中的两个Stream流对象a和b,合并成一个新的Stream流对象 备注:这是一个静态方法,与java.lang.String当中的concat方法是不同的。该方法的基本使用代码如:import java.util.stream.Stream; public class Demo20StreamConcat { public static void main(String[] args) { Stream streamA = Stream.of("张无忌"); Stream streamB = Stream.of("张翠山"); Stream result = Stream.concat(streamA, streamB); } } 3.5 Stream综合案例现在有两个ArrayList集合存储队伍当中的多个成员姓名,要求使用传统的for循环(或增强for循环)依次进行以下若干操作步骤:第一个队伍只要名字为3个字的成员姓名;第一个队伍筛选之后只要前3个人;第二个队伍只要姓张的成员姓名;第二个队伍筛选之后不要前2个人;将两个队伍合并为一个队伍;打印整个队伍的姓名信息。两个队伍(集合)的代码如下:public class Demo21ArrayListNames { public static void main(String[] args) { List one = new ArrayList<>(); one.add("迪丽热巴"); one.add("宋远桥"); one.add("苏星河"); one.add("老子"); one.add("庄子"); one.add("孙子"); one.add("洪七公"); ``` List two = new ArrayList<>(); two.add("古力娜扎"); two.add("张无忌"); two.add("张三丰"); two.add("赵丽颖"); two.add("张二狗"); two.add("张天爱"); two.add("张三"); // .... } ``` } 传统方式使用for循环 , 示例代码:public class Demo22ArrayListNames { public static void main(String[] args) { List one = new ArrayList<>(); // ... ``` List two = new ArrayList<>(); // ... // 第一个队伍只要名字为3个字的成员姓名; List oneA = new ArrayList<>(); for (String name : one) { if (name.length() == 3) { oneA.add(name); } } // 第一个队伍筛选之后只要前3个人; List oneB = new ArrayList<>(); for (int i = 0; i < 3; i++) { oneB.add(oneA.get(i)); } // 第二个队伍只要姓张的成员姓名; List twoA = new ArrayList<>(); for (String name : two) { if (name.startsWith("张")) { twoA.add(name); } } // 第二个队伍筛选之后不要前2个人; List twoB = new ArrayList<>(); for (int i = 2; i < twoA.size(); i++) { twoB.add(twoA.get(i)); } // 将两个队伍合并为一个队伍; List totalNames = new ArrayList<>(); totalNames.addAll(oneB); totalNames.addAll(twoB); // 打印整个队伍的姓名信息。 for (String name : totalNames) { System.out.println(name); } } ``` } 运行结果为:宋远桥 苏星河 洪七公 张二狗 张天爱 张三 Stream方式等效的Stream流式处理代码为:public class Demo23StreamNames { public static void main(String[] args) { List one = new ArrayList<>(); // ... ``` List two = new ArrayList<>(); // ... // 第一个队伍只要名字为3个字的成员姓名; // 第一个队伍筛选之后只要前3个人; Stream streamOne = one.stream().filter(s -> s.length() == 3).limit(3); // 第二个队伍只要姓张的成员姓名; // 第二个队伍筛选之后不要前2个人; Stream streamTwo = two.stream().filter(s -> s.startsWith("张")).skip(2); // 将两个队伍合并为一个队伍; // 根据姓名创建Person对象; // 打印整个队伍的Person对象信息。 Stream.concat(streamOne, streamTwo).forEach(s->System.out.println(s)); } ``` } 运行效果完全一样:宋远桥 苏星河 洪七公 张二狗 张天爱 张三 3.6 函数拼接与终结方法在上述介绍的各种方法中,凡是返回值仍然为Stream接口的为函数拼接方法,它们支持链式调用;而返回值不再为Stream接口的为终结方法,不再支持链式调用。如下表所示:方法名方法作用方法种类是否支持链式调用count统计个数终结否forEach逐一处理终结否filter过滤函数拼接是limit取用前几个函数拼接是skip跳过前几个函数拼接是concat组合函数拼接是第四章 方法引用4.1 概述和方法引用符来看一个简单的函数式接口以应用Lambda表达式 , 在accept方法中接收字符串 , 目的就是为了打印显示字符串 , 那么通过Lambda来使用它的代码很简单:public class DemoPrintSimple { private static void printString(Consumer data, String str) { data.accept(str); } public static void main(String[] args) { printString(s -> System.out.println(s), "Hello World"); } } 由于lambda表达式中,调用了已经实现的println方法 ,可以使用方法引用替代lambda表达式.符号表示 : ::符号说明 : 双冒号为方法引用运算符,而它所在的表达式被称为方法引用。**应用场景 : **如果Lambda要表达的函数方案 , 已经存在于某个方法的实现中,那么则可以使用方法引用。如上例中,System.out对象中有个println(String)方法 , 恰好就是我们所需要的 , 那么对于Consumer接口作为参数,对比下面两种写法,完全等效:Lambda表达式写法:s -> System.out.println(s); 拿到参数之后经Lambda之手,继而传递给System.out.println方法去处理。方法引用写法:System.out::println 直接让System.out中的println方法来取代Lambda。**推导与省略 : ** 如果使用Lambda,那么根据“可推导就是可省略”的原则,无需指定参数类型,也无需指定的重载形式——它们都将被自动推导。而如果使用方法引用,也是同样可以根据上下文进行推导。函数式接口是Lambda的基础,而方法引用是Lambda的简化形式。4.2 方法引用简化只要“引用”过去就好了:public class DemoPrintRef { private static void printString(Consumer data, String str) { data.accept(str); } public static void main(String[] args) { printString(System.out::println, "HelloWorld"); } } 请注意其中的双冒号::写法,这被称为“方法引用”,而双冒号是一种新的语法。4.3 扩展的引用方式对象名--引用成员方法这是最常见的一种用法,与上例相同。如果一个类中已经存在了一个成员方法,则可以通过对象名引用成员方法,代码为:public class DemoMethodRef { public static void main(String[] args) { String str = "hello"; printUP(str::toUpperCase); } ``` public static void printUP(Supplier< String> sup ){ String apply =sup.get(); System.out.println(apply); } ``` } 类名--引用静态方法由于在java.lang.Math类中已经存在了静态方法random,所以当我们需要通过Lambda来调用该方法时,可以使用方法引用 , 写法是:public class DemoMethodRef { public static void main(String[] args) { printRanNum(Math::random); } ``` public static void printRanNum(Supplier sup ){ Double apply =sup.get(); System.out.println(apply); } ``` } 在这个例子中,下面两种写法是等效的:Lambda表达式:n -> Math.abs(n)方法引用:Math::abs类--构造引用由于构造器的名称与类名完全一样,并不固定。所以构造器引用使用类名称::new的格式表示。首先是一个简单的Person类:public class Person { private String name; public Person(String name) { this.name = name; } public String getName() { return name; } } 要使用这个函数式接口,可以通过方法引用传递:public class Demo09Lambda { public static void main(String[] args) { String name = "tom"; Person person = createPerson(Person::new, name); System.out.println(person); ``` } public static Person createPerson(Function fun , String name){ Person p = fun.apply(name); return p; } ``` } 在这个例子中,下面两种写法是等效的:Lambda表达式:name -> new Person(name)方法引用:Person::new数组--构造引用数组也是Object的子类对象,所以同样具有构造器,只是语法稍有不同。如果对应到Lambda的使用场景中时,需要一个函数式接口:在应用该接口的时候,可以通过方法引用传递:public class Demo11ArrayInitRef { public static void main(String[] args) { ``` int[] array = createArray(int[]::new, 3); System.out.println(array.length); } public static int[] createArray(Function fun , int n){ int[] p = fun.apply(n); return p; } ``` } 在这个例子中,下面两种写法是等效的:Lambda表达式:length -> new int[length]方法引用:int[]::new注意 : 方法引用是对Lambda表达式符合特定情况下的一种缩写,它使得我们的Lambda表达式更加的精简,也可以理解为Lambda表达式的缩写形式 , 同学们可以尝试着 , 将之前使用lambda的地方 , 改写成方法引用的形式 ,不过要注意的是方法引用只能"引用"已经存在的方法! 最后修改:2021 年 11 月 15 日 © 来自互联网 打赏 赞赏作者 支付宝微信 赞 如果觉得我的文章对你有用,请随意赞赏