java8 --- Stream

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wxy540843763/article/details/80640784

本文是上篇java8-lambda的第二篇,介绍Stream和lambda的使用。

Java8中的Stream是对集合(Collection)对象功能的增强。它专注于对集合对象进行各种非常便利的操作,高效的聚合操作,或者大批量数据操作。Stream API借助于同样新出现的lambda表达式。极大的提高了编程效率和程序可读性。同时它提供了串行和并行两种操作模式进行汇聚操作,并发模式能够充分利用多核处理器的优秀,使用fork/join并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且很容易出现错误。但使用Stream API无需编写一行多线程的代码,就可以写出高性能的并发程序。

Stream不是集合元素,也不是数据结构,不保存数据,它是和算法和计算有关的。它更像一个高级版本的Iterator。原始的Iterator,用户只能显示的一个一个遍历元素并对其执行某些操作。高级半根的Stream,用户只要给出需要对其包含的元素执行什么操作,比如过滤掉长度大于10的字符串,获取每个字符串的首字母等。Stream会隐式地在内部进行遍历,做出相应的数据转换。

  Stream就如同一个迭代器,单向,不可往复,数据只能遍历一次。遍历一次就用尽了,就好比流水从面前流过,一区不复返。

和迭代器不同的是,Stream可以并行化操作,迭代器只能命令式的,串行化操作。而Stream使用并行化去遍历的时候,数据会分成多个段,其中每个段都会在不同的线程中去处理,然后将数据结果一起输出。Stream的并行操作依赖于Java7中引入的Fork/join框架来拆分任务和加速处理过程。

Java的并行API演变历程基本如下:

1.0-1.4 中的 java.lang.Thread

5.0 中的 java.util.concurrent

6.0 中的 Phasers 等

7.0 中的 Fork/Join 框架

8.0 中的 Lambda

Stream另外一个特点是:数据源本身可以是无限的。-----不太理解。

 

Stream的结构:

红色:创建一个Stream实例。生成了一个包含所有nums元素的Stream。(赋予其生命的地方)

绿色:把Stream转换成另外一个Stream,生成了一个filter过滤之后的Stream。Filter的条件就是num!=null.(赋予其灵魂的地方)

蓝色:把Stream里面包含的内容按照某种算法来汇聚陈给一个值。(丰收的地方)

 

创建Stream:

1.  通过Stream接口的静态工厂方法(注意Java8中可以带静态方法)

2.  通过Collection接口的默认方法(Default Method)-stream(),把Collection对象转换成Stream。

使用Stream静态方法创建Stream:

1.  of方法:

Stream<String> stringStream = Stream.of("1", "2", "3", "4");

stringStream.forEach(System.out::println);

    生成了含有字符串1,2,3,4的Stream。

 

2.  generator方法:

    Stream <Double> generate = Stream.generate(new Supplier <Double>() {

            @Override

            public Double get() {

                return Math.random();

            }

        });   generate.map(item->item).limit(10).forEach(System.out::println);

   

Stream <Double> generate = Stream.generate(() -> Math.random());

        generate.limit(10).forEach(System.out::println);

Stream <Double> generate = Stream.generate(Math::random);

        generate.limit(10).forEach(System.out::println);

 

使用Collection子类获取Stream:

在jdk8中的Collection类中,有一个方法叫stream(),这个方法也是java8的Default method。也就是说 所有Collection都可以获取自己对应的Stream对象。

 

转换Stream:

转换Stream就是将Stream通过某些行为转换成一个新的Stream。具体请看Stream接口。

1.  distinct: 对stream中的元素进行去重操作。(注:去重逻辑依靠对象的equals方法)。

2.  filter:对Stream中的元素使用给定的过滤函数进行过滤操作。新生成的Stream只包含符合条件的元素。

3.  map:对Stream中的元素使用给定的转换函数进行转换操作,新生成的stream值包含转换生成的元素。还有三个变种方法:mapToInt,mapToLong,mapToDouble.这三个方法省去了拆箱装箱的操作。

4.  flatMap: 每个元素转换得到的是Stream对象。会把子Stream中的元素压缩到父集合中。把InputStream中的层级结构扁平化,就是将最底层元素抽出来放在一起,最终output到新的Stream里面。

5.  peek:生成一个包含原Stream的所有元素的新Stream,同时会提供一个消费函数(Consumer实例),新Stream每个元素被消费的时候都会执行给定的消费函数。

6.  limit:对每个stream进行截断操作。获取前N个元素,如果原Stream中的元素个数小于N,则获取全部。

7.  skip: 返回一个丢弃原Stream的前N个元素后剩下元素组成的新Stream,如果原Stream中包含的元素个数小于N,那么返回空Stream。

8.  forEach:接收一个Lamdba表达式,然后在Stream的每一个元素上执行该表达式。

9.  findFirst:这是一个termimal兼short-circuiting操作,它总是返回Stream的第一个元素,或者空。

10.Optional:使用这个的目的是尽可能避免NullPointerException。

Stream <List <Integer>> lists = Stream.of(

                Arrays.asList(1),

                Arrays.asList(null, null, 3),

                Arrays.asList(1, 3, 3, 4, 4, 5)

        );

        lists.flatMap(child->child.stream()).forEach(item->Optional.ofNullable(item).ifPresent(System.out::println));

输出结果是:

1

3

1

3

3

4

4

5

上面的Optional.ofNillable().ifPresent()  如果不为null的时候就输出。

在更复杂的 if (xx != null)的情况中,使用Optional 代码的可读性更好,而且它提供的是编译时检查,能极大的降低 NPE 这种Runtime Exception 对程序的影响,或者迫使程序员更早的在编码阶段处理空值问题,而不是留到运行时再发现和调试

 

11.综合实例

Stream <String> stream = Stream.of("1", "2", "3", "4", null, "1", "2", "3", "5", "6","7","8");

   

        int sum = stream.filter(num -> num != null).distinct().map(item -> Integer.parseInt(item)).mapToInt(num -> num * 2).peek(System.out::println).skip(4).sum();

        System.out.println(sum);

输出结果:

2

4

6

8

10

12

14

16

52

 

 

Reduce StreamJ(这篇文章中的解释是:聚合Stream)

这里的Reduce和Sql语言中的聚合函数差不多的概念。

官方的文档:

A reductionoperation (also called a fold) takes a sequence of input elements and combinesthem into a single summary result by repeated application of a combiningoperation, such as finding the sum or maximum of a set of numbers, oraccumulating elements into a list. The streams classes have multiple forms ofgeneral reduction operations, called reduce() and collect(), as well asmultiple specialized reduction forms such as sum(), max(), or count().

 

Jdk8中的聚合分为可变汇聚其他汇聚。这里的汇聚可以去看看java doc。

1.  reduce();

这个方法主要是把Stream元素组合起来,他提供一个起始值(seed),然后按照运算规则和前面的第一个,第二次,第n次元素组合。字符串的拼接,数字的sum,min,max,average都是特殊的reduce。

    Integers.reduce(0,(a,b)->a+b);

或者

integers.reduce(0,Integer::sun);

String reduce = Stream.of("A", "B", "C", "D").reduce("", String::concat);

        System.out.println(reduce);

Integer reduce1 = Stream.of(2, 3, 3, 4, 54).reduce(3, Integer::min);

        System.out.println(reduce1);

int sum = Stream.of(2, 3, 6, 54, 8).mapToInt(i -> i).sum();

        System.out.println(sum);

 

2.  sorted()

ArrayList<String> names = new ArrayList();

        names.add("name1");

        names.add("name2");

        names.add("name3");

        names.add("name4");

        names.stream().limit(2).sorted((p1,p2)->p1.compareTo(p2)).forEach(System.out::println);

 

3.  match

Stream有三个match方法:

allMatch: Stream中全部元素符合传入的predicate,返回true。

anyMatch: Stream中只要有一个元素符合传入的predicate,返回true。

noneMatch: Stream中没有一个元素符合传入的predicate,返回true。

举个栗子吧:

boolean b = Stream.of(12, 32, 23, 31, 32, 17, 19).mapToInt(Integer::intValue).allMatch(item -> item > 10);

        System.out.println(b);

  boolean b1 = Stream.of(12, 32, 23, 31, 32, 17, 19).mapToInt(Integer::intValue).anyMatch(item -> item < 18);

        System.out.println(b1);

boolean b2 = Stream.of(12, 32, 23, 31, 32, 17, 19).mapToInt(Integer::intValue).anyMatch(item -> item < 0);

        System.out.println(b2);

 

 

4.  groupingBy/partitionBy

效果就是类似于sql中的group、、

后续研究。

 

 

总结:

不是数据结构

它没有内部存储,它只是用操作管道从source(数据结构、数组、generatorfunction、IOchannel)抓取数据。

它也绝不修改自己所封装的底层数据结构的数据。例如Stream 的filter 操作会产生一个不包含被过滤元素的新Stream,而不是从source 删除那些元素。

所有 Stream 的操作必须以lambda 表达式为参数

不支持索引访问

你可以请求第一个元素,但无法请求第二个,第三个,或最后一个。不过请参阅下一项。

很容易生成数组或者 List

惰性化

很多 Stream 操作是向后延迟的,一直到它弄清楚了最后需要多少数据才会开始。

Intermediate 操作永远是惰性化的。

并行能力

当一个 Stream 是并行化的,就不需要再写多线程代码,所有对它的操作会自动并行进行的。

可以是无限的

集合有固定大小,Stream 则不必。limit(n)和findFirst() 这类的short-circuiting 操作可以对无限的Stream 进行运算并很快完成

 

 

 

借鉴:

    https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/

http://www.importnew.com/20331.html

http://www.importnew.com/20317.html


 

再谈lambda表达式

1.java.util.function.Predicate(Predicate:断言,断定,述语,谓语)

官方解释是:

Represents a predicate (boolean-valued function) of one argument.

    表示一个参数的断定,断言(boolean型值)

@FunctionalInterface  //标注该接口是功能型接口,可以使用lambda。

public interface Predicate<T> {

    boolean test(T var1);

// and 与逻辑

    default Predicate<T> and(Predicate<? super T> var1) {

        Objects.requireNonNull(var1);

        return (var2) -> {

            return this.test(var2) && var1.test(var2);

        };

    }

// 非 逻辑

    default Predicate<T> negate() {

        return (var1) -> {

            return !this.test(var1);

        };

    }

// or或逻辑

    default Predicate<T> or(Predicate<? super T> var1) {

        Objects.requireNonNull(var1);

        return (var2) -> {

            return this.test(var2) || var1.test(var2);

        };

    }

// 判断是否相等

    static default <T> Predicate<T> isEqual(Object var0) {

        return null == var0?Objects::isNull:(var1) -> {

            return var0.equals(var1);

        };

    }

}

 

1.  Predicate 非常适合做过滤用。

刚煮的栗子

List<String> names = Arrays.asList("java","C++","C","python","javaScript");

names.stream().filter(item->item.startsWith("j")).forEach(System.out::println);

 输出结果:

java

javaScript

 

Stream API还可以接收多个Predicate合成一个。用于将filter()方法中条件合并起来。

栗子:

List<String> names = Arrays.asList("java","C++","C","python","javaScript");

Predicate <String> j = item -> item.startsWith("j");

Predicate <String> l = item -> item.length() == 4;

names.stream().filter(j.and(l)).forEach(System.out::println);

输出结果:

java

 

 

2.  java.util.function.Consumer

@FunctionalInterface

public interface Consumer<T> {

    void accept(T var1);

 

    default Consumer<T> andThen(Consumer<? super T> var1) {

        Objects.requireNonNull(var1);

        return (var2) -> {

            this.accept(var2);

            var1.accept(var2);

        };

    }

}

 表示一个可以接受简单输入参数的操作,并不返回任何结果。

List<String> names = Arrays.asList("java","C++","C","python","javaScript");

names.stream().forEach(System.out::println);

以上代码中的forEach方法中就是用到了Consumer接口。

 

3.  java.util.function.Function<T,R>

@FunctionalInterface

public interface Function<T, R> {

    R apply(T var1);

 

    default <V> Function<V, R> compose(Function<? super V, ? extends T> var1) {

        Objects.requireNonNull(var1);

        return (var2) -> {

            return this.apply(var1.apply(var2));

        };

    }

 

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> var1) {

        Objects.requireNonNull(var1);

        return (var2) -> {

            return var1.apply(this.apply(var2));

        };

    }

 

    static default <T> Function<T, T> identity() {

        return (var0) -> {

            return var0;

        };

    }

}

表示接收一个参数并且产生一个结果的功能。

栗子:

List<String> names = Arrays.asList("java","C++","C","python","javaScript");

names.stream().map(item->item.toUpperCase()).forEach(System.out::println);  

相当于:

map(new Function(){

          public R apply(T item){

            return item.toUpperCase();

          }

        })

 

4.  java.util.function.Supplier<T>

public interface Supplier<T> {

    T get();

}

表示一个供给者的结果。  -------------不懂 2017-11-21

    类似于工厂?不接受值。

5.  java.util.UnaryOperator<T>  接收T对象,返回T对象

6.  java.util.BinaryOperator<T>  接收两个T对象,返回T对象。

 

 

 

关于lambda表达式的总结:

1)lambda表达式仅能放入如下代码:预定义使用了 @Functional 注释的函数式接口,自带一个抽象函数的方法,或者SAM(Single Abstract Method 单个抽象方法)类型。这些称为lambda表达式的目标类型,可以用作返回类型,或lambda目标代码的参数。例如,若一个方法接收Runnable、Comparable或者 Callable 接口,都有单个抽象方法,可以传入lambda表达式。类似的,如果一个方法接受声明于 java.util.function 包内的接口,例如 Predicate、Function、Consumer 或 Supplier,那么可以向其传lambda表达式。

 

2)lambda表达式内可以使用方法引用,仅当该方法不修改lambda表达式提供的参数。本例中的lambda表达式可以换为方法引用,因为这仅是一个参数相同的简单方法调用。

3)lambda内部可以使用静态、非静态和局部变量,这称为lambda内的变量捕获。

4)Lambda表达式在Java中又称为闭包或匿名函数,所以如果有同事把它叫闭包的时候,不用惊讶。

5)Lambda方法在编译器内部被翻译成私有方法,并派发 invokedynamic 字节码指令来进行调用。

6)lambda表达式有个限制,那就是只能引用 final 或 final 局部变量,这就是说不能在lambda内部修改定义在域外的变量


猜你喜欢

转载自blog.csdn.net/wxy540843763/article/details/80640784