Java 8 Stream API

Posted 爱coding的卖油翁

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java 8 Stream API相关的知识,希望对你有一定的参考价值。

1.创建Stream

流(Stream) 到底是什么呢?是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。集合讲的是数据,流讲的是计算。Stream遵循“做什么,而不是怎么去做”的原则。

Stream与集合的区别:

  • Stream 自己不会存储元素。元素可能被存储在底层的集合中,或者根据需要产生出来。
  • Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
  • Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

Stream操作的三个步骤:

  • 创建一个Stream。
    一个数据源(如:集合、数组),获取一个流。
  • 中间操作。
    一个中间操作链,对数据源的数据进行处理。
  • 终止操作。
    一个终止操作,执行中间操作链,并产生结果。

Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:

  • default Stream stream():返回一个顺序流。
  • default Stream parallelStream():返回一个并行流。

Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:

  • static Stream stream(T[] array):返回一个流。
  • static IntStream stream(int[] array)
  • static LongStream stream(long[] array)
  • static DoubleStream stream(double[] array)

可以使用静态方法 Stream.of(),通过显示值创建一个流。它可以接收任意数量的参数。

  • static Stream of(T… values) : 返回一个流。

可以使用静态方法 Stream.iterate()和Stream.generate(),创建无限流。

  • static Stream iterate(final T seed, final UnaryOperator f)
  • static Stream generate(Supplier s)
    @Test
    public void test1() 
        // 1. Collection 提供了两个方法 stream() 与 parallelStream()
        List<String> list = new ArrayList<>();
        // 获取一个顺序流
        Stream<String> stream = list.stream();
        // 获取一个并行流
        Stream<String> parallelStream = list.parallelStream();

        // 2. 通过 Arrays 中的 stream() 获取一个数组流
        Integer[] nums = new Integer[10];
        Stream<Integer> stream1 = Arrays.stream(nums);

        // 3. 通过 Stream 类中静态方法 of()
        Stream<Integer> stream2 = Stream.of(1, 2, 3, 4, 5, 6);

        // 4. 创建无限流
        // 迭代
        Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2).limit(10);
        stream3.forEach(System.out::println);

        // 生成
        Stream<Double> stream4 = Stream.generate(Math::random).limit(2);
        stream4.forEach(System.out::println);
    

2.Stream的中间操作

多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理。而在终止操作时一次性全部处理,称为“惰性求值”。

1.筛选与切片

filter(Predicate p):接收 Lambda , 从流中排除某些元素。
distinct():筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素。
limit(long maxSize):截断流,使其元素不超过给定数量。
skip(long n):跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补。

2.映射

map(Function f):接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
mapToDouble(ToDoubleFunction f):接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。
mapToInt(ToIntFunction f):接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。
mapToLong(ToLongFunction f):接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。
flatMap(Function f):接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。

3.排序

sorted() :产生一个新流,其中按自然顺序排序。
sorted(Comparator comp):产生一个新流,其中按比较器顺序排序。

3.Stream的终止操作

终止操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。

1.查找与匹配

allMatch(Predicate p):检查是否匹配所有元素。
anyMatch(Predicate p):检查是否至少匹配一个元素。
noneMatch(Predicate p):检查是否没有匹配所有元素。
findFirst():返回第一个元素。
findAny():返回当前流中的任意元素。
count():返回流中元素总数。
max(Comparator c):返回流中最大值。
min(Comparator c):返回流中最小值。
forEach(Consumer c):内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部迭代——它帮你把迭代做了)。

2.归约(也叫聚合操作)

reduce(T iden, BinaryOperator b):可以将流中元素反复结合起来,得到一个值。
reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。返回 Optional。

3.收集结果

collect(Collector c):将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法。

Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到 List、Set、Map)。但是 Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下。

  • toList:把流中元素收集到List。

    List< Employee > emps = list.stream().collect(Collectors.toList());

  • toSet:把流中元素收集到Set。

    Set< Employee > emps = list.stream().collect(Collectors.toSet());

  • toCollection:把流中元素收集到创建的集合。

    ArrayList< Employee > emps = list.stream().collect(Collectors.toCollection(ArrayList::new));

  • counting:计算流中元素的个数。

    long count = list.stream().collect(Collectors.counting());

  • summingInt:对流中元素的整数属性求和。

    int total = list.stream().collect(Collectors.summingInt(Employee::getSalary));

  • averagingInt:计算流中元素Integer属性的平均值。

    double avg = list.stream().collect(Collectors.averagingInt(Employee::getSalary));

  • summarizingInt:收集流中Integer属性的统计值。如:平均值。

    IntSummaryStatistics iss = list.stream().collect(Collectors.summarizingInt(Employee::getSalary));

  • joining:连接流中每个字符串。

    String str = list.stream().map(Employee::getName).collect(Collectors.joining());

  • maxBy:根据比较器选择最大值。

    Optional< Emp > max = list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary)));

  • minBy:根据比较器选择最小值。

    Optional< Emp > min = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary)));

  • reducing:从一个作为累加器的初始值开始,利用BinaryOperator与流中元素逐个结合,从而归约成单个值。

    int total = list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum));

  • collectingAndThen:包裹另一个收集器,对其结果转换函数。
    inthow= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));

  • groupingBy:根据某属性值对流分组,属性为K,结果为V。

    Map< Emp.Status, List< Emp>> map = list.stream().collect(Collectors.groupingBy(Employee::getStatus));

  • partitioningBy:根据true或false进行分区。

    Map< Boolean, List< Emp >> vd = list.stream().collect(Collectors.partitioningBy(Employee::getManage));

// Test

public class Trader 

    private String name;
    private String city;

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    

    public String getCity() 
        return city;
    

    public void setCity(String city) 
        this.city = city;
    

    public Trader(String name, String city) 
        super();
        this.name = name;
        this.city = city;
    

    public Trader() 
        super();
    

    @Override
    public String toString() 
        return "Trader [name=" + name + ", city=" + city + "]";
    



public class Transaction 

    private Trader trader;
    private int year;
    private int value;

    public Transaction() 
    

    public Transaction(Trader trader, int year, int value) 
        this.trader = trader;
        this.year = year;
        this.value = value;
    

    public Trader getTrader() 
        return trader;
    

    public void setTrader(Trader trader) 
        this.trader = trader;
    

    public int getYear() 
        return year;
    

    public void setYear(int year) 
        this.year = year;
    

    public int getValue() 
        return value;
    

    public void setValue(int value) 
        this.value = value;
    

    @Override
    public String toString() 
        return "Transaction [trader=" + trader + ", year=" + year + ", value=" + value + "]";
    


public class TestTransaction 

    List<Transaction> transactions = null;

    @Before
    public void before() 
        Trader raoul = new Trader("Raoul", "Cambridge");
        Trader mario = new Trader("Mario", "Milan");
        Trader alan = new Trader("Alan", "Cambridge");
        Trader brian = new Trader("Brian", "Cambridge");

        transactions = Arrays.asList(
                new Transaction(brian, 2011, 300),
                new Transaction(raoul, 2012, 1000),
                new Transaction(raoul, 2011, 400),
                new Transaction(mario, 2012, 710),
                new Transaction(mario, 2012, 700),
                new Transaction(alan, 2012, 950)
        );
    

    /**
     * 1. 找出2011年发生的所有交易, 并按交易额排序(从低到高)
     */
    @Test
    public void test1() 
        transactions.stream()
                    .filter((t) -> t.getYear() == 2011)
                    .sorted((t1, t2) -> Integer.compare(t1.getValue(), t2.getValue()))
                    .forEach(System.out::println);
    

    /**
     * 2. 交易员都在哪些不同的城市工作过?
     */
    @Test
    public void test2() 
        transactions.stream()
                    .map((t) -> t.getTrader().getCity())
                    .distinct()
                    .forEach(System.out::println);
    

    /**
     * 3. 查找所有来自剑桥的交易员,并按姓名排序
     */
    @Test
    public void test3() 
        transactions.stream()
                    .filter((t) -> t.getTrader().getCity().equals("Cambridge"))
                    .sorted((t1, t2) -> t1.getTrader().getName().compareTo(t2.getTrader().getName()))
                    .forEach(System.out::println);
    

    /**
     * 4. 返回所有交易员的姓名字符串,按字母顺序排序
     */
    @Test
    public void test4() 
        transactions.stream()
                    .map((t) -> t.getTrader().getName())
                    .sorted()
                    .forEach(System.out::println);

        System.out.println("--------------------------");

        String reduce = transactions.stream()
                    .map((t) -> t.getTrader().getName())
                    .sorted()
                    .reduce("", String::concat);
        System.out.println(reduce);

        System.out.println("--------------------------");

        transactions.stream()
                    .map((t) -> t.getTrader().getName())
                    .flatMap(TestTransaction::filterCharacter)
                    .sorted((t1, t2) -> t1.compareToIgnoreCase(t2))
                    .forEach(System.out::println);
    

    public static Stream<String> filterCharacter(String str)
        List<String> list = new ArrayList<>();
        for (Character ch : str.toCharArray()) 
            list.add(ch.toString());
        
        return list.stream();
    

    /**
     * 5. 有没有交易员是在米兰工作的?
     */
    @Test
    public void test5() 
        boolean b = transactions.stream()
                    .anyMatch((t) -> t.getTrader().getCity().equals("Milan"));
        System.out.println(b);
    

    /**
     * 6. 打印生活在剑桥的交易员的所有交易额
     */
    @Test
    public void test6() 
        Optional<Integer> optional = transactions.stream()
                    .filter((t) -> t.getTrader().getCity().equals("Cambridge"))
                    .map((t) -> t.getValue())
                    .reduce(Integer::sum);
        System.out.println(optional.get());
    

    /**
     * 7. 所有交易中,最高的交易额是多少
     */
    @Test
    public void test7() 
        Optional<Integer> max = transactions.stream()
                    .map((t) -> t.getValue())
                    .max(Integer::compareTo);
        System.out.println(max.get());
    

    /**
     * 8. 找到交易额最小的交易
     */
    @Test
    public void test8() 
        Optional<Transaction> min = transactions.stream()
                    .min((t1, t2) -> Integer.compare(t1.getValue(), t2.getValue()));
        System.out.println(min.get());
    

将聚合的结果收集到Map中,可以使用Collections.toMap()方法,有2个参数,分别用来生产map的key和value。(java.util.stream.Collectors)如果有多个元素拥有相同的key,则会抛出一个IllegalStateException异常。

    @Test
    public void test1() 
        Map<Integer, String> map = emps.stream()
            .collect(Collectors.toMap(Employee::getId, Employee::getName));
        for(Integer i : map.keySet()) 
            System.out.println(map.get(i));
        
    

    @Test
    public void test2() 
        Map<Integer, Employee> map = emps.stream()
            .collect(Collectors.toMap(Employee::getId, Function.identity()));
        for(Integer i : map.keySet()) 
            System.out.println(map.get(i));
        
    

对于toMap方法的每种形式,都有一个对应的toConcurrentMap方法,用来产生一个并发的Stream。在并行收集的过程中,应当只使用一个并发的map。一个共享的map要比合并的map效率更高。当然,使用共享的map无法得到有序的结果。

4.原始信息流

对于Stream< Integer >、Stream< Long >、Stream< Double >这样的流,不过将每个数包装成相应的对象显然是一个低效的做法,Stream API提供了IntStream、LongStream、DoubleStream等类型,专门用来存储原始类型值。

        IntStream intStream = IntStream.of(1,2,3,4,5,6);
        DoubleStream doubleStream = DoubleStream.of(1f,2f,3f,4f);
        LongStream longStream = LongStream.of(1l,2l,3l,4l,5l,6l);
        Arrays.stream(value, from, to);

IntStream和LongStream有静态方法range()和rangeClosed(),用来产生一个范围内的整数。range(0, 100)和rangeClosed(0, 100),前者不包括上限,后者包括上限。

将对象刘转化为原始类型流,通过mapToLong、mapToInt、mapToDouble方法。

Stream<String> words = ...;
IntStream intStream = words.mapToInt(String::length);

将原始类型流转化为对象流,通过boxed方法。

Stream<Integer> stream = IntStream.range(0, 100).boxed();

注意:Random类现在提供了ints、longs、doubles这些方法,用来返回包含随机数的原始类型流。

5.Optional< T >类

Optional< T > 类(java.util.Optional) 是一个容器类,Optional< T >对象或者是对一个T类型对象的封装,或者表示不是任何对象。它比一般指向T类型的引用更安全,因为它不会返回null,可以避免空指针异常。

它的实现代码很少,只有350行,下面就对一些常用方法进行介绍:

// 只有2个成员变量
private static final Optional< ? > EMPTY = new Optional<>();
private final T value;

  • Optional.of(T t):创建一个 Optional 实例。
    public static <T> Optional<T> of(T value) 
        return new Optional<>(value);
    
  • Optional.empty():创建一个空的 Optional 实例。
    public static<T> Optional<T> empty() 
        @SuppressWarnings("unchecked")
        Optional<T> t = (Optional<T>) EMPTY;
        return t;
    
  • Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例。
    public static <T> Optional<T> ofNullable(T value) 
        return value == null ? empty() : of(value);
    
  • get():如果对象存在,会返回该对象,否则会抛出一个NoSuchElementException异常。
    public T get() 
        if (value == null) 
            throw new NoSuchElementException("No value present");
        
        return value;
    
  • isPresent():判断是否包含值。
    public boolean isPresent() 
        return value != null;
    
  • orElse(T t):如果调用对象包含值,返回该值,否则返回t。
    public T orElse(T other) 
        return value != null ? value : other;
    
  • orElseGet(Supplier s):如果调用对象包含值,返回该值,否则返回 s 获取的值。
    public T orElseGet(Supplier<? extends T> other) 
        return value != null ? value : other.get();
    `
  • map(Function f):如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()。
    public<U> Optional<U> map(Function<? super T, ? extends U> mapper) 
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else 
            return Optional.ofNullable(mapper.apply(value));
        
    
  • flatMap(Function mapper):与 map 类似,要求返回值必须是Optional。
    public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) 
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else 
            return Objects.requireNonNull(mapper.apply(value));
        
    

使用Optional可以避免空指针异常,高效的使用Optional的关键在于,使用一个或者接受正确值、或者返回另一个替代值的方法。以前使用 !=null判断, 现在使用Optional.isPresent()判断。

    @Test
    public void test2() 
        Optional<Employee> optional = Optional.empty();
        if (optional.isPresent()) 
            System.out.println(optional.get());
         else 
            System.out.println("optional is null");
        
    

    // 如果optional中没有该值,就返回一个默认值。
    @Test
    public void test3() 
        Optional<Employee> optional = Optional.empty();
        Employee employee = optional.orElse(new Employee());
        System.out.println(employee);
    

6.并行流

并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。默认情况下,流操作会创建一个串行流。Collection.parallelStream()可以创建一个并行流,parallel()方法可以将任意的串行流转换为一个并行流。

Stream< String > parallelWords = Stream.of(words).parallel();

记住,流不会收集它自己的数据,这些数据总是存在与另一个集合之中。如果你想要修改原有的集合,吗、、那么就无法定义流操作的输出。

        // ok
        List<String> list = new ArrayList<>();
        Stream<String> stream = list.stream();
        list.add("hello");
        long count = stream.distinct().count();

        // error
        List<String> list2 = new ArrayList<>();
        Stream<String> stream2 = list2.stream();
        stream2.forEach(s -> 
            if (s.length() < 12) 
                list2.remove(s);
            
        );

关于并行的操作,还在学习中,不是很了解…

以上是关于Java 8 Stream API的主要内容,如果未能解决你的问题,请参考以下文章

Java8新特性_stream API 练习

Java8 Stream流常用方法及例子

通过 websocket 问题进行币安流交易

Bittrex API 加密货币交易者:php 到 Java 的转换

贝宝 REST API 交易已获批准

Java8-04-04-笔记