1.简介
注:本文内容均来源于我看了java核心技术 卷二的Stream内容后写的,当一个笔记
Stream是java8引入的新特性,他提供一种从使用者角度去完成任务的数据视图,举个例子,获取一个从0到9的Integer类型的list,普通的做法要建一个循环,然后new一个Integer对象,然后再放进集合里面,而Stream则提供了从完成目标上面提供可选操作,而不是去实现,比如刚刚那个生成Integerlist的目标
普通的做法:
List<Integer> list=new ArrayList();
for(int i=0;i<10;i++){
list.add(new Integer(i));
}
运用Stream+lambda
IntStream.range(0,10).mapToObj(Integer::new).collect(Collectors.toList());
//或 IntStream.range(0,10).boxed().collect(Collectors.toList());
对比得出,我们只需告诉Stream,我们要做什么,而不用具体的实现它,即“做什么而非怎么做”,这便是好处之一。
流和集合看起来很相似,其实他们存在显著差异
1.流并不存储元素。元素来源于其他地方
2.流的操作并不会修改数据源。
3.流的操作尽可能是惰性的,直到需要结果时,操作才会执行
4.流在终止以后会强制执行之前的惰性操作,然后就不能再继续使用
2.流的使用
2.1 流的创建
产生一个元素为指定值的流
//static<T> Stream<T> of(T...values)
Stream.<Integer>of(1,2,3,4,5,6,7);
产生一个不包含任何元素的流
//static<T> Stream<T> empty()
Stream.empty();
产生一个无限流,它的值是通过反复调用函数s创建的
//static<T> Stream<T> generate(Supplier<T> s)
Stream.generate(Math::random);
产生一个无限流,他以seed为迭代开始,对此反复进行f函数的操作,并调用迭代结果作为下次一次迭代的值。
//static<T> Stream<T> iterate(T seed,UnaryOperator<T> f)
Stream.iterate(BigDecimal.ZERO,n->n.add(BigDecimal.ONE));
2.2filter、map、flatMap
产生一个流,它包含当前流中所有满足predicate条件的元素
Stream<T> filter(Predicate<? super T> predicate)
IntStream.range(0,10).filter(n->n%2!=0).forEach(System.out::print);
//13579
产生一个流,将mapper应用于当前流中所有元素产生的结果,即这个流的结果是搜集经过mapper处理的元素结果
<R> Stream<R> map(Function<? super T,? extends R> mapper)
IntStream.range(0,5).map(n->n*10).forEach(System.out::print);
//010203040
产生一个流,它是通过将mapper应用于当前流中的所有元素所产生的结果连接到一起而获得的。(注意,这里的每个结果都是一个流),其实就是把几个流的结果合并成一个流,A->B属性(B属性是一个集合),最终得到是所有A元素的所有B元素
<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
Stream<Stream<String>> test=Stream.of("abc","bcd","cde").map(n->{
return Arrays.asList(n.substring(0,1)).stream();
});
test.collect(Collectors.toList())//List<Stream<String>>
.forEach(n->n.forEach(System.out::println));
Stream<String> flat= Stream.of("abc","bcd","cde").flatMap(n->{
return Arrays.asList(n.substring(0,1)).stream();
});
flat.collect(Collectors.toList()).forEach(System.out::println);//List<String>
2.3 抽取子流和连接流
产生一个流,包含了当前流中前maxSize个元素
Stream<T> limit(long maxSize)
产生一个流,除了前n个元素其他都包含
Stream<T> skip(long n)
产生一个流,把流a和流b链接
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
2.4其他流转换
产生一个流,由剔除数据源流里重复的元素组成
Stream<T> distinct()
产生一个流,按照顺序排列,此方法要求实现Comparable
Stream<T> sorted()
Stream.generate(Math::random).limit(100).sorted().forEach(System.out::println);
产生一个流,按照排序器定义的排序方法排序
Stream<T> sorted(Comparator<? super T> comparator)
Stream.generate(UUID::randomUUID).limit(20).map(e->{
return e.toString().substring(0,new Random().nextInt(11));
}).sorted(Comparator.comparing(String::length)).forEach(System.out::println);
产生一个流,把当前的元素传递给action执行,可用于调试
Stream<T> peek(Consumer<? super T> action)
Stream.generate(Math::random).limit(20).peek(e-> System.out.println("当前元素为"+e)).collect(Collectors.toList());
2.5约简
约简是一种终止流的操作
分别产生流的最大元素和最小元素,使用由给定比较器定义的排序规则,如果这个流为空,则产生空的optional对象。
Optional<T> max(Comparator<? super T> comparator)
Optional<T> min(Comparator<? super T> comparator)
分别产生这个流的第一个和任意一个元素,如果这个流为空,会产生一个空的Optional对象。
Optional<T> findFirst()
Optional<T> findAny()
分别在这个流中任意元素、所有元素、和没有元素匹配给定断言时返回true。
boolean anyMatch(Predicate<? super T> predicate)
boolean allMatch(Predicate<? super T> predicate)
boolean noneMatch(Predicate<? super T> Predicate)
用给定的accumulator函数产生流中元素的累积综合。如果提供了元值,那么第一个被累计的元素就是该元值。如果提供了组合器,那么它可以用来将分别累积的各个部分整合成总和
Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity,BinaryOperator<T> accumulator)
<U> U reduce(U identity,BiFnction<U,? super T,U> accumulator, BinaryOperator<U> combiner>)
Stream.<Integer>of(1,2,3,4,5,6,7,8).reduce(Integer::sum);
Stream.<Integer>of(1,2,3,4,5,6,7,8).reduce(0,Integer::sum);
Stream.<Integer>of(1,2,3,4,5,6,7,8).reduce(0,(x,y)->x+y,(z,q)->z+q);
Optional
产生这个Optional的值,或者在该Optional为空时,产生other.
T orElse(T other)
产生这个Optional的值,或者在该Optional为空时,产生调用other的结果
T orElseGet(Supplier<? extends T> other)
产生这个Optional的值,或者再该Optional为空时,抛出调用exceptionSupplier的结果。
<X extends Throable> T orElseThrow(Supplier<? extends X> exceptionSupplier)
如果该Optional不为空,那么他将值传给consumer。
void ifPresent(Consumer<? super T> consumer)
产生将该Optional的值传递给mapper后的结果,只要这个Optional不为空且结果不为null,否则产生一个空Optional
<U> Optional <U> map(Function<? super T,? extends U> mapper)
2.6 收集结果
产生一个用于获取当前流中的各个元素的迭代器。是一种终结操作
Iterator<T> iterator()
Iterator<Double> i=Stream.generate(Math::random).iterator();
在流的每个元素上调用action。是一种终结操作
void forEach(Consumer<? super T> action)
产生一个对象数组,或者在将引用A[]::new传递给构造器时,返回一个A类型的数组。都是终结操作
Object[] toArray()
<A> A[] toArray(IntFunction<A[]> generator)
使用给定的收集器来收集当前流中的元素。Collectors类有用于多种收集器的工厂方法
<R,A> R collect<Collector<? super T,A,R> collector)
2.6.1收集器的工厂方法
产生一个将元素收集到列表或集中的收集器
static<T> Collector<T,?,List<T>> toList()
static<T> Collector<T,?,Set<T>> toSet()
List<Double> list=Stream.generate(Math::random).collect(Collectors.toList());
Set<Double> set= Stream.generate(Math::random).collect(Collectors.toSet());
产生一个将元素收集到任意集合中的收集器。可以传递一个诸如TreeSet::new的构造器引用
static <T,C extends Collection<T>> Collector<T,?,C> toCollection( Supplier<C> collectionFactory)
List<Double> arrayList=Stream.generate(Math::random).collect(Collectors.toCollection(ArrayList::new));
产生一个连接字符串的收集器。分隔符会置于字符串之间,而第一个字符串之前可以有前缀,最后一个字符串之后可以有后缀,如果没有指定,他们都为空
static Collector<CharSequence,?,String> joining()
static Collector<CharSequence,?,String> joining(CharSequence delimiter)
static Collector<CharSequence,?,String> joining(CharSequence delimiter,CharSequence prefix,CharSequence suffix)
String g=Stream.of("abc","cde","dsf").collect(Collectors.joining("-")).toString();
String g1=Stream.of("abc","cde","dsf").collect(Collectors.joining("-","pre","suf")).toString();
System.out.println(g);//abc-cde-dsf
System.out.println(g1);//preabc-cde-dsfsuf
产生能够生产(Int|Long|Double)SummaryStatistics对象的收集器,通过他可以获得将mapper应用于每个元素后所产生的结果的个数、总和、平均值、最大值和最小值
static <T> Collector<T,?,IntSummaryStatistics> summarizingInt(ToIntFunction<? super T> mapper)
static <T> Collector<T,?,DoubleSummaryStatistics> summarizingInt(ToDoubleFunction<? super T> mapper)
static <T> Collector<T,?,LongSummaryStatistics> summarizingLong(ToLongFunction<? super T> mapper)
IntSummaryStatistics、LongSummaryStatistics、DoubleSummaryStatistics的api
long getCount()//汇总后的元素个数
(int|long|double) getSum()//和
double getAverage()//平均值
(int|long|double) getMax()//最大值
(int|long|double) getMin()//最小值
IntSummaryStatistics s= Stream.iterate(new Integer(1),n->n+new Integer(1)).limit(100).collect(Collectors.summarizingInt(n->n));
System.out.printf("总数%s 平均值%s 最大值%s 最小值%s 和%s"
,s.getCount(),s.getAverage(),s.getMax(),s.getMin(),s.getSum()
产生一个收集器,它会产生一个映射表或并发映射表。keyMapper和valueMapper函数会应用于每个收集到的元素上,从而在所产生的映射表中生成一个键/值项。默认情况下,当两个元素产生相同的键时,会抛出一个IlleagalStateException异常。你可以提供一个mergeFunction来合并具有相同键的元素。默认情况下,其结果是一个HashMap或ConcurrentHashMap。你可以提供一个mapSupplier,它会产生所期望的映射表实例
static<T,K,U> Collector<T,?,Map<K,U>> toMap(Function<? super T,? extends K> keyMapper,Function<? super T,? extends U> valueMapper)
static<T,K,U> Collector<T,?,Map<K,U>> toMap(Function<? super T,? extends K> keyMapper,Function<? super T,? extends U> valueMapper,BinaryOperator<U> mergeFunction)
static<T,K,U,M extends Map<K,U>> Collector<T,?,M> > toMap(Function<? super T,? extends K> keyMapper,Function<? super T,? extends U> valueMapper,BinaryOperator<U> mergeFunction,
Supplier<M> mapSupplier)
static<T,K,U> Collector<T,?,ConcurrentMap<K,U>> toConcurrentMap(Function<? super T,? extends K> keyMapper,Function<? super T,? extends U> valueMapper)
static<T,K,U> Collector<T,?,ConcurrentMap<K,U>> toConcurrentMap(Function<? super T,? extends K> keyMapper,Function<? super T,? extends U> valueMapper,BinaryOperator<U> mergeFunction)
static<T,K,U,M extends ConcurrentMap<K,U>> Collector<T,?,M> > toConcurrentMap(Function<? super T,? extends K> keyMapper,Function<? super T,? extends U> valueMapper,BinaryOperator<U> mergeFunction,
Supplier<M> mapSupplier)
这里看一下用法:
定义一个Person类
static class Person {
public String name;
public Integer age;
public String area;
public String getName() {
return name;
}
public Integer getAge() {
return age;
}
public String getArea() {
return area;
}
Person(String name, String area, Integer age) {
this.name = name;
this.area = area;
this.age = age;
}
@Override
public String toString() {
return "[姓名:" + this.name + " 地区:" + this.area + " 年龄:" + this.age + "]";
}
}
例子:
List<Person> userList = Arrays.asList(new Person("t", "a区", 21), new Person("xt", "a区", 21), new Person("xt2", "b区", 20),
new Person("dt", "d区", 30), new Person("dt2", "c区", 20), new Person("gt", "g区", 21),
new Person("ct", "c区", 30), new Person("qt", "c区", 20), new Person("gt2", "g区", 22)
);
Map<String, Person> map = userList.stream().collect(Collectors.toMap(Person::getName//keyMapper
, Function.identity()//valueMapper
));
Map<String, Integer> map2 = userList.stream().collect(Collectors.toMap(Person::getName, Person::getAge));
Map<String, Person> map3 = userList.stream().collect(Collectors.toMap(Person::getName, Function.identity()
, (e, n) -> {
throw new IllegalStateException();
}//处理相同键的情况,默认抛出这个异常
, TreeMap::new//指定map搜集
));
Map<String, List<Person>> map4 = userList.stream().collect(Collectors.toMap(Person::getArea, l -> Collections.singletonList(l)
, (e, n) -> {
List<Person> list = new ArrayList<>(e);
List<Person> t = (List) n;
list.addAll(t);
return list;
}//处理相同键的情况
, LinkedHashMap::new//指定map搜集
));
map4.forEach((k, v) -> {
System.out.println(v.stream().map(n -> n.toString()).collect(Collectors.joining(",", k + ":\n", "")).toString());
});
这里只输出结果四
注1:这里面的Collections.singletonList(Object o)是生成一个仅包含对象o的不可变list
注2:Function.identity()是返回对象本身
这个一看方法好像很多,其实只要理解各参数的作用并按需求应用即可
2.7群组和分区
产生一个收集器,他会产生一个映射表或并发映射表,其键是将classifier应用于所有收集到的元素上所产生的结果,而值是由具有相同键的元素构成的一个个列表(其实刚刚写的例子,用这个groupingby就可以了,方便简单)
static<T,K> Collector<T,?,Map<K,List<T>> groupingBy(Function<? super T, ?
extends K classifier)
static<T,K> Collector<T,?,ConcurrentMap<K,List<T>> groupingBy(Function<? super T, ?
extends K classifier)
userList.stream().collect(Collectors.groupingBy(Person::getArea)).forEach((k,v)->{
System.out.println(v.stream().map(n -> n.toString()).collect(Collectors.joining(",", k + ":\n", "")).toString());
});
产生一个收集器,他会产生一个映射表,其键是true/false,而值是由满足/不满足断言的元素构成的列表
static<T> Collector<T,?,Map<Boolean,List<T>>> partitioningBy(Predicate<? super T> predicate)
userList.stream().collect(Collectors.partitioningBy(e->e.age>20)).get(true).forEach(System.out::println);
//输出大于20岁的家伙信息
2.8下游收集器
产生一个可以对收集到的元素进行计数的收集器
static<T> Collector<T,?,Long> counting()
产生一个收集器,对将mapper应用到收集到的元素之后产生的值计算总和
static<T> Collector<T,?,Integer> summingInt(ToIntFunction<? super T> mapper)
static<T> Collector<T,?,Long> summingLong(ToLongFunction<? super T> mapper)
static<T> Collector<T,?,Double> summingDouble(ToDoubleFunction<? super T> mapper)
产生一个收集器,使用comparator指定的排序方法,计算收集到的元素中的最大值和最小值
static <T> Collector<T,?,Optional<T>> maxBy(Comparator<? super T> comparator)
static <T> Collector<T,?,Optional<T>> minBy(Comparator<? super T> comparator)
产生一个收集器,它会产生一个映射表,其键是将mapper应用到收集到的数据上而产生的,其值是使用downstream收集器收集到的具有相同键的元素
static<T,U,A,R> Collector<T,?,R> mapping(Function<? super T,? extends U> mapper,Collector<? super U,A,R> downstream)
对应例子如下,相对难理解的是mapping。
userList.stream().collect(Collectors.groupingBy(Person::getArea, Collectors.counting())).forEach((k, v) -> {
System.out.println(k + " 数量:" + v);
});
userList.stream().collect(Collectors.groupingBy(Person::getArea, Collectors.summingInt(Person::getAge))).forEach((k, v) -> {
System.out.println(k + " 的总年龄数:" + v);
});
userList.stream().collect(Collectors.groupingBy(Person::getArea, Collectors.mapping(Function.identity(), Collectors.maxBy(Comparator.comparing(Person::getAge))))).forEach((k, v) -> {
System.out.println(k + " 最大年龄数:" + v);
});
2.8.1自定义下游收集器
上面的收集器都是基本类型,对于BigDecimal类型的,也想分组,然后对分组元素的某个属性进行聚合统计,这时候就可以用到自定义的收集器
首先看下面这个方法
泛型:其中T代表收集器的入参,A代表处理元素的容器,R代表返回参数
参数:supplier代表提供处理元素容器的函数,accumulator代表容器对入参的操作,combiner代表容器和容器的操作,finisher代表返回参数处理
首先看简单的累加聚合,先定义一个转换的接口
@FunctionalInterface
public interface ToBigDecimalFunction<T> {
BigDecimal applyAsBigDecimal(T value);
}
然后定义对应的下游收集器
public static <T>
Collector<T, ?, BigDecimal> sumBigDecimal(ToBigDecimalFunction<? super T> mapper) {
return Collector.of(() -> new BigDecimal[]{BigDecimal.ZERO},
(b, t) -> b[0] = b[0].add(mapper.applyAsBigDecimal(t))
, (sum1, sum2) -> {
sum1[0] = sum1[0].add(sum2[0]);
return sum1;
},
bigDecimals -> bigDecimals[0]
, Collector.Characteristics.UNORDERED
);
}
例子
这里根据头id进行分组,然后统计每项总数,可以看到结果和普通的做法是一样的
long[] headId = {1, 2, 3};
List<OrderLine> test = new LinkedList<>();
for (int i = 0; i < 10; i++) {
OrderLine orderLine = new OrderLine().setOrderHeadId(headId[i % headId.length])
.setPrice(BigDecimal.valueOf(i));
test.add(orderLine);
}
Map<Long, List<OrderLine>> map = test.stream().collect(Collectors.groupingBy(OrderLine::getOrderHeadId));
map.forEach((k, v) ->
{
BigDecimal reduce = v.stream().map(OrderLine::getPrice).reduce(BigDecimal.ZERO, BigDecimal::add);
System.out.println(String.format("%s,%s", k, reduce));
});
Map<Long, BigDecimal> collect = test.stream().collect(Collectors.groupingBy(OrderLine::getOrderHeadId, sumBigDecimal(OrderLine::getPrice)));
collect.forEach((k, v) -> System.out.println(String.format("%s,%s", k, v)));
DoubleSummaryStatistics实现了DoubleConsumer这个方法,然后里面有对应的统计字段,而sumCompensation,simpleSum都是为了处理精度丢失的情况,这相当于上面所说的容器
对应的,自定义一个BigDecimal容器
public class BigDecimalSummaryStatistics implements Consumer<BigDecimal> {
private long count;
private BigDecimal sum = BigDecimal.ZERO;
private BigDecimal min = null;
private BigDecimal max = null;
public BigDecimalSummaryStatistics() {
}
@Override
public void accept(BigDecimal value) {
if(Objects.isNull(value)){
throw new NullPointerException("can not handle with null value");
}
++count;
min = min(min, value);
max = max(max, value);
sum = sum.add(value);
}
public BigDecimalSummaryStatistics combine(BigDecimalSummaryStatistics other) {
count += other.count;
sum = sum.add(other.sum);
min = min(min, other.min);
max = max(max, other.max);
return this;
}
public final long getCount() {
return count;
}
public final BigDecimal getSum() {
return sum;
}
public final BigDecimal getMin() {
return min;
}
public final BigDecimal getMax() {
return max;
}
public final BigDecimal getAverage() {
return getCount() > 0 ? getSum().divide(BigDecimal.valueOf(getCount()), 8, RoundingMode.HALF_UP) : BigDecimal.ZERO;
}
@Override
public String toString() {
return String.format(
"%s{count=%d, sum=%f, min=%f, average=%f, max=%f}",
this.getClass().getSimpleName(),
getCount(),
getSum(),
getMin(),
getAverage(),
getMax());
}
private BigDecimal min(BigDecimal one, BigDecimal other) {
return Optional.ofNullable(ifHasNull(one, other))
.orElseGet(()->one.compareTo(other) > 0 ? other : one);
}
private BigDecimal max(BigDecimal one, BigDecimal other) {
return Optional.ofNullable(ifHasNull(one, other))
.orElseGet(()->one.compareTo(other) > 0 ? one : other);
}
private BigDecimal ifHasNull(BigDecimal one, BigDecimal other) {
if (Objects.isNull(one) && Objects.isNull(other)) {
throw new NullPointerException("can not handle with both null value");
}
if (Objects.isNull(one)) {
return other;
}
if (Objects.isNull(other)) {
return one;
}
return null;
}
}
及对应的收集器
public static <T>
Collector<T, ?, BigDecimalSummaryStatistics> summarizingBigDecimal(ToBigDecimalFunction<? super T> mapper) {
return Collector.of(BigDecimalSummaryStatistics::new,
(s, t) -> s.accept(mapper.applyAsBigDecimal(t))
, (s1, s2) -> s1.combine(s2),
s -> s, Collector.Characteristics.IDENTITY_FINISH);
}
2.9 基本类型流
产生一个由给定范围内的整数构成的IntStream
static IntStream range(int startInclusive,int endExclusive)
static IntStream rangeClosed(int startInclusive,int endInclusive)
产生一个由给定元素构成的IntStream
static IntStream of(int...values)
产生一个由当前流中的元素构成的数组。
int[] toArray()
产生当前流中的总和、平均值、最大值、最小值、或者拥有这四种结果的对象
int sum()
OptionalDouble average()
OptionalInt min()
IntSummaryStatistics summaryStatistics()
产生用于当前流中元素的包装器对象
Stream<Integer> boxed()
其他的Long、Double跟这个差不多,就不赘述了
产生由当前字符串的所有Unicode码点构成的流
IntStream codePoints()
产生随机流。如果提供了streamSize,这个流就是具有给定数量元素的有限流。当提供边界时,其元素将位于randomNumberOrigin(包含)和randomNumberBound(不包含)的区间内
IntStream ints()
IntStream ints(int randomNumberOrigin,int randomNumberBound)
IntStream ints(long streamSize,int randomNumberOrigin,int randomNumberBound)
//LongStream、DoubleStream也有相应方法,这里不赘述
// Stream<Integer> s= new Random().ints(1000,0,1000).boxed();
2.10并行流
并行流,相当于多线程执行同一个任务,但前提是该任务可以划分出子任务,内部使用fork-joinpool来操作流的各个部分
使用时应当满足如下条件
1.数据应该存在内存中,必须等到数据到达时非常低效的。
2.流应该可以被高效地分成若干个自部分。由数组或平衡二叉树支持的流都可以工作得很好,但是Stream.iterate返回的结果不行
3.流操作的工作量应该有较大的规模。如果总工作负载不是很大,那么搭建并行计算时所付出的代价就没有意义
4.流操作不应该被阻塞
即不要将所有的流都转换为并行流,只有对已经在内存里面的数据执行大量操作时,才应该使用并行流
产生一个与当前流中元素相同的并行流
S parallel()
产生一个与当前流中元素相同的无序流
S unordered()
用当前集合中的元素产生一个并行流
Stream<E> parallelStream()
IntStream.rangeClosed(0,1000000).parallel().unordered().filter(n->{
for(int i=0;i<100;i++){}
return n%2==0;
}).toArray();