本文是上篇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内部修改定义在域外的变量