说到 Stream 便容易想到 I/O Stream,而实际上,谁规定“流”就一定是“IO 流”呢? 在 Java 8 中,得益于Lambda 所带来的函数式编程, 引入了一个全新的 Stream 概念,用于解决已有集合类库既有的弊端。
案例需求:• 有如下集合List
实现步骤: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 流来实现代码如何写呢?请看下面的案例。
案例步骤: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 接口的为终结方法,不再支持链式调用。 如下表所示: 方法名 方法作用 方法种类 是否支持链式调用 forEach 逐一处理 终结 否 count 统计个数 终结 否 filter 过滤 函数拼接 是 limit 取用前几个 函数拼接 是 skip 跳过前几个 函数拼接 是 map 映射 函数拼接 是 concat 组合 函数拼接 是
方法声明如下 说明 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())); }
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); }
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()); }
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); }
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); }
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); }
如果有两个流,希望合并成为一个流,那么可以使用 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; } public String toString() { return "Person{name='" + name + "'}"; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
要求如下:
使用传统的 for 循环(或增强 for 循环)依次进行以下若干操作步骤
使用 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); }