Stream的基本使用

创建Stream

方法一:Stream.of()

直接传入可变参数,就可以创建一个能输出确定元素的Stream。

import java.util.stream.Stream;

public class demo1 {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("A","B","C","D");
        //forEach()相当于内部循环使用
        //下面双引号的写法相当于(x-> System.out.println(x));
        stream.forEach(System.out::println);
    }
}
/*
A
B
C
D
*/

方法二:基于数组或者Collection

public class Main {
    public static void main(String[] args) {
        Stream<String> stream1 = Arrays.stream(new String[] { "A", "B", "C" });
        Stream<String> stream2 = List.of("X", "Y", "Z").stream();
        stream1.forEach(System.out::println);
        stream2.forEach(System.out::println);
    }
}

基于数组创建Stream时,通过Arrays.stream(),对于集合Collection等,直接调用stream(),即可获得Stream。

方法三:基于Supplier

创建Stream也可以通过Stream.generate()方法,它需要传入一个Supplier对象,这种Stream保存的不是元素,而是算法,它可以用来表示无限序列。

例如,写一个不断生成自然数的Supplier,每次调用get方法就可以的到下一个自然数

public class Demo3 {
    public static void main(String[] args) {
        Stream<Integer> natual = Stream.generate(new NatualSupplier());
        natual.limit(20).forEach(System.out::println);
    }
}
class NatualSupplier implements Supplier<Integer>{
    int n =0;
    @Override
    public Integer get() {
        n++;
        return n;
    }
}

通过Supplier<Integer>实现了一个无限大的自然数序列,如果用List表示,即便在int范围内,也会占用巨大的内存,而Stream几乎不占用空间,因为每个元素都是实时计算出来的,用的时候再算。对于无限序列,如果直接调用forEach()或者count()这些最终求值操作,会进入死循环,因为永远无法计算完这个序列,所以正确的方法是先把无限序列变成有限序列,例如,用limit()方法可以截取前面若干个元素,这样就变成了一个有限序列,对这个有限序列调用forEach()或者count()操作就没有问题。

基本类型

因为Java的范型不支持基本类型,所以我们无法用Stream<int>这样的类型,会发生编译错误。为了保存int,只能使用Stream<Integer>,但这样会产生频繁的装箱、拆箱操作。为了提高效率,Java标准库提供了IntStreamLongStreamDoubleStream这三种使用基本类型的Stream,它们的使用方法和范型Stream没有大的区别,设计这三个Stream的目的是提高运行效率:

// 将int[]数组变为IntStream:
IntStream is = Arrays.stream(new int[] { 1, 2, 3 });
// 将Stream<String>转换为LongStream:
LongStream ls = List.of("1", "2", "3").stream().mapToLong(Long::parseLong);

map的使用

Stream.map()可以将一个Stream转为另一个Stream。map操作其实就是一个映射操作。例如:

Stream<Integer> s = Stream.of(1,2,3,4);
Stream<Integer> s2 = s.map(n -> n*n);

得到的s2其实就是s1中的每个元素的平方的stream。map()方法接收的是Function接口对象

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

Function的定义是:

@FunctionalInterface
public interface Function<T, R> {
    //将T类型转为R
    R apply(T t);
}

map不仅可以完成数学计算,还可以完成对对象的其他操作,具体操作由传入的Function中的apply方法决定。

public class Main {
    public static void main(String[] args) {
        List.of("  Apple ", " pear ", " ORANGE", " BaNaNa ")
                .stream()
                .map(String::trim) // 去空格
                .map(String::toLowerCase) // 变小写
                .forEach(System.out::println); // 打印
    }
}

filter的使用

Stream.filter()就是对Stream的所有元素一一进行帅选,剩下满足条件的构成一个新的Stream。例如:

public class Demo1 {
    public static void main(String[] args) {
        List<Integer> num = Lists.newArrayList(1,2,3,4,5,6,7,8);
        num.stream()
                .filter(n -> n % 2 !=0)
                .forEach(System.out::println);
    }
}

经过filter()处理后生成的新的stream是过滤掉偶数,留下奇数的stream。

filter()方法接收的是一个Predicate接口对象,它定义了一个test()方法,负责判断元素是否符合条件

Stream<T> filter(Predicate<? super T> predicate);
@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
}

filter()除了常用于数值外,也可应用于任何Java对象。例如,从一组给定的LocalDate中过滤掉周末,以便得到工作日:

public class Demo1 {
    public static void main(String[] args) {
        List<Integer> num = Lists.newArrayList(1,2,3,4,5,6,7,8);
        Stream.generate(new DateSupplier())
                .limit(30)
                .filter(ldt -> ldt.getDayOfWeek() != DayOfWeek.SATURDAY||ldt.getDayOfWeek() != DayOfWeek.SUNDAY)
                .forEach(System.out::println);
    }
}
class DateSupplier implements Supplier<LocalDate> {
    LocalDate localDate = LocalDate.of(2022,9,1);
    int n = 0;
    @Override
    public LocalDate get() {
       return localDate.plusDays(n++);
    }
}

reduce的使用

map()filter()都是Stream的转换方法,而Stream.reduce()则是Stream的一个聚合方法,它可以把一个Stream的所有元素按照聚合函数聚合成一个结果。

public class Demo2 {
    public static void main(String[] args) {
        int sum = Stream.of(1,2,3,4,5,6).reduce(0,(acc, n) -> acc+n);
        //这部分也可以通过方法引用来代替lambda来简化reduce(0,Integer::sum);
        System.out.println(sum);
    }
}

reduce方法传入的对象是BinaryOperator接口

T reduce(T identity, BinaryOperator<T> accumulator);
  • Identity : 定义一个元素代表是归并操作的初始值。
  • Accumulator: 定义一个带两个参数的函数,第一个参数是上个归并函数的返回值,第二个是Strem 中下一个元素。

如果去掉初始值,我们会得到一个Optional<Integer>

Optional<Integer> opt = stream.reduce((acc, n) -> acc + n);
if (opt.isPresent()) {
    System.out.println(opt.get());
}

这是因为Stream的元素有可能是0个,这样就没法调用reduce()的聚合函数了,因此返回Optional对象,需要进一步判断结果是否存在。

除了可以对数值进行累积计算外,灵活运用reduce()也可以对Java对象进行操作。下面的代码演示了如何将配置文件的每一行配置通过map()reduce()操作聚合成一个Map<String, String>

List<String> props = Lists.newArrayList("profile=native", "debug=true", "logging=warn", "interval=500");
        Map<String, String> map = props.stream()
                // 把k=v转换为Map[k]=v:
                .map(kv -> {
                    String[] ss = kv.split("=");
                    HashMap<String, String> hashMap = Maps.newHashMap();
                    hashMap.put(ss[0],ss[1]);
                    return hashMap;
                    // 把所有Map聚合到一个Map:
                }).reduce(new HashMap<String,String>(), (m,kv) -> {
                    m.putAll(kv);
                    return m;
                });
        map.forEach((k, v) -> {
            System.out.println(k+"="+v);
        });

reduce()是聚合方法,聚合方法会立刻对Stream进行计算。

输出集合

输出为List

如果希望把Stream中的元素保存到集合中,例如List,这是一种聚合操作,而不是转换操作,它会强制输出Stream中的每个元素。

public class Demo4 {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("Apple", "","Banana","Orange");
        List<String> list = stream.filter(s -> s != null && !s.isEmpty()).collect(Collectors.toList());
        System.out.println(list);
    }
}

Stream的每个元素收集到List的方法是调用collect()并传入Collectors.toList()对象,它实际上是一个Collector实例,通过类似reduce()的操作,把每个元素添加到一个收集器中(实际上是ArrayList)。

类似的,collect(Collectors.toSet())可以把Stream的每个元素收集到Set中。

输出为数组

把Stream的元素输出为数组只需调用toArray()方法,并传入数组的“构造方法”。

List<String> strs = Arrays.asList("a", "b", "c");
String[] dd = strs.stream().toArray(n -> new String[n]);
String[] dd1 = strs.stream().toArray(String[]::new);
Object[] obj = strs.stream().toArray();

输出为Map

输出为Map需要同时设置key和value,使用collect(Collectors.toMap()),返回的是HashMap

public class Demo3 {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("APPL:Apple", "MSFT:Microsoft");
        Map<String, String> map = stream.collect(Collectors.toMap(
                //设置key
                str -> str.split(":")[0],
                //设置value
                str -> str.split(":")[1]));
        System.out.println(map);
    }
}

分组输出

Stream可以分组输出,输出为Map

public static void fun1(){
    List<String> list = Lists.newArrayList("Apple", "Banana", "Blackberry", "Coconut", "Avocado", "Cherry", "Apricots");
    Map<String,List<String>> groups = list.stream().collect(Collectors.groupingBy(
            s -> s.substring(0,1), Collectors.toList()
    ));
    System.out.println(groups);
}

分组输出使用Collectors.groupingBy(),它需要提供两个函数:一个是分组的key,这里使用s -> s.substring(0, 1),表示只要首字母相同的String分到一组,第二个是分组的value,这里直接使用Collectors.toList(),表示输出为List,上述代码运行结果如下:

{A=[Apple, Avocado, Apricots], B=[Banana, Blackberry], C=[Coconut, Cherry]}

其他操作

排序sort

排序使用sorted()方法:

public static void fun2(){
    List<String> list = Lists.newArrayList("Apple","Coconut", "Banana");
    list.stream().sorted().collect(Collectors.toList())
}
/;*
Apple
Banana
Coconut
*/

1、sorted() 默认使用自然序排序, 其中的元素必须实现Comparable 接口
2、sorted(Comparator<? super T> comparator) :我们可以使用lambada 来创建一个Comparator 实例。可以按照升序或着降序来排序元素。

public static void fun2(){
    List<String> list = Lists.newArrayList("Apple","coconut", "Banana");
    //通过lambda表达式建立
    //Comparable接口实例list.stream().sorted((str1,str2) -> 	  str1.compareToIgnoreCase(str2)).forEach(System.out::println);
    //如果说lambda表达式本质上是将方法作为对象进行处理,那么方法引用就是将现有方法作为lambda表达式进行处理。方法引用:
    list.stream().sorted(String::compareToIgnoreCase).forEach(System.out::println);
}

去重distinct

对一个Stream进行去重,不需要转为Set,可以直接使用distinct():

List.of("A", "B", "A", "C", "B", "D")
    .stream()
    .distinct()
    .collect(Collectors.toList()); // [A, B, C, D]

合并concat

将两个Stream合并为一个Stream,使用Stream的静态方法concat()

Stream<String> s1 = List.of("A", "B", "C").stream();
Stream<String> s2 = List.of("D", "E").stream();
// 合并:
Stream<String> s = Stream.concat(s1, s2);
System.out.println(s.collect(Collectors.toList())); // [A, B, C, D, E]

截取skip,limit

skip()用于跳过当前Stream的前N个元素,limit()用于截取当前Stream最多前N个元素:

List.of("A", "B", "C", "D", "E", "F")
    .stream()
    .skip(2) // 跳过A, B
    .limit(3) // 截取C, D, E
    .collect(Collectors.toList()); // [C, D, E]

截取操作也是一个转换操作,将返回新的Stream

flapMap

所谓flatMap(),是指把Stream的每个元素映射为Stream,然后合并成一个新的Stream

Stream<List<Integer>> s = Stream.of(
        Arrays.asList(1, 2, 3),
        Arrays.asList(4, 5, 6),
        Arrays.asList(7, 8, 9));
//Stream<Integer> i = s.flapMap(List::stream);
Stream<Integer> i = s.flapMap(list -> list.stream());

并行

如果希望并行处理Stream中的元素可以使用parallel(),经过parallel()转换后的Stream只要可能,就会对后续操作进行并行处理。

Stream<String> s = ...
String[] result = s.parallel() // 变成一个可以并行处理的Stream
                   .sorted() // 可以进行并行排序
                   .toArray(String[]::new);
static void fun2() {
    int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    IntStream intArrStream = Arrays.stream(array);
    IntStream intParallelStream = Arrays.stream(array).parallel();
    intParallelStream.forEach(s ->
            {
                System.out.println(s + " " + Thread.currentThread().getName());
            }
    );
}

输出的结果为

4 ForkJoinPool.commonPool-worker-15
3 ForkJoinPool.commonPool-worker-9
6 ForkJoinPool.commonPool-worker-9
2 ForkJoinPool.commonPool-worker-4
8 ForkJoinPool.commonPool-worker-6
7 main
10 ForkJoinPool.commonPool-worker-13
9 ForkJoinPool.commonPool-worker-2
5 ForkJoinPool.commonPool-worker-11
1 ForkJoinPool.commonPool-worker-15

在并行流的情况下,在内部使用Fork和Join池创建和管理线程。并行流通过静态ForkJoinPool.commonPool() 方法创建ForkJoinPool实例。

通过上述代码我们可以看到仅通过添加parallel()即可轻松将顺序流转换为并行流,但是这并不意味着我们应该始终使用它。
在使用并行流时我们需要考虑很多因素,否则我们将会遭受并行流所带来的负面影响。

并行流比顺序流具有更高的开销,并且在线程之间的上下文进行协调切换需要花费大量时间。
仅在以下情况下,才需要考虑是否使用并行流:

  • 需要处理大量数据集。
  • 我们知道 Java 使用ForkJoinPool实现并行性,ForkJoinPool派生源流并提交执行,因此源数据流应该是可拆分的。例如:ArrayList非常容易拆分,因为我们可以通过索引找到中间元素并将其拆分,但是LinkedList很难拆分,并且在大多数情况下表现不佳。在这种情况下就不适合用并行流。
  • 我们在处理问题的时候确实遇到流性能问题,否则请不要为了并行而并行。
  • 我们需要确保线程之间的所有共享资源都是正确同步,否则可能会产生数据不一致问题。

参考:

https://www.liaoxuefeng.com/

https://www.jianshu.com/p/76d91f940055

https://blog.csdn.net/leimeng123/article/details/109676358

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值