概述
这一篇文章,要有Lambda表达式的基础,在此对Lambda的使用不做介绍,请看前一篇对Java lambda的使用。
学以致用,学习Lambda表达式,就冲java 8 新的库Stream来说就够本。一定将Lambda和Stream分开来学,先把Lambda表达式玩转了,在深入学习Stream就容易了,因为里面大量的lambda的写法,不要因为lambda不熟练影响了Stream API的使用,时刻提醒自己方法内传递的是函数式接口的对象,lambda只不过是重写了接口的方法体。
前提
使用Stream前,先来着重学习几个java8函数式接口。
在学习了解之前,希望大家能记住几个单词,掌握这几个单词,什么3,40个官方的函数接口都是小问题了,不信的话接着往下看啦。ok,那这几个单词呢分别是supplier提供者 ,consumer消费者 ,function函数,Predicate断言, operation运算符, binary二元(就是数学里二元一次方程那个二元,代表2个的意思)。
- Function<T, R>:接受一个参数T,返回结果R
- Predicate<T>:接受一个参数T,返回boolean
- Supplier<T>:不接受任何参数,返回结果T
- Consumer<T>:接受一个参数T,不返回结果
- UnaryOperator<T>:继承自Function<T, T>,接受一个参数T,返回相同类型T的结果
- BiFunction<T, U, R>:接受两个参数T和U,返回结果R
- BinaryOperator<T>:继承自BiFunction<T, T, T>,接受两个相同类型T的参数,返回相同类型T的结果
请将上面几个函数式接口,清晰的记在脑子里(前面4个使用量大),这几个接口的方法其实是高度再高度的抽象,根据实际使用几乎涵盖了方法应该有情况。具体如下:
Functio<t,r>接口
function,顾名思义,函数的意思,这里的函数是指数学上的函数哦,你也可以说是严格函数语言中的函数,例如haskell里的,他接受一个参数,返回一个值,永远都是这样,是一个恒定的,状态不可改变的方法。其实想讲函数这个彻底将明白可以再开一篇博客了,所以这里不详细的说了。
上面说到,函数接口是对行为的抽象,因此我方便大家理解,就用java中的方法作例子。
Fcuntion接口是对接受一个T类型参数,返回R类型的结果的方法的抽象,通过调用apply方法执行内容。
public static void main(String[] args) {
Function<String> function= s -> s + "你好";
String a = function.apply("helloWorld!");
System.out.println(a);
//控制台输出 helloWorld!你好
}
Consumer 接口
Consumer 接口翻译过来就是消费者,顾名思义,该接口对应的方法类型为接收一个参数,没有返回值,可以通俗的理解成将这个参数'消费掉了',一般来说使用Consumer接口往往伴随着一些期望状态的改变或者事件的发生,例如最典型的forEach就是使用的Consumer接口,虽然没有任何的返回值,但是却向控制台输出了语句。
Consumer 使用accept对参数执行行为
public static void main(String[] args) {
Consumer<String> printString = s -> System.out.println(s);
printString.accept("helloWorld!");
//控制台输出 helloWorld!
}
Supplier 接口
Supplier 接口翻译过来就是提供者,和上面的消费者相反,该接口对应的方法类型为不接受参数,但是提供一个返回值,通俗的理解为这种接口是无私的奉献者,不仅不要参数,还返回一个值,使用get()方法获得这个返回值
public static void main(String[] args) {
Supplier<String> getInstance = () -> "HelloWorld!";
System.out.println(getInstance.get());
// 控偶值台输出 HelloWorld
}
Predicate 接口
predicate<t,boolean> 谓语接口,顾名思义,中文中的‘是’与‘不是’是中文语法的谓语,同样的该接口对应的方法为接收一个参数,返回一个Boolean类型值,多用于判断与过滤,当然你可以把他理解成特殊的Funcation<t,r>,但是为了便于区分语义,还是单独的划了一个接口,使用test()方法执行这段行为
public static void main(String[] args) {
Predicate<Integer> predOdd = integer -> integer % 2 == 1;
System.out.println(predOdd.test(5));
//控制台输出 true
}
其他的接口
介绍完正面这四种最基本的接口,剩余的接口就可以很容易的理解了,java8中定义了几十种的函数接口,但是剩下的接口都是上面这几种接口的变种,大多为限制参数类型如IntPredicate等。
熟悉了以上几个接口,咱们就可以踏实的学习集合框架的新操作了。
Java 8 Stream
当我们使用一个流的时候,通常包括三个基本步骤:
获取一个数据源(source)→ 数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。
Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 取重,排序,聚合等。
元素流在管道中经过一系列中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
先来看看一个例子:
List<Integer> transactionsIds =
widgets.stream()
.filter(b -> b.getColor() == RED)
.sorted((x,y) -> x.getWeight() - y.getWeight())
.mapToInt(Widget::getWeight)
.sum();
什么是Stream?
Stream(流)是一个来自数据源的元素队列并支持聚合操作
- 元素是特定类型的对象,形成一个队列。 Java中的Stream并不会存储元素,而是按需计算。
- 数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
- 聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等。
和以前的Collection操作不同, Stream操作还有两个基础的特征:
- Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
- 内部迭代: 以前对集合遍历都是通过Iterator或者For-Each的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式, 通过访问者模式(Visitor)实现。
创建Stream
1.Stream接口的静态工厂方法
- of(T... values):返回含有多个T元素的Stream
- generate(Supplier<T> s):返回一个无限长度的Stream
2.在 Java 8 中, 集合接口有两个方法来生成流:(使用最多就是对集合创建stream)
- stream() − 为集合创建串行流。
- parallelStream() − 为集合创建并行流。
3.数组的创建方法:
- Arrays.stream(T[] array)
4.其他方式创建
- Random.ints()
- BitSet.stream()
- Pattern.splitAsStream(java.lang.CharSequence)
- JarFile.stream()
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream()
.filter(string -> !string.isEmpty())
.collect(Collectors.toList());
对于Stream的使用就是使用其API,关键是其中方法的参数是各种各样的函数式接口,来看看Stream中的方法:
forEach(Collection接口中添加新forEach的方法,使用相同)
void forEach(Consumer<? super T> action);
Stream 提供了新的方法 'forEach' 来迭代流中的每个数据。以下代码片段使用 forEach 输出了10个随机数:
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
map
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
map方法将对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素。为了提高处理效率,官方已封装好了,三种变形:mapToDouble,mapToInt,mapToLong。其实很好理解,如果想将原Stream中的数据类型,转换为double,int或者是long是可以调用相对应的方法。以下代码片段使用 map 输出了元素对应的平方数:
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
// 获取对应的平方数
List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());
filter
Stream<T> filter(Predicate<? super T> predicate);
filter方法对原Stream按照指定条件过滤,在新建的Stream中,只包含满足条件的元素,将不满足条件的元素过滤掉。以下代码片段使用 filter 方法过滤出空字符串:
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空字符串的数量
int count = strings.stream().filter(string -> string.isEmpty()).count();
limit
Stream<T> limit(long maxSize);
limit 方法用于获取指定数量的流。 以下代码片段使用 limit 方法打印出 10 条数据:
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);
sorted
Stream<T> sorted(Comparator<? super T> comparator);
sorted 方法用于对流进行排序。以下代码片段使用 sorted 方法对输出的 10 个随机数进行排序:(有无参重载)
Random random = new Random();
random.ints().limit(10).sorted().forEach(System.out::println);
peek
Stream<T> peek(Consumer<? super T> action);
peek方法生成一个包含原Stream的所有元素的新Stream,同时会提供一个消费函数(Consumer实例),新Stream每个元素被消费的时候都会执行给定的消费函数,并且消费函数优先执行
Stream.of(1, 2, 3, 4, 5)
.peek(integer -> System.out.println("accept:" + integer))
.forEach(System.out::println);
skip
Stream<T> skip(long n);
skip方法将过滤掉原Stream中的前N个元素,返回剩下的元素所组成的新Stream。如果原Stream的元素个数大于N,将返回原Stream的后(原Stream长度-N)个元素所组成的新Stream;如果原Stream的元素个数小于或等于N,将返回一个空Stream。
Stream.of(1, 2, 3,4,5)
.skip(2)
.forEach(System.out::println);
// 打印结果
// 3,4,5
并行(parallel)程序
parallelStream 是流并行处理程序的代替方法。以下实例我们使用 parallelStream 来输出空字符串的数量:
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空字符串的数量
int count = strings.parallelStream().filter(string -> string.isEmpty()).count();
concat
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b);
concat方法将两个Stream连接在一起,合成一个Stream。若两个输入的Stream都时排序的,则新Stream也是排序的;若输入的Stream中任何一个是并行的,则新的Stream也是并行的;若关闭新的Stream时,原两个输入的Stream都将执行关闭处理。
Stream.concat(Stream.of(1, 2, 3), Stream.of(4, 5))
.forEach(integer -> System.out.print(integer + " "));
// 打印结果
// 1 2 3 4 5
distinct
distinct方法以达到去除掉原Stream中重复的元素,生成的新Stream中没有没有重复的元素。
Stream.of(1,2,3,1,2,3)
.distinct()
.forEach(System.out::println);
// 打印结果:1,2,3
count
count方法将返回Stream中元素的个数。
long count = Stream.of(1, 2, 3, 4, 5)
.count();
System.out.println("count:" + count);// 打印结果:count:5
Collectors 合并器
Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串:
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
System.out.println("筛选列表: " + filtered);
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", "));
System.out.println("合并字符串: " + mergedString);
等还有其他一些API使用不是很频繁,如需使用请查看官方API。
(完)