Java8之Stream
什么是Stream?
Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API(java.util.stream.*)。
Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用Stream API来并行执行操作。简而言之,Stream API提供了一种高效且易于使用的处理数据的方式。
总结一句话就是:流是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据,流讲的是计算!”
创建流
要对流进行操作,首先要创建流,有6种方法可以获取流。
1. 通过集合创建流
@Test
public void testCollectionStream() {
List<String> strs = Arrays.asList("a", "b", "c", "d");
//创建普通流
Stream<String> stream = strs.stream();
//创建并行流(即多个线程处理)
Stream<String> streamParallel = strs.parallelStream();
stream.forEach(System.out::println);
streamParallel.forEach(System.out::println);
}
输出结果
a
b
c
d
c
d
a
b
2. 通过数组创建流
@Test
public void testArrayStream() {
int[] arr = new int[]{1, 2, 3, 4};
IntStream intStream = Arrays.stream(arr);
intStream.forEach(System.out::println);
System.out.println("========");
Integer[] arr2 = new Integer[]{1, 2, 3, 4};
Stream<Integer> stream = Arrays.stream(arr2);
stream.forEach(System.out::println);
}
输出结果
1
2
3
4
========
1
2
3
4
3. 通过Stream.of方法创建流
@Test
public void testStreamOf() {
Stream<Integer> stream = Stream.of(1, 2, 3);
stream.forEach(System.out::println);
}
输出结果
1
2
3
4. 创建规律的无限流
@Test
public void testUnlimitStream() {
Stream<Integer> stream = Stream.iterate(0, x -> x + 2).limit(3);
stream.forEach(System.out::println);
}
输出结果
0
2
4
5. 创建无限流
@Test
public void testUnlimitStream2() {
Stream<String> stream = Stream.generate(() -> "number" + new Random().nextInt()).limit(3);
stream.forEach(System.out::println);
}
输出结果
number996910758
number715217789
number-1287821385
6. 创建空流
@Test
public void testEmptyStream() {
Stream<Integer> stream = Stream.empty();
stream.forEach(System.out::println);
}
操作流
Stream操作分类
分为两大类,中间操作和终结操作。
中间操作又可以分为无状态(Stateless)与有状态(Stateful)操作。
- 无状态是指元素的处理不受之前元素的影响,
- 有状态是指该操作只有拿到所有元素之后才能继续下去。
终结操作又可以分为短路(Short-circuiting)与非短路(Unshort-circuiting)操作。
- 短路是指遇到某些符合条件的元素就可以得到最终结果,
- 非短路是指必须处理完所有元素才能得到最终结果。
关于stream的操作分类见下图(图片来源于网络,侵删)。。。
一些常用的方法见下表。
方法 | 参数 | 作用 |
---|---|---|
filter | Predicate<? super T> predicate | 过滤 |
map | 转换 | |
flatMap | 转换+扁平化 | |
distinct | 去重 | |
sorted | 排序 | |
limit | 限量 | |
skip | 跳过 | |
forEach | 遍历 | |
anyMatch/allMatch | 匹配 |
1. filter
filter接收一个Predicate函数接口参数,boolean test(T t);即接收一个参数,返回boolean类型。
@Test
public void testFilter() {
Integer[] arr = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
Arrays.stream(arr).filter(x -> x > 3 && x < 8).forEach(System.out::println);
}
输出结果
4
5
6
7
2. map
map接收一个Function<T, R>函数接口,R apply(T t);即接收一个参数,并且有返回值。
@Test
public void testMap() {
String[] arr = new String[]{"yes", "YES", "no", "NO"};
Arrays.stream(arr).map(x -> x.toLowerCase()).forEach(System.out::println);
System.out.println("==============================");
Arrays.stream(arr).forEach(System.out::println);
}
输出结果
yes
yes
no
no
==============================
yes
YES
no
NO
注意:map操作并未改变原有数组内的元素。
3. flatMap
flatMap接收一个Function<T, R>函数接口: Stream flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);即入参为集合类型,返回Stream类型。
@Test
public void testFlatMap() {
String[] arr1 = {"a", "b"};
String[] arr2 = {"e", "f"};
String[] arr3 = {"h", "j"};
// Stream.of(arr1, arr2, arr3).flatMap(x -> Arrays.stream(x)).forEach(System.out::println);
Stream.of(arr1, arr2, arr3).flatMap(Arrays::stream).forEach(System.out::println);
}
flatMap会将lambda表达式函数中所有返回的stream进行合并,然后对合并后的流进行处理。
输出结果
a
b
e
f
h
j
为了加深对于flatMap的理解,再举一个例子。
private static class Klass {
private int field;
public Klass(int field) {
this.field = field;
}
@Override
public String toString() {
return "field=" + field;
}
}
private static class KlassGroup {
private List<Klass> group = new ArrayList<>();
public KlassGroup(Klass... objList) {
for (Klass item : objList) {
this.group.add(item);
}
}
public List<Klass> getKlassList() {
return group;
}
}
现在有一个groupList对象列表,列表中的元素仍然是一个列表,我们的需求是将内层列表中的元素取出来,放到一个ArrayList中。
List<KlassGroup> groupList = Arrays.asList(
new KlassGroup(new Klass(1), new Klass(2), new Klass(3)),
new KlassGroup(new Klass(4), new Klass(5), new Klass(6)),
new KlassGroup(new Klass(7), new Klass(8), new Klass(9)),
new KlassGroup(new Klass(10))
);
我们可以用两层for循环来做,像这样。
List<Klass> result2 = new ArrayList<>();
for (KlassGroup group : groupList) {
for (Klass klass : group.getKlassList()) {
result2.add(klass);
}
}
如果用flatMap来写的话,有这样。
List<Klass> result3 = groupList.stream()
.flatMap(it -> it.getKlassList().stream())
.collect(Collectors.toList());
filter方法、map方法以及flatMap方法均为懒操作,只有等到需要结果的时候才执行。
4. distinct
@Test
public void testDistinct() {
List<String> list = new ArrayList<String>() {
{
add("user1");
add("user2");
add("user2");
add("user2");
}
};
list.stream().distinct().forEach(System.out::println);
}
输出结果
user1
user2
5. sorted
@Test
public void testSorted1() {
String[] arr1 = {"abc", "a", "bc", "abcd"};
// 按照字符长度排序
System.out.println("lambda表达式");
Arrays.stream(arr1).sorted((x, y) -> {
if (x.length() > y.length())
return 1;
else if (x.length() < y.length())
return -1;
else
return 0;
}).forEach(System.out::println);
// Comparator.comparing是一个键提取的功能
System.out.println("方法引用");
Arrays.stream(arr1).sorted(Comparator.comparing(String::length)).forEach(System.out::println);
}
输出结果
lambda表达式
a
bc
abc
abcd
方法引用
a
bc
abc
abcd
6. reversed
/**
* 倒序
* reversed(),java8泛型推导的问题,所以如果comparing里面是非方法引用的lambda表达式就没办法直接使用reversed()
* Comparator.reverseOrder():也是用于翻转顺序,用于比较对象(Stream里面的类型必须是可比较的)
* Comparator.naturalOrder():返回一个自然排序比较器,用于比较对象(Stream里面的类型必须是可比较的)
*/
@Test
public void testSorted2_() {
String[] arr1 = {"abc", "a", "bc", "abcd"};
System.out.println("reversed(),这里是按照字符串长度倒序排序");
Arrays.stream(arr1).sorted(Comparator.comparing(String::length).reversed()).forEach(System.out::println);
System.out.println("Comparator.reverseOrder(),这里是按照首字母倒序排序");
Arrays.stream(arr1).sorted(Comparator.reverseOrder()).forEach(System.out::println);
System.out.println("Comparator.naturalOrder(),这里是按照首字母顺序排序");
Arrays.stream(arr1).sorted(Comparator.naturalOrder()).forEach(System.out::println);
}
输出结果
reversed(),这里是按照字符串长度倒序排序
abcd
abc
bc
a
Comparator.reverseOrder(),这里是按照首字母倒序排序
bc
abcd
abc
a
Comparator.naturalOrder(),这里是按照首字母顺序排序
a
abc
abcd
bc
7. limit从流中获得前n个数据
/**
* limit,限制从流中获得前n个数据
*/
@Test
public void testLimit() {
Stream.iterate(1, x -> x + 2).limit(3).forEach(System.out::println);
}
输出结果
1
3
5
8. forEach
@Test
public void testForEach() {
List<String> list = new ArrayList<String>() {
{
add("a");
add("b");
}
};
list.stream().forEach(System.out::println);
}
输出结果
a
b
9. redeuce
@Test
public void testReduce() {
Optional<Integer> optional = Stream.of(1, 2, 3).filter(x -> x > 1).reduce((x, y) -> x + y);
System.out.println(optional.get());
}
5
10.collect
collect是一个非常常用的操作,可以将流转换为其他形式。
用户实体类
@Data
@AllArgsConstructor
@ToString
public class User {
private String name;
private Integer age;
private Integer salary;
}
toList、toSet、toMap
@Test
public void testCollect() {
List<User> users = Arrays.asList(new User("张三", 19, 1000),
new User("张三", 58, 2000),
new User("李四", 38, 3000),
new User("赵五", 48, 4000)
);
List<String> collect = users.stream().map(x -> x.getName()).collect(Collectors.toList());
Set<String> collect1 = users.stream().map(x -> x.getName()).collect(Collectors.toSet());
Map<Integer, String> collect2 = users.stream().collect(Collectors.toMap(x -> x.getAge(), x -> x.getName()));
System.out.println(collect);
System.out.println(collect1);
System.out.println(collect2);
}
输出结果
[张三, 张三, 李四, 赵五]
[李四, 张三, 赵五]
{48=赵五, 19=张三, 38=李四, 58=张三}
总结
- Stream 自己不会存储元素。
- Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
- Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
参考文章链接: