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);**
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容