JAVA8新特性:Stream与Lambda表达式
一、Stream学习
在学习lambda表达式之前,我们需要先了解Stream这个java8的新特性。
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表达式以及相关的对应操作,这里可以看出,Stream流一般配合lambda表达式以及方法引用使用
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 为队列strings建立流,并通过filter筛选需要的非空成员,最后成为新的list
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
从上面的例子我们可以看到,流的操作流程一般可以分成三类:
创建流:创建一个Stream。
中间方法:在一个或者多个操作中,将指定的Stream转换为另一个Stream的中间操作。一般是对数据集的整理(过滤、排序、匹配、抽取等)。
包括:filter()、distinct()、sorted()、map()、flatMap()等
终止方法:往往是完成对数据集中数据的处理,通过终止(terminal)方法来产生一个结果。该操作会强制它之前的延时操作立即执行,这之后该Stream就不能再被使用了。
如forEach(),还有allMatch()、anyMatch()、findAny()、 findFirst(),数值计算类的方法有sum、max、min、average等等。终止方法也可以是对集合的处理,如reduce()、 collect()等等。reduce()方法的处理方式一般是每次都产生新的数据集,而collect()方法是在原数据集的基础上进行更新,过程中不产生新的数据集。
二、Stream相关方法
forEach
Stream 提供了新的方法 'forEach' 来迭代流中的每个数据。
// 为队列strings建立流,并通过foreach遍历并输出所有元素
//注意,该处::为方法引用,作为stream参数含义为,将方法传递给stream,将其内部每个成员都作为参数执行一遍该方法。
strings.stream().forEach(System.out::print);
map
Stream提供的方法之一,将stream内每个元素都根据lambda表达式或者方法引用的逻辑映射到对应的结果
//将string队列中的元素每个都计算其长度并输出到一个新的list中
List<Integer> lengthList = strings.stream().map((String s)->(s.length())).collect(Collectors.toList());
filter
Stream提供的方法之一,根据提供的lambda表达式,将流中的每个元素过一遍条件,最终stream中只留存满足条件的成员
// 为队列strings建立流,并通过filter筛选需要的非空成员,最后成为新的list
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
limit
Stream提供的方法之一,只保留流内前n个成员
sorted
Stream提供的方法之一,对流内元素进行排序。可以传入Compator比较器,如不传入参数,成员需实现Comparable接口,默认为自然序升序,采用经过优化的归并排序。
List<Integer> numList = Lists.newArrayList(5,3,1,7,1);
//升序输出
List<Integer> sortedList = numList.stream().sorted().collect(Collectors.toList());
//降序输出,利用Comparator的reverse
List<Integer> reverseSortedList = numList.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());
二、lambda表达式
lambda表达式为java8提供的一套语法糖,本质上为由编译器推断并帮你转换包装为常规的代码,因此你可以使用更少的代码来实现同样的功能。作为java8的一种新特性,lambda表达式可以让java代码表现的更加简单、优雅,同时可以更加符合函数式编程的特性。但最好不要到处都用,对于一些关键的业务逻辑,传统的方式更加易于维护以及调试。大数量下的遍历以及数据处理,增强的for-each也比lambda表达式效率更高。
Lambda表达式的语法
基本语法:
(parameters) -> expression
或
(parameters) ->{ statements; }
可以通过简单的例子来了解lambda表达式的特性。下面是几个简单的lambda表达式,
// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)
以上就是一些简单的lambda表达式,下面我们可以通过一个遍历list的例子,来看看lambda表达式是如何使用的。
String[] atp = {"Rafael Nadal", "Novak Djokovic",
"Stanislas Wawrinka",
"David Ferrer","Roger Federer",
"Andy Murray","Tomas Berdych",
"Juan Martin Del Potro"};
List<String> players = Arrays.asList(atp);
// 以前的循环方式
for (String player : players) {
System.out.print(player + "; ");
}
// 使用 lambda 表达式以及函数操作(functional operation)
players.forEach((player) -> System.out.print(player + "; "));
// 在 Java 8 中使用双冒号操作符(double colon operator)
players.forEach(System.out::println);
在上面的表达式中,后面使用了三种java8中新加入的语法糖,foreach、lambda以及双冒号操作符,双冒号操作符在java8中的意义为方法引用。
本质上,lambda表达式所返回的是“编译器认为该处不会导致歧义并应该返回的格式”,下面针对匿名内部类的例子具体讲解
//test.foreach()方法中,我们需要一个BiConsumer<? super K, ? super V>类型的参数,那么构建这个参数本来如下所示
Consumer<Integer> consumer = new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer + ";");
}
};
test.forEach(consumer);
//从上面我们可以看到,这里是使用了一个匿名内部类Consumer,
//在jdk1.8中,Consumer已经被注解为了一个函数式接口(除了与Object的public方法签名一致的方法外,只有一个抽象方法)
//为什么Object的public方法可以豁免,是因为当实现接口时,所有实现类都会继承Object父类,因此不会出现歧义,因此在注解成为函数式接口时,编译器可以跳过Object的public签名一致的接口
//那么,当我们在对匿名类实例化时,编译器会自动实现相关的抽象方法,当我们加入lambda表达式时,编译器可以判断出我们是想覆盖这个唯一的抽象方法。
test.forEach((player) -> System.out.print(player + "; "));
//这里也是一样
Collections.sort(test, (o1, o2)->o1-o2);**