简单理解Java的lambda表达式和StreamsApi

前言

jdk1.8已经出了比较长的一段时间了,我们公司也开始逐步接入,各种新特性或API也渐渐用起来了(前路坑漫漫~~)
以下就个人理解,简单介绍下JDK1.8的两大特性:lambda表达式和StreamsAPI。

quick start

需求:将一群用户(User)按年龄从小至大排序。

public class User {
    private long id;
    private int age;
    ...
    get/set...
}

在java1.8之前:

List<User> userList = new ArrayList<>();
...填充数据
Collections.sort(userList, new Comparator<User>() {
     @Override
     public int compare(User user1, User user2) {
         return user1.getAge() - user2.getAge();
     }
 });

在java1.8:

List<User> userList = new ArrayList<>();
...填充数据
Collections.sort(userList, (user1, user2) -> user1.getAge() - user2.getAge());

看不懂的童鞋可以先看以下介绍。

语法

其实lambda表达式,通俗一点理解就是将一个方法(函数)写成某种特殊的形式,可以更加方便快捷地开发。
java通过lambda表达式替代了原来的匿名内部类的繁琐的写法,使其比较方便地实现原来的功能,同时使java这门语言往函数式编程的方向发展。

lambda语法:

  参数列表  箭头(->)  语句块或表达式

我们以上面的Collections中的sort方法为例:

// Collections类中的sort方法声明为:
public static <T> void sort(List<T> list, Comparator<? super T> c) 

// 我们来看下第二个参数,在jdk1.8中Comparator这个接口新增了一个@FunctionalInterface的注解
// 这个注解表示为该接口为函数式接口,可以使用lambda表达式表示其实现。
@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
    ...
}

利用lambda表达式语法,我们可以将实现写成(以UserList为例):

Collections.sort(userList, 
    // 参数列表 箭头(->)
    (User user1, User user2) -> {
        return user1.getAge() - user2.getAge(); //语句块
    }
);

由于编译器可以自动识别入参的类型,入参类型的声明可以省略

Collections.sort(userList, 
    // 参数列表 箭头(->)
    (user1, user2) -> {
        return user1.getAge() - user2.getAge(); //语句块
    }
);

上面的方法体中的是语句块,可以写成表达式

Collections.sort(userList, 
    // 参数列表 箭头(->)
    (user1, user2) ->user1.getAge() - user2.getAge());// 表达式

注:当该方法有返回值时,表达式的值则会被当成返回值
另外在jdk1.8,List已经提供了sort方法,可以直接userList.sort(...)

当接口中的入参为1个参数时,方法入参的括号可以省略。如List中的forEach方法:

// 将所有user的年龄增加1
userList.forEach(user -> user.setAge(user.getAge() + 1));

当接口的入参为非1个参数时(0个或多个),方法入参的括号不可省略

// 0个
new Thread(()->{
    ...
});
// 多个
Collections.sort(userList, (user1, user2) -> user1.getAge() - user2.getAge());

jdk自带的函数接口

为避免用户定义过多重复的函数接口以及提供给自身API(如Steam)的使用,jdk提供了许多通用的函数接口,基本上满足需求了。如下:

// 无返回值
@FunctionalInterface
public interface Consumer<T> { 
   void accept(T t);
}

// 返回true或false
@FunctionalInterface
public interface Predicate<T> {    
   boolean test(T t);
}

// 返回某个对象
@FunctionalInterface
public interface Function<T, R> {    
   R apply(T t);
}

另外Comparator、Runnable等接口也被打上@FunctionalInterface注解,表示为函数式接口。
具体的使用可以参考这篇文章

Stream(流)

Java 8 中的Stream是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作。由于Stream中的集合的操作时可以进行并行操作的,这也就可以充分利用多核处理器的优势。另外在日常的集合操作中也非常方便。

简单使用

需求:将大于18周岁的用户(User)按照年龄从小至大排序后,获取其对应的id集合。

 // 获取流(Stream<User>)
Stream<User> userStream = userList.stream();
// 过滤操作,过滤后只剩符合条件的流(Stream<User>)
Stream<User> filterStream = userStream.filter(user -> user.getAge() > 18);
// 根据年龄排序,得到排序后的流(Stream<User>)
Stream<User> sortedStream = filterStream.sorted((user1, user2) -> user1.getAge() - user2.getAge());

// 映射,sortedStream为Stream<User>对象,调用map后,映射成了关于id类型的流对象(Stream<Long>)
Stream<Long> idStream = sortedStream.map(user -> user.getId());

// 收集(终结方法),不再返回Stream对象了,而是转成集合对象List(可以理解前面的方法是水流啊流,现在用个盆子装起来了)
List<Long> idList = idStream.collect(Collectors.toList());

--------------------------------------------------------------------------------------------------------------

// 上面写法仅为大家方便理解,其实是可以写出链式写法的(也是日常写法,建议每个方法调用各占一行)
List<Long> idList = userList.stream() 
                .filter(user -> user.getAge() > 18)  
                .sorted((user1, user2) -> user1.getAge() - user2.getAge())  
                .map(user -> user.getId())  
                .collect(Collectors.toList());

Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。即每个Stream对象只能操作/调用相关方法一次,如果再次操作,则会抛出“java.lang.IllegalStateException: stream has already been operated upon or closed”(所以建议写成链式写法)
另外,这里提到的Stream流和IO流不是一个概念。

我们知道lambda表达式,其实就是代表了某个接口(interface)的一个实现,下面来看看filter、sort等方法传入的lambda代表的是什么接口:

// Stream接口
public interface Stream<T> extends BaseStream<T, Stream<T>> {
  ...
  // 上述提到的Predicate接口(有个test()方法,返回boolean)
  Stream<T> filter(Predicate<? super T> predicate);

  // 常见的比较接口Comparator
  Stream<T> sorted(Comparator<? super T> comparator);

  // 上述提到的Function接口(里面有个R apply(T)方法,入参为一个类型为T的对象,返回一个R类型的新对象,其实这就是一个映射过程)
  Stream<R> map(Function<? super T, ? extends R> mapper);

  // 这个方法为终结(Terminal)方法,返回的是某个类型R(通常为集合类型List、Set、Map等),
  // 入参为Collector接口,Collectors类中给我们提供了许多生成对应接口的静态方法,如toList()、toSet()等
  R collect(Collector<? super T, A, R> collector);
  ...
}

若对Streams API有兴趣或者不理解,推荐看下Java 8 中的 Streams API 详解,里面提到了Stream 非常详细的API和相关概念。本文的关于Stream的一些概念也是来自于此。

性能相关

网上很多关于Stream的性能评测,许多都太过于片面,或刻意错误使用(频繁装拆箱),或测试数据量过小;以下一篇个人认为是比较全面的测试:Stream Performance.

简单来说,性能方面Stream利用的是现代多核处理器的优势,可以将原本的遍历处理利用多线程来处理,数据量越大、计算机核数越多、操作越复杂,执行效率就越高。

Streams使用的建议:

  1. 对于简单操作推荐使用外部迭代手动实现(即常规forEach或iterator)
  2. 对于复杂操作,推荐使用Stream API
  3. 在多核情况下,推荐使用并行Stream API来发挥多核优势
  4. 单核情况下不建议使用并行Stream API
  5. 一般的Stream中装、拆箱会很耗时,建议使用IntStream、LongStream、DoubleStream

啰嗦几句

目前许多框架(如spring)的低版本,可能和JDK1.8兼容性不太好(尤其是lambda和代理类的相关问题),排查问题的时候,可以留意下新的API和lambda的问题,升级踩坑难以避免,量力而行。

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

推荐阅读更多精彩内容