天天看点

Stream 流Stream 流

Stream 流

​
说到 Stream 便容易想到 I/O Stream,而实际上,谁规定“流”就一定是“IO 流”呢?
​
在 Java 8 中,得益于Lambda 所带来的函数式编程,
引入了一个全新的 Stream 概念,用于解决已有集合类库既有的弊端。      

Stream 流引入案例

 案例需求:• 有如下集合List<String> list = new ArrayList<>();list.add("张无忌");list.add("周芷若");list.add("赵敏"); list.add("张强");list.add("张三丰");• 按要求执行以下三个操作1) 首先筛选所有姓张的人。2) 然后筛选名字有三个字的人。3) 最后进行对结果进行打印输出。

JDK1.8 之前的实现方式

 实现步骤:1) 创建原始的集合添加上面的数据2) 创建一个新的集合用于存储姓张的姓名,判断字符串是否以"张"开头,如果符合要求添加到集合中。3) 再创建一个集合,遍历上面的集合,判断每个元素的长度,如果是 3 个就添加到集合中。4) 循环输出最后过滤的结果 实例代码:

public class Demo {
 public static void main(String[] args){
 List<String> list = new ArrayList<>();
 list.add("张无忌");
 list.add("周芷若");
 list.add("赵敏");
 list.add("张强");
 list.add("张三丰");
     
 // 创建集合:存储姓张的
 List<String> oneList = new ArrayList<>();
     
 // 首先筛选所有姓张的人
 for(String name:list) {
 if (name.startsWith("张")){
 oneList.add(name);
 }
 }
     
 // 创建集合:存储名字三个字且姓张的
 List<String> twoList = new ArrayList<>();
     
 // 然后筛选名字有三个字的人
 for(String name:oneList) {
 if (name.length() == 3){
 twoList.add(name);
 }
 }
     
 // 最后进行对结果进行打印输出。
 for (String name: twoList) {
 System.out.println(name);
 }
 }
}
​
//每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。
//这是理所当然的么?
//不是。循环是做事情的方式,而不是目的。
​
//另一方面,使用线性循环就意味着只能遍历一次。
//如果希望再次遍历,只能再使用另一个循环从头开始。
​
//Lambda 的衍生物 Stream 能给我们带来怎样更加优雅的写法呢?      

Stream 流的初体验

上面的案例如果使用 Stream 流来实现代码如何写呢?请看下面的案例。

 案例步骤:1) 将数组转成一个 List,"张无忌","周芷若","赵敏","张强","张三丰"2) 将 list 转成 stream 流,调用两次 filter()方法,filter()方法的参数是 Predicate,使用 Lambda 传递参数。3) 首先筛选所有姓张的人,然后筛选名字有三个字的人,最后使用 forEach 进行对结果进行打印输出 案例代码:

import java.util.Arrays;
import java.util.List;
​
public class DemoBegin {
 public static void main(String[] args) {
 List<String> list = Arrays.asList("张无忌", "周芷若", "赵敏", "张强", "张三丰");
     
 //首先筛选所有姓张的人,然后筛选名字有三个字的人,最后进行对结果进行打印输出
 list.stream().filter(s -> s.startsWith("张")).filter(s -> s.length() ==
3).forEach(System.out::println);
 }
}
//直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为 3、逐一打印。
                            //获取流、list.stream()
                            //过滤姓张、filter(s -> s.startsWith("张"))
                            //过滤长度为3、filter(s -> s.length() ==3)
                            //逐一打印。forEach(System.out::println)      

获取流的方式

java.util.stream.Stream是 Java 8 新加入的最常用的流接口。//这不是一个函数式接口。
    
//获取一个流非常简单,有以下几种常用的方式:
​
1.collection 对象.stream() 
    所有的 Collection 集合都可以通过 stream()这个方法获取流,这是 Collection 接口中的默认方法。
    
2.Stream.of() 
    Stream 接口的静态方法 of()可以得到数组对应的流。
    
根据 Collection 获取流
首先,java.util.Collection 接口中加入了 default 方法 stream()用来获取流,
所以其所有子接口和实现类均可获取流。
​
//只要是 Collection 单列集合,都可以直接调用 stream()方法获得流对象。      

 案例说明:创建一个 List 和 Set 集合,添加几个元素,调用它们的 stream()方法得到它们的流对象。并且调用 forEach()方法输出每个元素。 案例代码:

public class Demo02 {
 public static void main(String[] args){
     
 // 创建 List 集合
 List<String> list = new ArrayList<>();
     
 // 创建 Set 集合
 Set<String> set = new HashSet<>();
     
 // 获得流对象
 Stream<String> s1 = list.stream();
 Stream<String> s2 = set.stream();
 }
}      

根据数组获取流

如果使用的不是集合或映射而是数组,
由于数组对象不可能添加默认方法,所以 Stream 接口中提供了静态方法 of,使用很简单:
​
static  Stream of(T... values) 
        通过指定的值返回一个有顺序的流
        参数:指定流中的所有元素
        返回:一个新的流      

 示例:通过 Stream 类的静态方法 of 得到一组字符串的流对象

Stream<String> s = Stream.of("a","b","c");      

Stream 常用方法

流操作的方法很丰富,这里介绍一些常用的方法,这些方法可以被分成两种://终结方法和非终结方法      

非终结方法与终结方法

//凡是返回值仍然为 Stream 接口的为非终结方法(函数拼接方法),它们支持链式调用;
//而返回值不再为Stream 接口的为终结方法,不再支持链式调用。
​
如下表所示:
方法名 方法作用 方法种类 是否支持链式调用
forEach 逐一处理 终结     否
count 统计个数 终结       否
filter 过滤     函数拼接  是
limit 取用前几个 函数拼接  是
skip 跳过前几个 函数拼接   是
map    映射     函数拼接   是
concat 组合     函数拼接   是      

Stream 流中逐一处理的方法:forEach

方法声明如下 说明
void forEach(Consumer action) 对此流的每个元素执行一个操作,这是一个终结方法。
对于并行流,此操作并不保证流的操作顺序,因为这样做将牺牲并行性的好处。
​
参数:对元素执行的操作      

方法演示 案例说明:使用 of 方法将一组字符串转成流,调用 forEach 方法将每个元素转成大写,输出每个元素。 执行代码:

public static void main(String[] args){
 // 通过 Stream 类的静态方法获得流对象
 Stream<String> s1 = Stream.of("Jack","Rose","Hello","Select","Insert");
    
 // 调用 forEach 方法
 s1.forEach(s-> System.out.println(s.toUpperCase()));
}      

过滤:filter

Stream filter(Predicate p) 返回一个与给定判断条件匹配的元素组成的流。
​
参数:谓词对象,指定判断条件,应用于每个元素以确定是否应该包含它。
返回:一个新的流      
Predicate 接口
此前我们已经学习过 java.util.stream.Predicate 函数式接口,
​
其中唯一的抽象方法为:
boolean test(T t);
该方法将会产生一个 boolean 值结果,代表指定的条件是否满足。
如果结果为 true,那么 Stream 流的 filter方法将会留用元素;
如果结果为 false,那么 filter 方法将会舍弃元素。
​      

基本使用

 案例说明:1) 通过 Stream 类的 of 静态方法获得流对象:"Jack","Rose","Hello","Select","Insert"2) 得到所有长度是 4 个的元素生成一个新的流3) 使用 forEach()输出打印流对象 案例代码:

public static void main(String[] args){
 // 通过 Stream 类的静态方法获得流对象
 Stream<String> s = Stream.of("Jack", "Rose", "Hello", "Select", "Insert");
    
 // filter 方法
 Stream<String> ss = s.filter(s1 -> s1.length() == 4);
 ss.forEach(System.out::println);
​
}      

统计个数:count

long count() 返回此流中元素的计数。这是一个终结方法。
返回:此流中元素的个数。      

基本使用 案例说明:统计一个流中元素的个数 案例代码:

public static void main(String[] args){
 // 通过 Stream 类的静态方法获得流对象
 Stream<String> s = Stream.of("abc","cc","cd","d","e");
    
 // 调用 count 方法
 System.out.println(s.count());
}      

获取前几个:limit

Stream limit(long maxSize) 获得流中前 maxSize 个元素,将元素添加到另一个流中返回
如果 maxSize 大于等于当前流的元素个数,则所有元素都会获取到
如果 maxSize 等于 0,则会获得一个空流。      

基本使用 案例说明:得到流中前 3 个元素输出,得到流中前 10 个元素输出,得到流中前 0 个元素输出 案例代码:

public static void main(String[] args){
 // 通过 Stream 类的静态方法获得流对象
 Stream<String> s = Stream.of("abc","cc","cd","d","e");
    
 // 调用 limit 方法    
 //得到流中前 3 个元素输出
 s.limit(3).forEach(System.out::println); 
    
 //得到流中前 10 个元素输出,大于等于当前流的元素个数,则所有元素都会获取到 
 //s.limit(10).forEach(System.out::println);  
    
 //得到流中前 0 个元素输出,则会获得一个空流
 //s.limit(0).forEach(System.out::println); 
}      

跳过前几个:skip

Stream skip(long n) 跳过前面 n 元素,将后面元素添加到另一个流中      

基本使用 案例说明:跳过前面 3 个元素得到一个新的流,并且使用 forEach 输出。

 案例代码:

public static void main(String[] args){
 // 通过 Stream 类的静态方法获得流对象
 Stream<String> s = Stream.of("abc","cc","cd","d","e");
    
 // 调用 skip 方法
 s.skip(1).forEach(System.out::println);
}      

映射:map

Stream map(Function mapper)
使用 map 可以遍历集合中的每个元素,并对其进行操作。
将一种类型映射成另一种类型,得到一个新的流。
​
该接口需要一个 Function 函数式接口参数,可以将当前流中的 T 类型数据转换为另一种 R 类型的流。
​
​
Function 接口
​
此前我们已经学习过 java.util.stream.Function 函数式接口,其中唯一的抽象方法为:
​
R apply(T t);这可以将一种 T 类型转换成为 R 类型,而这种转换的动作,就称为“映射”。      

 案例说明:1) 有一组字符串类型的数字流2) 使用 map 方法,将字符串类型全部转成整数,可以使用静态方法引用,映射成一个新的流3) 使用 map 方法,将整数类型每个元素加 1,映射成一个新的流4) 使用 forEach 输出新流中的每个元素。

注:流只能消费一次,即只能使用 forEach 一次,第二次使用会抛出异常。
java.lang.IllegalStateException: stream has already been operated upon or closed      

 案例代码:

public static void main(String[] args) {
    // 通过 Stream 类的静态方法获得流对象
    Stream<String> s1 = Stream.of("123","46","456","673","131");
​
    //支持链式写法
    Stream<Integer> s2 = s1.map(Integer::parseInt);
​
    Stream<Integer> s3 = s2.map(m ->++m);
​
    s3.forEach(System.out::println);
}      

组合:concat

如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat:      
public static  Stream concat(Stream a,Stream b)
​
静态方法,通过类名调用,将两个流合并成一个流返回一个新的流      

基本使用 案例说明1) 从两个字符串数组创建两个流2) 使用 concat 方法对两个流进行拼接3) 输出拼接后的流 案例代码:

public static void main(String[] args) {
 // 通过 Stream 类的静态方法获得流对象
 Stream<String> s1 = Stream.of("123","46","456","673","131");
 Stream<String> s2 = Stream.of("abc","bbb","ccc","ddd");
 // 调用 concat 方法
 Stream.concat(s1,s2).forEach(s -> System.out.print(s + " "));
}      

流的综合案例

 案例说明现在有两个 ArrayList 集合存储队伍当中的多个成员姓名,两个队伍(集合)的代码如下:

List<String> one = new ArrayList<>();
one.add("迪丽热巴");
one.add("宋远桥");
one.add("苏星河");
one.add("老子");
one.add("庄子");
one.add("孙子");
one.add("洪七公");
List<String> two = new ArrayList<>();
two.add("古力娜扎");
two.add("张无忌");
two.add("张三丰");
two.add("赵丽颖");
two.add("张二狗");
two.add("张天爱");
two.add("张三");
Person 类的代码如下
public class Person {
 private String name;
 public Person() {}
 public Person(String name) {
 this.name = name;
 }
 @Override
 public String toString() {
 return "Person{name='" + name + "'}";
 }
 public String getName() {
 return name;
 }
 public void setName(String name) {
 this.name = name;
 }
}      

 要求如下:

  1. 使用传统的 for 循环(或增强 for 循环)依次进行以下若干操作步骤
  2. 使用 Stream 方式依次进行以下若干操作步骤1) 第一个队伍只要名字为 3 个字的成员姓名;2) 第一个队伍筛选之后只要前 3 个人;3) 第二个队伍只要姓张的成员姓名;4) 第二个队伍筛选之后不要前 2 个人;5) 将两个队伍合并为一个队伍;6) 根据姓名创建 Person 对象;7) 打印整个队伍的 Person 对象信息。 实现代码
    public static void main(String[] args){
        List<String> one = new ArrayList<>();
        one.add("迪丽热巴");
        one.add("宋远桥");
        one.add("苏星河");
        one.add("老子");
        one.add("庄子");
        one.add("孙子");
        one.add("洪七公");
        List<String> two = new ArrayList<>();
        two.add("古力娜扎");
        two.add("张无忌");
        two.add("张三丰");
        two.add("赵丽颖");
        two.add("张二狗");
        two.add("张天爱");
        two.add("张三");
        // 1. 第一个队伍只要名字为 3 个字的成员姓名;
        // 2. 第一个队伍筛选之后只要前 3 个人;
        Stream<String>  s1 = one.stream().filter(s -> s.length() == 3).limit(3);
        // 3. 第二个队伍只要姓张的成员姓名;
        // 4. 第二个队伍筛选之后不要前 2 个人;
        Stream<String>  s2 = two.stream().filter(s -> s.startsWith("张")).skip(2);
        // 5. 将两个队伍合并为一个队伍;
        Stream<String>  s3 = Stream.concat(s1, s2);
        // 6. 根据姓名Person创建 Person 对象;
    ​
        Stream<Person> s4 = s3.map(Person::new);
        // 7. 打印整个队伍的 Person 对象信息。
        s4.forEach(System.out::println);
    }