前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一文带你玩转Java8Stream,从此集合操作SoEasy

一文带你玩转Java8Stream,从此集合操作SoEasy

作者头像
九转成圣
发布2024-04-10 18:34:33
960
发布2024-04-10 18:34:33
举报
文章被收录于专栏:csdncsdn

一文带你玩转Java8Stream,从此集合操作SoEasy

有需要互关的小伙伴,关注一下,有关必回关,争取今年认证早日拿到博客专家

标签:java基础

不同类型的流

代码语言:javascript
复制
public interface Stream<T> extends BaseStream<T, Stream<T>>
代码语言:javascript
复制
public interface IntStream extends BaseStream<Integer, IntStream>
代码语言:javascript
复制
public interface LongStream extends BaseStream<Long, LongStream>
代码语言:javascript
复制
public interface DoubleStream extends BaseStream<Double, DoubleStream>

上面这些原始类型流的工作方式与常规对象流基本是一样的,但还是略微存在一些区别:

  • 原始类型流使用其独有的函数式接口,例如IntFunction代替FunctionIntPredicate代替Predicate
  • 原始类型流支持额外的终端聚合操作,sum()以及average(),如下所示:
代码语言:javascript
复制
Arrays.stream(new int[] {1, 2, 3}).average().ifPresent(System.out::println);

但是,偶尔我们也有这种需求,需要将常规对象流转换为原始类型流,这个时候,中间操作 mapToInt()mapToLong() 以及mapToDouble就派上用场了

代码语言:javascript
复制
List<Person> persons = getPersons();
for (Person person : persons) {
    System.out.println("person = " + person);
}
persons.stream().mapToInt(p->p.age).max().ifPresent(System.out::println);
代码语言:javascript
复制
person = {name='Max', age=18}
person = {name='Peter', age=23}
person = {name='Pamela', age=23}
person = {name='David', age=12}
23

如果说,您需要将原始类型流装换成对象流,您可以使用 mapToObj()来达到目的:

代码语言:javascript
复制
IntStream.range(1,4).mapToObj(String::valueOf).forEach(System.out::println);

中间操作与终端操作

中间操作返回Stream,终端操作返回void或者非stream

没有终端操作,中间操作是不会生效的.

代码语言:javascript
复制
/**
 * 什么都不会输出,why
 * 因为只有存在终端操作时中间操作才会被执行
 */
Stream.of("d2", "a2", "b1", "b3", "c").filter(s -> {
			System.out.println("filter: " + s);
			return true;
		});

Stream 流的处理顺序

代码语言:javascript
复制
/**
 * sorted 水平调用(需要保存中间状态) 在水平调用前的所有垂直调用将变为水平调用
 * filter,map,垂直执行(提高性能)
 * 有状态的操作 distinct skip limit sorted reduce
 */
Stream.of("d2", "a2", "b1", "b3", "c")
		.map(s -> {
			System.out.println("map1: " + s);
			return s.toUpperCase(); // 转大写
		})
		.sorted((s1, s2) -> {
			System.out.printf("sort: %s; %s\n", s1, s2);
			return s1.compareTo(s2); // 排序
		})
		.map(s -> {
			System.out.println("map2: " + s);
			return s.toLowerCase();
		})
		.filter(s -> {
			System.out.println("filter: " + s);
			return s.startsWith("a"); // 过滤出以 a 为前缀的元素
		})
		.forEach(s -> System.out.println("forEach: " + s)); // for 循环输出
代码语言:javascript
复制
Stream.of("d2", "a2", "b1", "b3", "c").filter(s -> {
            System.out.println("filter: " + s);
            return true;
        });

执行此代码段时,您可能会认为,将依次打印 “d2”, “a2”, “b1”, “b3”, “c” 元素。然而当你实际去执行的时候,它不会打印任何内容。

为什么呢?

原因是:当且仅当存在终端操作时,中间操作操作才会被执行。

数据流复用问题

Java8 Stream 流是不能被复用的,一旦你调用任何终端操作,流就会关闭:

代码语言:javascript
复制
Stream<String> stream = Stream.of("d2", "a2", "b1", "b3", "c");
// 终端操作会关闭流
boolean b = stream.anyMatch(item -> true);
Optional<String> any = stream.findAny();
代码语言:javascript
复制
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
	at java.util.stream.ReferencePipeline.findAny(ReferencePipeline.java:469)
	at StreamDemo.extracted7(StreamDemo.java:95)
	at StreamDemo.main(StreamDemo.java:20)

为了克服这个限制,我们必须为我们想要执行的每个终端操作创建一个新的流链,例如,我们可以通过 Supplier 来包装一下流,通过 get() 方法来构建一个新的 Stream 流,如下所示:

代码语言:javascript
复制
Supplier<Stream<String>> supplier = () -> Stream.of("d2", "a2", "b1", "b3", "c");
// 终端操作会关闭流 流的复用
boolean b = supplier.get().anyMatch(item -> true);
Optional<String> any = supplier.get().findAny();

通过构造一个新的流,来避开流不能被复用的限制, 这也是取巧的一种方式。

Stream常用api

中间操作

distinct
skip与limit
代码语言:javascript
复制
List<String> list = getList();
int pageSizse = 10;
int total = list.size() / pageSizse;
if (list.size() % pageSizse != 0) {
    total += 1;
}
for (int i = 0; i < total; i++) {
    System.out.println(String.format("第%d页", i + 1));
    list.stream().skip(i * pageSizse).limit(pageSizse).forEach(item -> System.out.println("item = " + item));
}
filter
代码语言:javascript
复制
public static void main(String[] args) {
    List<String> xxx = new ArrayList<>();
    xxx.add("zss");
    List<String> yyy = xxx.stream().filter(item -> item.equals("yyy")).collect(Collectors.toList());
    System.out.println(yyy.size());
}

filter没有元素返回空集合,不会返回null

peek

peek 窥视 偷看

代码语言:javascript
复制
List<Person> persons = getPersons();
for (Person person : persons) {
    System.out.println("person = " + person);
}
// peek 改变元素的内部状态(如果没有终端操作,这里是不会触发的) 可以这么用,但不建议
persons.stream().peek(p->p.age = p.age*2).forEach(System.out::println);
代码语言:javascript
复制
person = {name='Max', age=18}
person = {name='Peter', age=23}
person = {name='Pamela', age=23}
person = {name='David', age=11}
{name='Max', age=36}
{name='Peter', age=46}
{name='Pamela', age=46}
{name='David', age=22

peek() vs forEach()

forEach() 是一个最终操作。除此之外,peek()forEach() 再无其他不同。

peek() 的典型用法:协助调试

代码语言:javascript
复制
List<Person> persons = getPersons();
for (Person person : persons) {
    System.out.println("person = " + person);
}
persons.stream().filter(p -> p.age >= 18).peek(System.out::println).forEach(p->{
    p.age=p.age*2;
    System.out.println("p = " + p);
});

小结

  • 如果想对流经的每个元素应用一个函数,从而改变某些状态,那么请用 forEach()
  • 如果想打印流经的每个元素的状态(日志或 debug),这时应该用 peek()
map
mapToInt
mapToLong
mapToDouble
sorted
FlatMap
代码语言:javascript
复制
@Data
class Foo {
    private String name;
    private List<Bar> bars = new ArrayList<>();

    public Foo(String name) {
        this.name = name;
    }
}

@Data
class Bar {
    private String name;

    public Bar(String name) {
        this.name = name;
    }
}
代码语言:javascript
复制
List<Foo> foos = new ArrayList<>();
for (int i = 1; i < 4; i++) {
    Foo foo = new Foo("Foo" + i);
    List<Bar> bars = new ArrayList<>();
    for (int j = 1; j < 4; j++) {
        bars.add(new Bar("Bar"+j+"<-"+foo.getName()));
    }
    foo.setBars(bars);
    foos.add(foo);
}
for (Foo foo : foos) {
    System.out.println("foo = " + foo);
}
foos.stream().flatMap(foo -> foo.getBars().stream()).forEach(bar-> System.out.println(bar.getName()));
代码语言:javascript
复制
foo = Foo(name=Foo1, bars=[Bar(name=Bar1<-Foo1), Bar(name=Bar2<-Foo1), Bar(name=Bar3<-Foo1)])
foo = Foo(name=Foo2, bars=[Bar(name=Bar1<-Foo2), Bar(name=Bar2<-Foo2), Bar(name=Bar3<-Foo2)])
foo = Foo(name=Foo3, bars=[Bar(name=Bar1<-Foo3), Bar(name=Bar2<-Foo3), Bar(name=Bar3<-Foo3)])
Bar1<-Foo1
Bar2<-Foo1
Bar3<-Foo1
Bar1<-Foo2
Bar2<-Foo2
Bar3<-Foo2
Bar1<-Foo3
Bar2<-Foo3
Bar3<-Foo3
Optional的flatMap
代码语言:javascript
复制
Outer outer = new Outer();
Nested nested = new Nested();
outer.nested = nested;
Inner inner = new Inner();
nested.inner = inner;
inner.foo = "foo...";

if (outer != null && outer.nested != null && outer.nested.inner != null) {
    System.out.println(outer.nested.inner.foo);
}

取代

代码语言:javascript
复制
Outer outer = new Outer();
Nested nested = new Nested();
outer.nested = nested;
Inner inner = new Inner();
nested.inner = inner;
inner.foo = "foo...";

Optional.of(outer)
        .flatMap(o -> Optional.ofNullable(o.nested))
        .flatMap(n -> Optional.ofNullable(n.inner))
        .flatMap(i -> Optional.ofNullable(i.foo))
        .ifPresent(System.out::println);
代码语言:javascript
复制
List<EmergencyIot> compCode1 = detectionIots.stream().flatMap(item -> Optional.ofNullable(item.getEmergencyIots()).orElse(Collections.emptyList()).stream()).peek(item -> item.setCompCode("compCode")).collect(Collectors.toList());
代码语言:javascript
复制
List<EmergencyIot> compCode1 = detectionIots.stream().flatMap(item -> Optional.ofNullable(item.getEmergencyIots()).orElse(Collections.emptyList()).stream()).peek(item -> item.setCompCode("compCode")).collect(Collectors.toList());

终端操作

forEach与forEachOrdered
代码语言:javascript
复制
List<String> strs = Arrays.asList("a", "b", "c");
strs.stream().map(String::toUpperCase).forEachOrdered(System.out::print);
System.out.println();
strs.stream().map(String::toUpperCase).forEach(System.out::print);
System.out.println();
strs.parallelStream().forEachOrdered(System.out::print);
System.out.println();
strs.parallelStream().forEach(System.out::print);
代码语言:javascript
复制
ABC
ABC
abc
bac

猜测:forEach与forEachOrdered在串行流下没区别

并行流parallelStream下forEachOrdered严格按照顺序(效率低),forEach与线程执行速度有关(效率高)

count
findAny、findFirst 、max与min

findAny(找到并返回任何一个(都行))、findFirst 、max与min返回值都是Optional

代码语言:javascript
复制
List<Person> persons = getPersons();
for (Person person : persons) {
    System.out.println("person = " + person);
}
// 在串行时findAny返回的是第一个元素与findFirst效果一样,在并行时返回的是处理最快的线程的结果,效率上比findFirst快
persons.stream().findAny().ifPresent(System.out::println);
persons.stream().findFirst().ifPresent(System.out::println);
persons.stream().max(Comparator.comparingInt(p -> p.age)).ifPresent(System.out::println);
persons.stream().min(Comparator.comparingInt(p -> p.age)).ifPresent(System.out::println);
代码语言:javascript
复制
person = {name='Max', age=18}
person = {name='Peter', age=23}
person = {name='Pamela', age=23}
person = {name='David', age=12}
{name='Max', age=18}
{name='Max', age=18}
{name='Peter', age=23}
{name='David', age=12}
anyMatch、allMatch与noneMatch

anyMatch、allMatch与noneMatch的返回值都是boolean

代码语言:javascript
复制
List<Person> persons = getPersons();
for (Person person : persons) {
    System.out.println("person = " + person);
}
// anyMatch 只要匹配到任意一个就停止并返回true匹配到就停止
System.out.println(persons.stream().anyMatch(item -> item.age == 18));
// 都是则返回true
System.out.println(persons.stream().allMatch(item -> item.age == 18));
// 都不是则返回true
System.out.println(persons.stream().noneMatch(item -> item.age == 18));
代码语言:javascript
复制
person = {name='Peter', age=23}
person = {name='Pamela', age=23}
person = {name='David', age=12}
true
false
false
toArray
代码语言:javascript
复制
Object[] listToArrayobjects = persons.toArray();
System.out.println("listToArrayobjects = " + Arrays.toString(listToArrayobjects));
Person[] listToArrayPersons = persons.toArray(new Person[0]);
System.out.println("listToArrayPersons = " + Arrays.toString(listToArrayPersons));
Object[] streamToArrayObjects = persons.stream().filter(p -> p.age > 18).toArray();
System.out.println("streamToArrayObjects = " + Arrays.toString(streamToArrayObjects));
Person[] streamToArrayPersons = persons.stream().filter(p -> p.age > 18).toArray(Person[]::new);
System.out.println("streamToArrayPersons = " + Arrays.toString(streamToArrayPersons));
代码语言:javascript
复制
person = {name='Max', age=18}
person = {name='Peter', age=23}
person = {name='Pamela', age=23}
person = {name='David', age=12}
listToArrayobjects = [{name='Max', age=18}, {name='Peter', age=23}, {name='Pamela', age=23}, {name='David', age=12}]
listToArrayPersons = [{name='Max', age=18}, {name='Peter', age=23}, {name='Pamela', age=23}, {name='David', age=12}]
streamToArrayObjects = [{name='Peter', age=23}, {name='Pamela', age=23}]
streamToArrayPersons = [{name='Peter', age=23}, {name='Pamela', age=23}]

注意:使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一致、长度为 0 的空数组(阿里开发手册)

直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它类型数组将出现 ClassCastException 错误。

说明:使用 toArray 带参方法,数组空间大小的 length: 1) 等于 0,动态创建与 size 相同的数组,性能最好。 2) 大于 0 但小于 size,重新创建大小等于 size 的数组,增加 GC 负担。 3) 等于 size,在高并发情况下,数组创建完成之后,size 正在变大的情况下,负面影响与上相同。 4) 大于 size,空间浪费,且在 size 处插入 null 值,存在 NPE 隐患。

reduce

规约操作可以将流的所有元素组规约一个结果。Java 8 支持三种不同的reduce方法。第一种将流中的元素规约成流中的一个元素。

找出年龄最大的人

代码语言:javascript
复制
List<Person> persons = getPersons();
for (Person person : persons) {
    System.out.println("person = " + person);
}
persons.stream().reduce((p1,p2)->p1.age>p2.age?p1:p2).ifPresent(System.out::println);
代码语言:javascript
复制
person = {name='Max', age=18}
person = {name='Peter', age=23}
person = {name='Pamela', age=23}
person = {name='David', age=12}
{name='Pamela', age=23}

第二种reduce方法接受标识值和BinaryOperator累加器。此方法可用于构造一个新的 Person,其中包含来自流中所有其他人的聚合名称和年龄:

代码语言:javascript
复制
List<Person> persons = getPersons();
for (Person person : persons) {
    System.out.println("person = " + person);
}
Person result = persons.stream().reduce(new Person("", 0), (p1, p2) -> {
                    p1.age += p2.age;
                    p1.name += p2.name;
                    return p1;
                });
System.out.format("name=%s; age=%s", result.name, result.age);
代码语言:javascript
复制
person = {name='Max', age=18}
person = {name='Peter', age=23}
person = {name='Pamela', age=23}
person = {name='David', age=12}
name=MaxPeterPamelaDavid; age=76

第三种reduce方法接受三个参数:标识值,BiFunction累加器和类型的组合器函数BinaryOperator。由于初始值的类型不一定为Person,我们可以使用这个归约函数来计算所有人的年龄总和:

代码语言:javascript
复制
List<Person> persons = getPersons();
for (Person person : persons) {
    System.out.println("person = " + person);
}
Integer ageSum = persons.stream().reduce(0, (sum, p) -> sum += p.age, Integer::sum);
System.out.println(ageSum);
代码语言:javascript
复制
person = {name='Max', age=18}
person = {name='Peter', age=23}
person = {name='Pamela', age=23}
person = {name='David', age=12}
76

结果为76,但是内部究竟发生了什么呢?让我们再打印一些调试日志:

代码语言:javascript
复制
Integer ageSum = persons.stream().reduce(0,
		(sum, p) -> {
			System.out.format("accumulator: sum=%s; person=%s\n", sum, p);
			return sum += p.age;
		},
		(sum1, sum2) -> {
			System.out.format("combiner: sum1=%s; sum2=%s\n", sum1, sum2);
			return sum1 + sum2;
		});
代码语言:javascript
复制
person = {name='Max', age=18}
person = {name='Peter', age=23}
person = {name='Pamela', age=23}
person = {name='David', age=12}
accumulator: sum=0; person={name='Max', age=18}
accumulator: sum=18; person={name='Peter', age=23}
accumulator: sum=41; person={name='Pamela', age=23}
accumulator: sum=64; person={name='David', age=12}

你可以看到,累加器函数完成了所有工作。它首先使用初始值0和第一个人年龄相加。接下来的三步中sum会持续增加,直到76。

等等?好像哪里不太对!组合器从来都没有调用过啊?

我们以并行流的方式运行上面的代码,看看日志输出:

代码语言:javascript
复制
Integer ageSum = persons.parallelStream().reduce(0,
		(sum, p) -> {
			System.out.format("accumulator: sum=%s; person=%s\n", sum, p);
			return sum += p.age;
		},
		(sum1, sum2) -> {
			System.out.format("combiner: sum1=%s; sum2=%s\n", sum1, sum2);
			return sum1 + sum2;
		});
代码语言:javascript
复制
person = {name='Max', age=18}
person = {name='Peter', age=23}
person = {name='Pamela', age=23}
person = {name='David', age=12}
accumulator: sum=0; person={name='Max', age=18}
accumulator: sum=0; person={name='Peter', age=23}
combiner: sum1=18; sum2=23
accumulator: sum=0; person={name='Pamela', age=23}
accumulator: sum=0; person={name='David', age=12}
combiner: sum1=23; sum2=12
combiner: sum1=41; sum2=35

并行流的执行方式完全不同。这里组合器被调用了。实际上,由于累加器被并行调用,组合器需要被用于计算部分累加值的总和。

collect

常见收集器

数据源

代码语言:javascript
复制
private static List<Person> getPersons() {
    List<Person> persons = Arrays.asList(new Person("Max", 18),
                                         new Person("Peter", 23),
                                         new Person("Pamela", 23),
                                         new Person("David", 12));
    return persons;
}

Collectors.toList()

Collectors.toSet()

Collectors.toMap

如果有两个相同的key会报错java.lang.IllegalStateException: Duplicate key Peter

源码

代码语言:javascript
复制
public static <T, K, U > Collector < T, ?,Map<K, U>>toMap(
    Function < ? super T, ? extends K > keyMapper,
    Function < ? super T, ? extends U > valueMapper){
    return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}

测试代码

代码语言:javascript
复制
// [{name='Max', age=18}, {name='Peter', age=23}, {name='Pamela', age=23}, {name='David', age=12}]
List<Person> persons = getPersons();
// 出现两个相同的key,报错
Map<Integer, String> map = persons.stream().collect(Collectors.toMap(p -> p.age, p -> p.name)); 

出现两个相同的key,将value合并

源码

代码语言:javascript
复制
public 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){
    return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
}

测试代码

代码语言:javascript
复制
BinaryOperator<String> mergeFunction = (value1, value2) -> {
    if (value1.equals(value2)) {
        return value1;
    }
    return value1 + ";" + value2;
};
Map<Integer, String> map = persons.stream().collect(Collectors.toMap(p -> p.age, p -> p.name, mergeFunction));
System.out.println(map);
// {18=Max, 23=Peter;Pamela, 12=David}

Collectors.groupingBy

代码语言:javascript
复制
List<Person> persons = getPersons();
for (Person person : persons) {
    System.out.println("person = " + person);
}
// 分组
Map<Integer, List<Person>> groupByPersons = persons.stream().collect(Collectors.groupingBy(p -> p.age));
System.out.println("groupingByPersons = " + groupByPersons);
代码语言:javascript
复制
person = {name='Max', age=18}
person = {name='Peter', age=23}
person = {name='Pamela', age=23}
person = {name='David', age=11}
groupingByPersons = {18=[{name='Max', age=18}], 23=[{name='Peter', age=23}, {name='Pamela', age=23}], 11=[{name='David', age=11}]}

Collectors.partitioningBy

代码语言:javascript
复制
List<Person> persons = getPersons();
for (Person person : persons) {
    System.out.println("person = " + person);
}
// 分区
Map<Boolean, List<Person>> partitioningByPersons = persons.stream().collect(Collectors.partitioningBy(p -> p.age >= 18));
System.out.println("partitioningByPersons = " + partitioningByPersons);
代码语言:javascript
复制
person = {name='Max', age=18}
person = {name='Peter', age=23}
person = {name='Pamela', age=23}
person = {name='David', age=11}
partitioningByPersons = {false=[{name='David', age=11}], true=[{name='Max', age=18}, {name='Peter', age=23}, {name='Pamela', age=23}]}

Collectors.summarizingInt

代码语言:javascript
复制
List<Person> persons = getPersons();
for (Person person : persons) {
    System.out.println("person = " + person);
}
// 综合统计age属性 count max min sum average
IntSummaryStatistics statistics = persons.stream().collect(Collectors.summarizingInt(p -> p.age));
System.out.println("statistics = " + statistics);
代码语言:javascript
复制
person = {name='Max', age=18}
person = {name='Peter', age=23}
person = {name='Pamela', age=23}
person = {name='David', age=11}
statistics = IntSummaryStatistics{count=4, sum=75, min=11, average=18.750000, max=23}
count = 4
max = 23
min = 11
sum = 75
average = 18.75

Collectors.joining

如果能用

代码语言:javascript
复制
/**
 * Can be replaced with 'String.join'
 * 如果能用String.join则优先使用String.join 例如字符串数组或字符串集合
 * 如果不能用String.join再用Collectors.joining(),例如对象的某个属性拼接(需要先map成字符串,然后收集)
 */
String collect = String.join("", list);
// Can be replaced with 'String.join'
collect = list.stream().collect(Collectors.joining());
System.out.println("collect = " + collect);
// Can be replaced with 'String.join'
collect = list.stream().collect(Collectors.joining("and"));
System.out.println("collect = " + collect);
// 底层实现StringJoiner,StringJoiner的底层实现StringBuilder
System.out.println("collect = " + collect);
String collect3 = list.stream().collect(Collectors.joining("and", "start", "end"));
System.out.println("collect3 = " + collect3);

其他

‘collect(summingInt())’ can be replaced with ‘mapToInt().sum()’

对age属性求和或者平均数,可以先用mapToInt成IntStream,然后调用IntStream的sum,average方法而非收集器的方法

最大值Collectors.maxBy 最小值Collectors.minBy 规约Collectors.reducing 均推荐用stream的max,min,reduce

自定义收集器Collector

一个Collector是由四部分组成的:

  • Supplier supplier(): 创建新的结果容器
  • BiConsumer<A, T> accumulator(): 将元素添加到结果容器
  • BinaryOperator combiner(): 将两个结果容器合并为一个结果容器
  • Function<A, R> finisher(): 对结果容器作相应的变换

Collector自定义起来,也不是特别的麻烦,不过要明确以下几点:

  1. 参数类型: 待收集元素的类型:T 累加器的类型:A 最终结果的类型:R
  2. 累加器的逻辑
  3. 最终结果的转换
  4. Collector特征的选择

现在有个简单的需求,求一段数字的和,如果是奇数,直接相加;如果是偶数,乘以2后在相加。这样的场景下,Collector类库中的收集器不能满足我们的需求,我们只能够自己定义了。

代码语言:javascript
复制
class IntegerSum {
    int sum;

    public IntegerSum doSum(Integer item) {
        if (item % 2 == 0) {
            this.sum += item * 2;
        } else {
            this.sum += item;
        }
        return this;

    }

    public IntegerSum doCombine(IntegerSum it) {
        this.sum += it.sum;
        return this;
    }

    public Integer toValue() {
        return this.sum;
    }

}
代码语言:javascript
复制
public static void main(String[] args) {
    // 1.创建新的结果容器
    Supplier<IntegerSum> supplier = IntegerSum::new;
    // 2.将元素添加到结果容器
    BiConsumer<IntegerSum, Integer> accumulator = IntegerSum::doSum;
    // 3.合并多个结果容器
    BinaryOperator<IntegerSum> combiner = IntegerSum::doCombine;
    // 4.最终结果的转换
    Function<IntegerSum, Integer> finisher = IntegerSum::toValue;
    /**
     * 泛型说明
     * T 待收集元素的类型
     * A 累加器的类型
     * R 最终结果的类型
     */
    Collector<Integer, IntegerSum, Integer> collector = Collector.of(supplier, accumulator, combiner, finisher);
    Integer result = Stream.of(1, 3, 5, 7).collect(collector);
    System.out.println("result = " + result);
}
代码语言:javascript
复制
/**
 * 泛型说明
 * T 待收集元素的类型
 * A 累加器的类型
 * R 最终结果的类型
 */
Collector<Integer, IntegerSum, Integer> collector = new Collector<Integer, IntegerSum, Integer>() {
    @Override
    public Supplier<IntegerSum> supplier() {
        return IntegerSum::new;
    }

    @Override
    public BiConsumer<IntegerSum, Integer> accumulator() {
        return IntegerSum::doSum;
    }

    @Override
    public BinaryOperator<IntegerSum> combiner() {
        return IntegerSum::doCombine;
    }

    @Override
    public Function<IntegerSum, Integer> finisher() {
        return IntegerSum::toValue;
    }

    @Override
    public Set<Characteristics> characteristics() {
        return Collections.emptySet();
    }
};
Integer result = Stream.of(1, 3, 5, 7).collect(collector);
System.out.println("result = " + result);

描述收集器的特征: enum Characteristics

并行流

流是可以并行执行的,当流中存在大量元素时,可以显著提升性能。并行流底层使用的ForkJoinPool, 它由ForkJoinPool.commonPool()方法提供。底层线程池的大小取决于 CPU 可用核心数:

代码语言:javascript
复制
ForkJoinPool commonPool = ForkJoinPool.commonPool();
System.out.println(commonPool.getParallelism());    // 11

在我的机器上,公共池初始化默认值为 3。你也可以通过设置以下JVM参数可以减小或增加此值:

代码语言:javascript
复制
-Djava.util.concurrent.ForkJoinPool.common.parallelism=5

集合支持parallelStream()方法来创建元素的并行流。或者你可以在已存在的数据流上调用中间方法parallel(),将串行流转换为并行流,这也是可以的。

代码语言:javascript
复制
Arrays.asList("a1", "a2", "b1", "c2", "c1").parallelStream()
		.filter(s -> {
			System.out.format("filter: %s [%s]\n", s, Thread.currentThread().getName());
			return true;
		})
		.map(s -> {
			System.out.format("map: %s [%s]\n", s, Thread.currentThread().getName());
			return s.toUpperCase();
		})
		.sorted((s1, s2) -> {
			System.out.format("sort: %s <> %s [%s]\n", s1, s2, Thread.currentThread().getName());
			return s1.compareTo(s2);
		})
		.forEach(s -> System.out.format("forEach: %s [%s]\n", s, Thread.currentThread().getName()));
代码语言:javascript
复制
filter: b1 [main]
filter: c2 [ForkJoinPool.commonPool-worker-4]
map: c2 [ForkJoinPool.commonPool-worker-4]
filter: c1 [ForkJoinPool.commonPool-worker-11]
map: c1 [ForkJoinPool.commonPool-worker-11]
filter: a2 [ForkJoinPool.commonPool-worker-9]
map: a2 [ForkJoinPool.commonPool-worker-9]
filter: a1 [ForkJoinPool.commonPool-worker-2]
map: a1 [ForkJoinPool.commonPool-worker-2]
map: b1 [main]
sort: A2 <> A1 [main]
sort: B1 <> A2 [main]
sort: C2 <> B1 [main]
sort: C1 <> C2 [main]
sort: C1 <> B1 [main]
sort: C1 <> C2 [main]
forEach: B1 [main]
forEach: C1 [ForkJoinPool.commonPool-worker-9]
forEach: A1 [ForkJoinPool.commonPool-worker-11]
forEach: C2 [ForkJoinPool.commonPool-worker-4]
forEach: A2 [ForkJoinPool.commonPool-worker-2]

貌似sort只在主线程上串行执行。但是实际上,并行流中的sort在底层使用了Java8中新的方法Arrays.parallelSort()。如 javadoc官方文档解释的,这个方法会按照数据长度来决定以串行方式,或者以并行的方式来执行。

如果指定数据的长度小于最小数值,它则使用相应的Arrays.sort方法来进行排序。

总之,你需要记住的是,并行流对含有大量元素的数据流提升性能极大。但是你也需要记住并行流的一些操作,例如reducecollect操作,需要额外的计算(如组合操作),这在串行执行时是并不需要。

此外,我们也了解了,所有并行流操作都共享相同的 JVM 相关的公共ForkJoinPool。所以你可能需要避免写出一些又慢又卡的流式操作,这很有可能会拖慢你应用中,严重依赖并行流的其它部分代码的性能。

作者:犬小哈 链接:https://juejin.cn/post/6844903830254010381 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-03-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一文带你玩转Java8Stream,从此集合操作SoEasy
    • 标签:java基础
    • 不同类型的流
    • 中间操作与终端操作
    • Stream 流的处理顺序
    • 数据流复用问题
    • Stream常用api
      • 中间操作
        • distinct
        • skip与limit
        • filter
        • peek
        • map
        • mapToInt
        • mapToLong
        • mapToDouble
        • sorted
        • FlatMap
        • Optional的flatMap
      • 终端操作
        • forEach与forEachOrdered
        • count
        • findAny、findFirst 、max与min
        • anyMatch、allMatch与noneMatch
        • toArray
        • reduce
        • collect
    • 常见收集器
      • Collectors.toList()
        • Collectors.toSet()
          • Collectors.toMap
            • Collectors.groupingBy
              • Collectors.partitioningBy
                • Collectors.summarizingInt
                  • Collectors.joining
                    • 其他
                      • 自定义收集器Collector
                        • 描述收集器的特征: enum Characteristics
                        • 并行流
                        相关产品与服务
                        容器服务
                        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档