Stream流操作
Stream(流) 是一个来自数据源的元素队列,元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。数据源:流的来源,可以是集合,数组 等。(可参考使用Stream - 廖雪峰的官方网站 (liaoxuefeng.com))
Stream流操作是通过对把元素转换成Stream,然后通过中间操作,最终得到想要的结果,一般用在对集合操作中。
Stream流操作分为三步骤:
- 创建Stream流
- Stream中间处理
- 终止Steam
创建Stream流
- 集合获取流
通过Collection接口中的Stream方法生成。
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 创建出一个新的stream串行流对象
Stream<String> stream = strings.stream();
// 创建出一个可并行执行的stream流对象
Stream<String> stream = strings.parallelStream();
- 数组获取流
如果使用的不是集合而是数组,由于数组对象不可能添加默认方法,所以 Stream 接口中提供了静态方法of 。
String[] array = { "张无忌", "张翠山", "张三丰", "张一元" };
Stream<String> stream = Stream.of(array);
- Map获取流
java.util.Map 接口不是 Collection 的子接口,且其K-V数据结构不符合流元素的单一特征,所以获取对应的流需要分key、value或entry等情况:
Stream<String> keyStream = map.keySet().stream();
Stream<String> valueStream = map.values().stream();
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
- 文件获取流
通过Files.line方法得到一个流,并且得到的每个流是给定文件中的一行
Stream<String> lines = Files.lines(Paths.get("data.txt"), Charset.defaultCharset())
- 通过值生成
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
Stream中间处理
| API | 说明 |
|---|---|
limit() | 获取数据的条数 |
sorted() | 对stream的数据进行排序 |
peek() | 对stream流中的每个元素进行逐个遍历处理,返回处理后的stream流 |
concat() | 将两个流的数据合并起来为1个新的流,返回新的stream流 |
filter() | 筛选,按照筛选条件过滤符合要求的元素, 返回新的stream流 |
skip(long n) | 跳过前 n 条数据,必须 n >= 0; |
map() | 将流中的元素转换成另一种元素 |
flatMap() | 将流中的元素转换成另一种或多种元素 |
allMatch();allMatch();noneMatch() | 元素匹配 |
foreach() | |
max() | |
min() | |
empty() | |
toArray() | |
anyMatch() | |
findFirst() | |
findAny() | |
collect();count(); |
limit()
获取前 n 条数据,接收的参数是数据条数。
List<String> strings = Arrays.asList("abc", "vv", "bc", "efg", "abcd","", "jkl");
Stream<String> stream = strings.stream();
stream.limit(2).forEach(System.out::println);
输出:
abc
vv
sorted()
有两个重载,一个无参数,另外一个有个 Comparator类型的参数。
无参类型的按照自然顺序进行排序。
public static void main(String[] args) {
List<String> strings = Arrays.asList("abc", "vv", "bc", "efg", "abcd", "jkl");
Stream<String> stream = strings.stream();
stream.sorted().forEach(System.out::println);
}
有参数的需要自定义排序规则,例如按第二位的数字大小排序
public static void main(String[] args) {
List<String> strings = Arrays.asList("a1c", "v2", "b3", "e4", "a5cd", "j6l");
Stream<String> stream = strings.stream();
stream.sorted((x,y)->Integer.parseInt(x.substring(1,2))>Integer.parseInt(y.substring(1,2))?1:-1).forEach(e->System.out.println(e));
}
peek()
对stream流中的每个元素进行逐个遍历处理
public static void main(String[] args) {
List<String> strings = Arrays.asList("a1c", "v2", "b3", "e4", "a5cd", "j6l");
Stream<String> stream = strings.stream();
System.out.println("===========================");
stream.peek(System.out::println).count();
System.out.println("===========================");
}
上面代码的结果如下:
===========================
a1c
v2
b3
e4
a5cd
j6l
===========================
peek()是一个 stream流中间处理过程,如果没有结束操作的话,是不会执行的。例如上面的方法,把count()去掉的话,什么也不会输出。
concat()
将两个流合并成一个流
public static void main(String[] args) {
Stream<String> stringa = Stream.of("a","b","c");
Stream<String> stringb = Stream.of("d","e","f");
Stream<String> stream = Stream.concat(stringa, stringb);
System.out.println("===========================");
stream.forEach(System.out::println);
System.out.println("===========================");
}
filter
筛选,筛选出符合条件的流数据。
public static void main(String[] args) {
List<String> strings = Arrays.asList("a1c", "v2", "b3", "e4", "a5cd", "j6l");
System.out.println("===========================");
strings.stream().filter(i -> i.length() > 2).forEach(System.out::println);
System.out.println("===========================");
}
skip(long n)
跳过前n条数据,n必须大于等于0,否则抛出异常;
public static void main(String[] args) {
List<String> strings = Arrays.asList("a1c", "v2", "b3", "e4", "a5cd", "j6l");
strings.stream().skip(2).forEach(System.out::println);
}
//输出:
b3
e4
a5cd
j6l
map
通过原始数据元素,映射出新的类型。
private static void map(){
List<User> users = getUserData();
Stream<User> stream = users.stream();
List<UserDto> userDtos = stream.map(user -> dao2Dto(user)).collect(Collectors.toList());
}
private static UserDto dao2Dto(User user){
UserDto dto = new UserDto();
BeanUtils.copyProperties(user, dto);
//其他额外处理
return dto;
}
map和peek的区别
-
源码区别
map
/**
* Returns a stream consisting of the results of applying the given
* function to the elements of this stream.
*
* <p>This is an <a href="package-summary.html#StreamOps">intermediate
* operation</a>.
*
* @param <R> The element type of the new stream
* @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,
* <a href="package-summary.html#Statelessness">stateless</a>
* function to apply to each element
* @return the new stream
*/
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
peek
/**
* Returns a stream consisting of the elements of this stream, additionally
* performing the provided action on each element as elements are consumed
* from the resulting stream.
*
* <p>This is an <a href="package-summary.html#StreamOps">intermediate
* operation</a>.
*
* <p>For parallel stream pipelines, the action may be called at
* whatever time and in whatever thread the element is made available by the
* upstream operation. If the action modifies shared state,
* it is responsible for providing the required synchronization.
*
* @apiNote This method exists mainly to support debugging, where you want
* to see the elements as they flow past a certain point in a pipeline:
* <pre>{@code
* Stream.of("one", "two", "three", "four")
* .filter(e -> e.length() > 3)
* .peek(e -> System.out.println("Filtered value: " + e))
* .map(String::toUpperCase)
* .peek(e -> System.out.println("Mapped value: " + e))
* .collect(Collectors.toList());
* }</pre>
*
* @param action a <a href="package-summary.html#NonInterference">
* non-interfering</a> action to perform on the elements as
* they are consumed from the stream
* @return the new stream
*/
Stream<T> peek(Consumer<? super T> action);
- peek接受的参数是Consumer,Consumer的注释如下,可以看到是不返回结果的。
Represents an operation that accepts a single input argument and returns no result. Unlike most other functional interfaces, Consumer is expected to operate via side-effects.
map接受的参数是Function,Function是可以返回参数的。
-
使用
peek接收一个没有返回值的λ表达式,可以做一些输出,外部处理等。map接收一个有返回值的λ表达式,之后Stream的泛型类型将转换为map参数λ表达式返回的类型
flatMap
这是用在一些比较特别的场景下,当你的 Stream 是以下这几种结构的时候,需要用到 flatMap方法,用于将原有二维结构扁平化。
Stream<String[]>Stream<Set<String>>Stream<List<String>>
以上这三类结构,通过 flatMap方法,可以将结果转化为 Stream<String>这种形式,方便之后的其他操作。
比如下面这个方法,将List<List<User>>扁平处理,然后再使用 map或其他方法进行操作。
List<UserDto> userDtos = stream.flatMap(subUserList->subUserList.stream()).map(user -> dao2Dto(user)).collect(Collectors.toList());
map和flatmap的区别
- map 必须是一对一的,即每个元素都只能转换为1个新的元素
- flatMap 可以是一对多的,即每个元素都可以转换为1个或者多个新的元素
allMatch()、allMatch()、noneMatch()
-
allMatch匹配所有
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); if (integerList.stream().allMatch(i -> i > 3)) { System.out.println("值都大于3"); }
-
anyMatch匹配其中一个
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); if (integerList.stream().anyMatch(i -> i > 3)) { System.out.println("存在大于3的值"); }//存在大于3的值则打印等同于
for (Integer i : integerList) { if (i > 3) { System.out.println("存在大于3的值"); break; } }
-
noneMatch全部不匹配
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5); if (integerList.stream().noneMatch(i -> i > 3)) { System.out.println("值都小于3"); }
终止Steam
| API | 说明 |
|---|---|
foreach() | 遍历流中的元素,也可以直接对list使用 |
max() | 获取元素中的最大值 |
min() | 获取元素中的最小值 |
toArray() | 获取数组 |
anyMatch() | 返回boolean,判断是否匹配 |
findFirst() | 查找第一个元素,返回Optional类型 |
findAny() | 随机查找一个元素,返回Optional类型 |
collect() | 通过collection()获取返回的List或者Map这样的数据结构 |
count() | 计算流的数量 |
foreach()
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);
integerList.stream().forEach(System.out::println);
// 或者直接变量列表也可以
integerList.forEach(System.out::println)
max()、min()
public static void main(String[] args) {
List<Integer> integers = Arrays.asList(1, 2, 3, 4);
System.out.println(integers.stream().max(Integer::compare).get());
}
toArray()
collection是返回列表、map 等,toArray是返回数组,有两个重载,一个空参数,返回的是 Object[]。
另一个接收一个 IntFunction<R>类型参数。
@FunctionalInterface
public interface IntFunction<R> {
/**
* Applies this function to the given argument.
*
* @param value the function argument
* @return the function result
*/
R apply(int value);
}
比如像下面这样使用,参数是 User[]::new也就是new 一个 User 数组,长度为最后的 Stream 长度。
private static void toArray() {
List<User> users = getUserData();
Stream<User> stream = users.stream();
User[] userArray = stream.filter(user -> user.getGender().equals(0) && user.getAge() > 50).toArray(User[]::new);
}
生成集合
public void testCollectStopOptions() {
List<Dept> ids = Arrays.asList(new Dept(17), new Dept(22), new Dept(23));
// collect成list
List<Dept> collectList = ids.stream().filter(dept -> dept.getId() > 20).collect(Collectors.toList());
System.out.println("collectList:" + collectList);
// collect成Set
Set<Dept> collectSet = ids.stream().filter(dept -> dept.getId() > 20).collect(Collectors.toSet());
System.out.println("collectSet:" + collectSet);
// collect成HashMap,key为id,value为Dept对象
Map<Integer, Dept> collectMap = ids.stream().filter(dept -> dept.getId() > 20).collect(Collectors.toMap(Dept::getId, dept -> dept));
System.out.println("collectMap:" + collectMap);
}
生成字符串
生成拼接字符串,相比于普通方法,代码量变少,不用考虑最后的逗号
public void testCollectJoinStrings() {
List<String> ids = Arrays.asList("205", "10", "308", "49", "627", "193", "111", "193");
String joinResult = ids.stream().collect(Collectors.joining(","));
System.out.println("拼接后:" + joinResult);
}
Stream优势
代码更简洁、偏声明式的编码风格,更容易体现出代码的逻辑意图
逻辑间解耦,一个stream中间处理逻辑,无需关注上游与下游的内容,只需要按约定实现自身逻辑即可
函数式接口,延迟执行的特性,中间管道操作不管有多少步骤都不会立即执行,只有遇到终止操作的时候才会开始执行,可以避免一些中间不必要的操作消耗