Java基础之stream
1 流的概述
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream 接口API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
stream对象的操作有两种,一种是中间操作,得到的仍是stream;一种是结束操作,可得到基本类型或对象类型。又可细分为以下几种stream对象的操作主要有以下几种
无状态:指元素的处理不受之前元素的影响;
有状态:指该操作只有拿到所有元素之后才能继续下去。
非短路操作:指必须处理所有元素才能得到最终结果;
短路操作:指遇到某些符合条件的元素就可以得到最终结果,如 A || B,只要A为true,则无需判断B的结果。
2 创建stream
2.1 使用Collection下的 stream() 和 parallelStream() 方法(集合)
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream(); //获取一个顺序流
Stream<String> parallelStream = list.parallelStream(); //获取一个并行流
2.2 使用Arrays 中的 stream() 方法,将数组转成流(数组)
Integer[] nums = new Integer[10];
Stream<Integer> stream = Arrays.stream(nums);
2.3 使用Stream中的静态方法:of()、iterate()、generate()
Stream<Integer> stream = Stream.of(1,2,3,4,5,6);
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 2).limit(6);
stream2.forEach(System.out::println); // 0 2 4 6 8 10
Stream<Double> stream3 = Stream.generate(Math::random).limit(2);
stream3.forEach(System.out::println);
2.4 使用 BufferedReader.lines() 方法,将每行内容转成流(文件IO)
BufferedReader reader = new BufferedReader(new FileReader("F:\\test_stream.txt"));
Stream<String> lineStream = reader.lines();
lineStream.forEach(System.out::println);
2.5 使用 Pattern.splitAsStream() 方法,将字符串分隔成流(字符串)
Pattern pattern = Pattern.compile(",");
Stream<String> stringStream = pattern.splitAsStream("a,b,c,d");
stringStream.forEach(System.out::println);
3 stream的中间操作
中间操作完成后仍得到stream对象。按分类主要有以下几种
3.1 筛选与切片
filter:过滤流中的某些元素
limit(n):获取n个元素
skip(n):跳过n元素,配合limit(n)可实现分页
distinct():通过流中元素的 hashCode() 和 equals() 去除重复元素
concat(Stream a, Stream b): 合并两个Stream
从名字可以看出上面这几个操作类似于mysql的select语句的字句,比如where/limit/distinct。
Stream<Integer> stream = Stream.of(6, 4, 6, 7, 3, 9, 8, 10, 12, 14, 14);
Stream<Integer> newStream = stream.filter(s -> s > 5) //6 6 7 9 8 10 12 14 14
.distinct() //6 7 9 8 10 12 14
.skip(2) //9 8 10 12 14
.limit(2); //9 8
newStream.forEach(System.out::println);
3.2 映射
map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
List<String> list = Arrays.asList("a,b,c", "1,2,3");
//将每个元素转成一个新的且不带逗号的元素
Stream<String> s1 = list.stream().map(s -> s.replaceAll(",", ""));
s1.forEach(System.out::println); // abc 123
Stream<String> s3 = list.stream().flatMap(s -> {
//将每个元素转换成一个stream
String[] split = s.split(",");
Stream<String> s2 = Arrays.stream(split);
return s2;
});
s3.forEach(System.out::println); // a b c 1 2 3
3.3 排序
sorted():自然排序,流中元素需实现Comparable接口
sorted(Comparator com):定制排序,自定义Comparator排序器
代码例子
List<String> list = Arrays.asList("aa", "ff", "dd");
//String 类自身已实现Compareable接口
list.stream().sorted().forEach(System.out::println);// aa dd ff
Student s1 = new Student("aa", 10);
Student s2 = new Student("bb", 20);
Student s3 = new Student("aa", 30);
Student s4 = new Student("dd", 40);
List<Student> studentList = Arrays.asList(s1, s2, s3, s4);
//自定义排序:先按姓名升序,姓名相同则按年龄升序
studentList.stream().sorted(
(o1, o2) -> {
if (o1.getName().equals(o2.getName())) {
return o1.getAge() - o2.getAge();
} else {
return o1.getName().compareTo(o2.getName());
}
}
).forEach(System.out::println);
3.4 消费
peek:得到一个与当前流元素相同的流,而peek接收的是Consumer表达式(接收T对象,不返回值),对流中每个元素执行操作。
Student s1 = new Student("aa", 10);
Student s2 = new Student("bb", 20);
List<Student> studentList = Arrays.asList(s1, s2);
studentList.stream()
.peek(o -> o.setAge(100))
.forEach(System.out::println);
4 流的终止操作
4.1 聚合操作
这些操作类似于sql中的聚合函数,其实它们都可以用下文的reduce方法来实现。
allMatch:接收一个 Predicate 函数,当流中每个元素都符合该断言时才返回true,否则返回false
noneMatch:接收一个 Predicate 函数,当流中每个元素都不符合该断言时才返回true,否则返回false
anyMatch:接收一个 Predicate 函数,只要流中有一个元素满足该断言则返回true,否则返回false
findFirst:返回流中第一个元素
findAny:返回流中的任意元素
count:返回流中元素的总个数
max:返回流中元素最大值
min:返回流中元素最小值
// 判断文件名是否已存在
boolean isDuplicate = list.stream()
.anyMatch(file->MyStringUtil.isEqual(fileName,file.getFileName()));
4.2 约简操作
约简是一种终结操作,它们会将流约简为可以在程序中使用的非流值。上文的max/min实际也是约简操作。
4.2.1 函数式接口
说到reduce方法,不得不提函数式接口,因为reduce接收函数式接口作为参数(实际上是接口的实现类作为参数)。java8有如下常用的函数式接口
name | type | description |
---|---|---|
Consumer | Consumer< T > | 接收T对象,不返回值 |
Predicate | Predicate< T > | 接收T对象并返回boolean |
Function | Function< T, R > | 接收T对象,返回R对象 |
Supplier | Supplier< T > | 提供T对象(例如工厂),不接收值 |
UnaryOperator | UnaryOperator | 接收T对象,返回T对象 |
BinaryOperator | BinaryOperator | 接收两个T对象,返回T对象 |
description中接收对象,返回值实际上说的是接口中的方法,比如下面几个接口
BiFunction
它是一个函数式接口,包含的函数式方法定义如下:
@FunctionalInterface
public interface BiFunction<T, U, R> {
R apply(T t, U u);
}
BiFunction与Function不同点在于它接收两个输入返回一个输出; 而Function接收一个输入返回一个输出。注意它的两个输入、一个输出的类型可以是不一样的。
BinaryOperator
它实际上就是继承自BiFunction的一个接口,它的apply方法中3个泛型参数必须是同种类型。
public interface BinaryOperator<T> extends BiFunction<T,T,T>
4.2.2 reduce详细
Reduce中文含义为:减少、缩小;而Stream中的reduce方法干的正是这样的活:根据一定的规则将Stream中的元素进行计算后返回一个唯一的值。它有三个变种,输入参数分别是一个参数、二个参数以及三个参数;
Optional reduce(BinaryOperator accumulator):第一次执行时,accumulator函数的第一个参数为流中的第一个元素,第二个参数为流中元素的第二个元素;第二次执行时,第一个参数为第一次函数执行的结果,第二个参数为流中的第三个元素;依次类推。
// 求和
Integer sum = s.reduce(new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer integer, Integer integer2) {
return integer + integer2;
}
}).get();
T reduce(T identity, BinaryOperator accumulator):流程跟上面一样,只是第一次执行时,accumulator函数的第一个参数为identity,而第二个参数为流中的第一个元素。
如要将一个String类型的Stream中的所有元素连接到一起并在最前面添加[value]后返回
Stream<String> ss = Stream.of("test", "t1", "t2", "teeeee", "aaaa", "taaa");
/**
* 也可以使用Lambda语法:
* System.out.println(s.reduce("[value]", (s1, s2) -> s1.concat(s2)));
*/
System.out.println(ss.reduce("[value]", new BinaryOperator<String>() {
@Override
public String apply(String s, String s2) {
return s.concat(s2);
}
}));
U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator combiner):其中BiFunction的返回类型是U,在串行流(stream)中,该方法跟第二个方法一样,因为第三个参数combiner不会起作用。
在并行流(parallelStream)中,我们知道流被fork join出多个线程进行执行,此时每个线程的执行流程就跟第二个方法reduce(identity,accumulator)一样。而第三个参数combiner,则是将每个线程的执行结果当成一个新的流,进行汇总操作。
第三个参数combiner主要是使用在并行计算的场景下;如果Stream是非并行时,第三个参数实际上是不生效的。
非并行
/**
* 以下reduce生成的List将会是[aa, ab, c, ad]
* Lambda语法:
* System.out.println(s1.reduce(new ArrayList<String>(), (r, t) -> {r.add(t); return r; }, (r1, r2) -> r1));
*/
Stream<String> s1 = Stream.of("aa", "ab", "c", "ad");
System.out.println(s1.reduce(new ArrayList<String>(),
new BiFunction<ArrayList<String>, String, ArrayList<String>>() {
@Override
public ArrayList<String> apply(ArrayList<String> u, String s) {
u.add(s);
return u;
}
}, new BinaryOperator<ArrayList<String>>() {
@Override
public ArrayList<String> apply(ArrayList<String> strings, ArrayList<String> strings2) {
return strings;
}
}));