本文共 7347 字,大约阅读时间需要 24 分钟。
这一篇文章,要有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个的意思)。
请将上面几个函数式接口,清晰的记在脑子里(前面4个使用量大),这几个接口的方法其实是高度再高度的抽象,根据实际使用几乎涵盖了方法应该有情况。具体如下:
function,顾名思义,函数的意思,这里的函数是指数学上的函数哦,你也可以说是严格函数语言中的函数,例如haskell里的,他接受一个参数,返回一个值,永远都是这样,是一个恒定的,状态不可改变的方法。其实想讲函数这个彻底将明白可以再开一篇博客了,所以这里不详细的说了。
上面说到,函数接口是对行为的抽象,因此我方便大家理解,就用java中的方法作例子。Fcuntion接口是对接受一个T类型参数,返回R类型的结果的方法的抽象,通过调用apply方法执行内容。
public static void main(String[] args) { Functionfunction= s -> s + "你好"; String a = function.apply("helloWorld!"); System.out.println(a); //控制台输出 helloWorld!你好 }
Consumer 接口翻译过来就是消费者,顾名思义,该接口对应的方法类型为接收一个参数,没有返回值,可以通俗的理解成将这个参数'消费掉了',一般来说使用Consumer接口往往伴随着一些期望状态的改变或者事件的发生,例如最典型的forEach就是使用的Consumer接口,虽然没有任何的返回值,但是却向控制台输出了语句。
Consumer 使用accept对参数执行行为public static void main(String[] args) { ConsumerprintString = s -> System.out.println(s); printString.accept("helloWorld!"); //控制台输出 helloWorld! }
Supplier 接口翻译过来就是提供者,和上面的消费者相反,该接口对应的方法类型为不接受参数,但是提供一个返回值,通俗的理解为这种接口是无私的奉献者,不仅不要参数,还返回一个值,使用get()方法获得这个返回值
public static void main(String[] args) { SuppliergetInstance = () -> "HelloWorld!"; System.out.println(getInstance.get()); // 控偶值台输出 HelloWorld}
predicate<t,boolean> 谓语接口,顾名思义,中文中的‘是’与‘不是’是中文语法的谓语,同样的该接口对应的方法为接收一个参数,返回一个Boolean类型值,多用于判断与过滤,当然你可以把他理解成特殊的Funcation<t,r>,但是为了便于区分语义,还是单独的划了一个接口,使用test()方法执行这段行为
public static void main(String[] args) { PredicatepredOdd = integer -> integer % 2 == 1; System.out.println(predOdd.test(5)); //控制台输出 true }
介绍完正面这四种最基本的接口,剩余的接口就可以很容易的理解了,java8中定义了几十种的函数接口,但是剩下的接口都是上面这几种接口的变种,大多为限制参数类型如IntPredicate等。
熟悉了以上几个接口,咱们就可以踏实的学习集合框架的新操作了。
当我们使用一个流的时候,通常包括三个基本步骤:
获取一个数据源(source)→ 数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。
Stream API可以极大提高Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 取重,排序,聚合等。
元素流在管道中经过一系列中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
先来看看一个例子:
ListtransactionsIds = widgets.stream() .filter(b -> b.getColor() == RED) .sorted((x,y) -> x.getWeight() - y.getWeight()) .mapToInt(Widget::getWeight) .sum();
Stream(流)是一个来自数据源的元素队列并支持聚合操作
和以前的Collection操作不同, Stream操作还有两个基础的特征:
1.Stream接口的静态工厂方法
2.在 Java 8 中, 集合接口有两个方法来生成流:(使用最多就是对集合创建stream)
3.数组的创建方法:
4.其他方式创建
Liststrings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");List filtered = strings.stream() .filter(string -> !string.isEmpty()) .collect(Collectors.toList());
对于Stream的使用就是使用其API,关键是其中方法的参数是各种各样的函数式接口,来看看Stream中的方法:
void forEach(Consumer<? super T> action);
Stream 提供了新的方法 'forEach' 来迭代流中的每个数据。以下代码片段使用 forEach 输出了10个随机数:
Random random = new Random();random.ints().limit(10).forEach(System.out::println);
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
map方法将对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素。为了提高处理效率,官方已封装好了,三种变形:mapToDouble,mapToInt,mapToLong。其实很好理解,如果想将原Stream中的数据类型,转换为double,int或者是long是可以调用相对应的方法。以下代码片段使用 map 输出了元素对应的平方数:
Listnumbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);// 获取对应的平方数List squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());
Stream<T> filter(Predicate<? super T> predicate);
filter方法对原Stream按照指定条件过滤,在新建的Stream中,只包含满足条件的元素,将不满足条件的元素过滤掉。以下代码片段使用 filter 方法过滤出空字符串:
Liststrings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");// 获取空字符串的数量int count = strings.stream().filter(string -> string.isEmpty()).count();
Stream<T> limit(long maxSize);
limit 方法用于获取指定数量的流。 以下代码片段使用 limit 方法打印出 10 条数据:
Random random = new Random();random.ints().limit(10).forEach(System.out::println);
Stream<T> sorted(Comparator<? super T> comparator);
sorted 方法用于对流进行排序。以下代码片段使用 sorted 方法对输出的 10 个随机数进行排序:(有无参重载)
Random random = new Random();random.ints().limit(10).sorted().forEach(System.out::println);
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);
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
parallelStream 是流并行处理程序的代替方法。以下实例我们使用 parallelStream 来输出空字符串的数量:
Liststrings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");// 获取空字符串的数量int count = strings.parallelStream().filter(string -> string.isEmpty()).count();
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方法以达到去除掉原Stream中重复的元素,生成的新Stream中没有没有重复的元素。
Stream.of(1,2,3,1,2,3) .distinct() .forEach(System.out::println); // 打印结果:1,2,3
count方法将返回Stream中元素的个数。
long count = Stream.of(1, 2, 3, 4, 5) .count();System.out.println("count:" + count);// 打印结果:count:5
Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串:
Liststrings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");List 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。
(完)
转载地址:http://rqhgf.baihongyu.com/