1. 概念引入
- 利用lambda函数式编程,解决已有集合或数组既有的弊端。这个就是Stream流。
- 优点:
- 提高编程效率和程序可读性;
- 支持并发方式处理。
1.1 简述集合循环遍历的弊端
在没有引入Stream流前:
- 集合的线性遍历是一次性的。如果需要再次遍历,就只能在另一个循环中从头开始遍历。
- 对集合内部元素的每次操作(比如筛选指定元素)都需要通过遍历的方式。
2. Stream流概述
- 与传统意义的流比较:
- 传统的IO流,就好比运河,将数据从一个地方传送到另外的一个地方。发送/接收的数据基本是一致的。
- 而Stream流则好比工厂的流水线,通过流水线上的一步步操作,最终获得所需要的“产品”。
- 操作Stream流的含义:
- Stream流本质是一个集合元素的函数模型,不是集合也不是数据结构,本身不存储任何元素/地址值。
- Stream流的操作实际是对函数模型进行转换、设置,集合元素实际上并未被处理。只有Stream流的终结方法被执行的时候,集合元素才会根据指定的模型进行操作(lambda表达式的延迟执行)。
3. 获得Stream流的方式
java.util.stream.Stream<T>
Stream流最常用的接口。注意,这个不是函数式接口,别直接用lambda- Collection集合都能通过Collection接口中的默认方法stream获取Stream流:
集合名.stream();
- Stream接口的静态方法
of
可以获得数组对应的Stream流:Stream.of(数组名)
- Collection集合都能通过Collection接口中的默认方法stream获取Stream流:
3.1 Collection获取流
集合名.stream();
//ArrayList
List<Integer> list = new ArrayList<>();
Stream<Integer> ALscream = list.stream();
//Set
Set<Integer> Set = new HashSet<>();
Stream<Integer> Setscream = Set.stream();
3.2 Map获得流
- Map不能直接获得对应的流,键值对的数据结构与流的单一特征不相符。
- Map获得流需要分成获得Key的流、获得Value的流、获得Entry的流三种:
Map<String, String> map = new HashMap<>();
//获得Key的流
Stream<String> keyStream = map.keySet().stream();
//获得Value的流
Stream<String> valueStream = map.values().stream();
//获得Entry的流
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
3.3 数组获得流
Stream.of(数组名)
int[] array = {1,351,3,2132,884,35};
Stream<Integer> stream = Stream.of(array);
4. Stream流常用方法
- 终结方法:返回值类型不在是Stream接口自身类型的方法,不支持链式调用。可以视为Stream流模型已确定,执行该方法时集合将按照指定模型进行操作。常用的终结方法有:
count
、foreach
。 - 非终结方法:返回值类型是Stream接口自身类型的方法,支持链式调用。(除终结方法以外的Stream流方法)
注意事项:
- Stream流一旦调用终结方法,就会被关闭,不能继续调用方法操作流中的元素!!
- 如果通过流方法产生新的流,则旧的流就不能继续操作!!
4.1 filter过滤
转换成另外一个子集流,其方法声明如下
Stream<T> filter(Predicate<? super T> predicate);
该接口接收Predicate函数式接口参数作为筛选条件,返回true则保留元素,返回false则舍弃元素。
使用格式:
集合流名.filter(筛选条件)
//示例 String[] strs = {"马云","马化腾","周杰伦"}; Stream<String> original = Stream.of(str); Stream<String> result = original.filter(s ‐> s.startsWith("马"));//用lambda指定筛选条件
4.2 count统计个数
- 统计元素个数,返回long值
- 格式:
long count();
- 使用格式:
流名.count();
4.3 limit获取前几个
- 对流进行截取,取出前几个元素放到新流中
- 格式:
Stream<T> limit(long maxSize);
- 集合当前长度大于参数就截取,当前长度小于参数则会将所有元素存储到新流中。
- maxSize不能为负数。
- 使用格式:
流名.limit(long maxSize);
4.4 skip跳过前几个
- 跳过前几个元素,获得截取后的新流
- 格式:
Stream<T> skip(long n);
- 集合当前长度大于参数就跳过前n个,小于参数返回长度为0的空流。
- n不能为负数
- 使用格式:
流名.skip(long n);
4.5 map映射
- 使用map操作可遍历集合中的每个对象并对其操作
- map之后用调用collect方法可得到操作后的集合
- 格式:
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
- 需要一个Function函数式接口,将当前流中的T类型数据转换为另一种R类型的流。
- 映射:一一对应
public static void main(String[] args) {
Stream<String> original = Stream.of("aa", "bb", "cc");
Stream<Integer> result = original.map(Integer::parseInt);//<=>original.map(s->Integer.parseInt(s))
}
4.6 concat组合
两个流合并成一个流,使用Stream流的静态方法concat方法
格式:
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
使用格式:
Stream.concat(StreamA,StreamB);
注意:合并之后,原来的两个流就不能再使用
4.7 foreach逐一处理
- 不是for循环中的增强for(for-each)
- 元素的逐一消费(操作)
- 格式:
void forEach(Consumer<? super T> action);
- 需要一个Consumer接口,将流中每个元素交由其处理
Stream<String> stream = Stream.of("马云","马化腾","周杰伦");
stream.forEach(System.out::println);//<=>s->System.out.println(s);
4.8 示例案例
public class DemoTest {
public static void main(String[] args) {
List<String> one = new ArrayList<>();
Collections.addAll(one,"迪丽热巴","宋远桥","苏星河","老子","庄子","孙子","洪七公");
List<String> two = new ArrayList<>();
Collections.addAll(two,"古力娜扎","张无忌","张三丰","赵丽颖","张二狗","张天爱","张三");
//1. 第一个队伍只要名字为3个字的成员姓名;
//2. 第一个队伍筛选之后只要前3个人;
Stream<String> stream1 = one.stream().filter(s -> s.length() == 3).limit(3);
//3. 第二个队伍只要姓张的成员姓名;
//4. 第二个队伍筛选之后不要前2个人;
Stream<String> stream2 = two.stream().filter(s -> s.startsWith("张")).skip(2);
//5. 将两个队伍合并为一个队伍;
Stream<String> stream = Stream.concat(stream1, stream2);
Stream<Person> finalStream = stream.map(Person::new);//用map方法转换类型
finalStream.forEach(System.out::println);
/*上述三行代码可以直接合并成
* Stream.concat(stream1, stream2).map(Person::new).forEach(System.out::println); */
}
}
public class Person {
private String name;
public Person() {}
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{name='" + name + "'}";
}
/*省略getter&setter*/
}
5.Stream流的结果收集
- 一个流仅能收集一次结果
- 收集结果分为三种方式:收集到List,收集到Set,收集到数组。
5.1 收集到集合
collect(Collector<T>);
- 可通过Collectors工具类的方法获得Collector实例
5.1.1 收集到List集合
- 使用格式:
流名.collect(Collectors.toList());
5.1.2 收集到Set集合
- 使用格式:
流名.collect(Collectors.toSet());
5.2 收集到数组
- 方法1:
Object[] toArray();
- 该方法返回值是Object[]
但返回值是个Object[],如何解决泛型数组的问题?
使用lambda和方法引用对toArray进行重载,传递一个
IntFunction<A[]>
的函数,从而从外面指定泛型参数。
- 方法2:
<A> A[] toArray(IntFunction<A[]> generator);
- 该方法返回的数组将根据传入的IntFunction的类型确定。
- toArray()的参数是IntFunction接口的实现类,可用lambda表达式,也可用方法引用
public class Notes05 {
public static void main(String[] args) {
Stream<String> stream = Stream.of("10", "20", "30", "40", "50");
//方式1:
Object[] objArray = stream.toArray();
//方式2:
String[] strArray = stream.toArray(String[]::new);//stream.toArray(length -> new String[length]);
}
}
6. 并发流
上述例子均使用串行流(单线程执行),Stream也可以使用并行流(多线程执行)
default Stream stream()
:返回串行流;default Stream parallelStream()
:返回并行流;
6.1 流的概述
- 串行流:在当前线程中按序执行
- 并发流: 开启多个线程,和当前线程一同执行(开启的线程个数由系统根据计算机性能自动匹配)
6.2 获取并发流
6.2.1 串行流转换为并发流
- 通过串行流对象调用parallel方法获得:
串行流名.parallel();
6.2.2 直接获取并发流
- 通过集合对象调用parallelStream方法直接获得:
集合对象名.parallelStream();
6.3 串行流和并发流的选择
- 数据量不大时,串行流往往更快。因为开线程池和配备资源也要时间;
若任务涉及I/O操作且任务间不相互依赖,可以考虑并行化。
- 若任务之间是独立的,表明代码是可被并行化
- 因为并行环境中任务的执行顺序是不确定的,因此对依赖顺序的任务而言,并行化也许不能给出正确的结果