当前位置: 移动技术网 > IT编程>开发语言>Java > Java Lambda 表达式详解及示例代码

Java Lambda 表达式详解及示例代码

2019年07月22日  | 移动技术网IT编程  | 我要评论
java lambda 表达式是 java 8 引入的一个新的功能,可以说是模拟函数式编程的一个语法糖,类似于 javascript 中的闭包,但又有些不同,主要目的是提供

java lambda 表达式是 java 8 引入的一个新的功能,可以说是模拟函数式编程的一个语法糖,类似于 javascript 中的闭包,但又有些不同,主要目的是提供一个函数化的语法来简化我们的编码。

lambda 基本语法

lambda 的基本结构为 (arguments) -> body,有如下几种情况:

  1. 参数类型可推导时,不需要指定类型,如 (a) -> system.out.println(a)
  2. 当只有一个参数且类型可推导时,不强制写 (), 如 a -> system.out.println(a)
  3. 参数指定类型时,必须有括号,如 (int a) -> system.out.println(a)
  4. 参数可以为空,如 () -> system.out.println(“hello”)

body 需要用 {} 包含语句,当只有一条语句时 {} 可省略

常见的写法如下:

(a) -> a * a
(int a, int b) -> a + b
(a, b) -> {return a - b;}
() -> system.out.println(thread.currentthread().getid())

函数式接口 functionalinterface

概念

java lambda 表达式以函数式接口为基础。什么是函数式接口(functionalinterface)? 简单说来就是只有一个方法(函数)的接口,这类接口的目的是为了一个单一的操作,也就相当于一个单一的函数了。常见的接口如:runnable, comparator 都是函数式接口,并且都标注了注解 @functionalinterface 。

举例

以 thread 为例说明很容易理解。runnable 接口是我们线程编程时常用的一个接口,就包含一个方法 void run(),这个方法就是线程的运行逻辑。按照以前的语法,我们新建线程一般要用到 runnable 的匿名类,如下:

new thread(new runnable() {
  @override
  public void run() {
    system.out.println(thread.currentthread().getid());
  }

}).start();

如果写多了,是不是很无聊,而基于 lambda 的写法则变得简洁明了,如下:

new thread(() -> system.out.println(thread.currentthread().getid())).start();

注意 thread 的参数,runnable 的匿名实现就通过一句就实现了出来,写成下面的更好理解

runnable r = () -> system.out.println(thread.currentthread().getid());
new thread(r).start();

当然 lambda 的目的不仅仅是写起来简洁,更高层次的目的等体会到了再总结。

再看一个比较器的例子,按照传统的写法,如下:

integer[] a = {1, 8, 3, 9, 2, 0, 5};
arrays.sort(a, new comparator<integer>() {
  @override
  public int compare(integer o1, integer o2) {
    return o1 - o2;
  }
});

lambda 表达式写法如下:

integer[] a = {1, 8, 3, 9, 2, 0, 5};
arrays.sort(a, (o1, o2) -> o1 - o2);

jdk中的函数式接口

为了现有的类库能够直接使用 lambda 表达式,java 8 以前存在一些接口已经被标注为函数式接口的:

  1. java.lang.runnable
  2. java.util.comparator
  3. java.util.concurrent.callable
  4. java.io.filefilter
  5. java.security.privilegedaction
  6. java.beans.propertychangelistener

java 8 中更是新增加了一个包 java.util.function,带来了常用的函数式接口:

  1. function<t, r> - 函数:输入 t 输出 r
  2. bifunction<t, u, r> - 函数:输入 t 和 u 输出 r 对象
  3. predicate<t> - 断言/判断:输入 t 输出 boolean
  4. bipredicate<t, u> - 断言/判断:输入 t 和 u 输出 boolean
  5. supplier<t> - 生产者:无输入,输出 t
  6. consumer<t> - 消费者:输入 t,无输出
  7. biconsumer<t, u> - 消费者:输入 t 和 u 无输出
  8. unaryoperator<t> - 单元运算:输入 t 输出 t
  9. binaryoperator<t> - 二元运算:输入 t 和 t 输出 t

另外还对基本类型的处理增加了更加具体的函数是接口,包括:booleansupplier, doublebinaryoperator, doubleconsumer, doublefunction<r>, doublepredicate, doublesupplier, doubletointfunction, doubletolongfunction, doubleunaryoperator, intbinaryoperator, intconsumer, intfunction<r>, intpredicate, intsupplier, inttodoublefunction, inttolongfunction, intunaryoperator, longbinaryoperator, longconsumer,longfunction<r>, longpredicate, longsupplier, longtodoublefunction,longtointfunction, longunaryoperator, todoublebifunction<t, u>, todoublefunction<t>,tointbifunction<t, u>, tointfunction<t>, tolongbifunction<t, u>, tolongfunction<t> 。结合上面的函数式接口,对这些基本类型的函数式接口通过类名就能一眼看出接口的作用。

创建函数式接口

有时候我们需要自己实现一个函数式接口,做法也很简单,首先你要保证此接口只能有一个函数操作,然后在接口类型上标注注解 @functionalinterface 即可。

类型推导

类型推导是 lambda 表达式的基础,类型推导的过程就是 lambda 表达式的编译过程。以下面的代码为例:

function<string, integer> strtoint = str -> integer.parseint(str);
编译期间,我理解的类型推导的过程如下:

  1. 先确定目标类型 function
  2. function 作为函数式接口,其方法签名为:integer apply(string t)
  3. 检测 str -> integer.parseint(str) 是否与方法签名匹配(方法的参数类型、个数、顺序 和返回值类型)
  4. 如果不匹配,则报编译错误

这里的目标类型是关键,通过目标类型获取方法签名,然后和 lambda 表达式做出对比。

方法引用

方法引用(method reference)的基础同样是函数式接口,可以直接作为函数式接口的实现,与 lambda 表达式有相同的作用,同样依赖于类型推导。方法引用可以看作是只调用一个方法的 lambda 表达式的简化。

方法引用的语法为: type::methodname 或者 instancename::methodname , 构造函数对应的 methodname 为 new。

例如上面曾用到例子:

function<string, integer> strtoint = str -> integer.parseint(str);

对应的方法引用的写法为

function<string, integer> strtoint = integer::parseint;

根据方法的类型,方法引用主要分为一下几种类型,构造方法引用、静态方法引用、实例上实例方法引用、类型上实例方法引用等

构造方法引用

语法为: type::new 。 如下面的函数为了将字符串转为数组

方法引用写法

function<string, integer> strtoint = integer::new;

lambda 写法

function<string, integer> strtoint = str -> new integer(str);

传统写法

function<string, integer> strtoint = new function<string, integer>() {
  @override
  public integer apply(string str) {
    return new integer(str);
  }
};


数组构造方法引用

语法为: type[]::new 。如下面的函数为了构造一个指定长度的字符串数组

方法引用写法

function<integer, string[]> fixedarray = string[]::new;

方法引用写法

function<integer, string[]> fixedarray = length -> new string[length];

传统写法

function<integer, string[]> fixedarray = new function<integer, string[]>() {
  @override
  public string[] apply(integer length) {
    return new string[length];
  }
};

静态方法引用

语法为: type::new 。 如下面的函数同样为了将字符串转为数组

方法引用写法

function<string, integer> strtoint = integer::parseint;

lambda 写法

function<string, integer> strtoint = str -> integer.parseint(str);

传统写法

function<string, integer> strtoint = new function<string, integer>() {
  @override
  public integer apply(string str) {
    return integer.parseint(str);
  }
};

实例上实例方法引用

语法为: instancename::methodname 。如下面的判断函数用来判断给定的姓名是否在列表中存在

list<string> names = arrays.aslist(new string[]{"张三", "李四", "王五"});
predicate<string> checknameexists = names::contains;
system.out.println(checknameexists.test("张三"));
system.out.println(checknameexists.test("张四"));

类型上实例方法引用

语法为: type::methodname 。运行时引用是指上下文中的对象,如下面的函数来返回字符串的长度

function<string, integer> calcstrlength = string::length;
system.out.println(calcstrlength.apply("张三"));
list<string> names = arrays.aslist(new string[]{"zhangsan", "lisi", "wangwu"});
names.stream().map(string::length).foreach(system.out::println);

又比如下面的函数已指定的分隔符分割字符串为数组

bifunction<string, string, string[]> split = string::split;
string[] names = split.apply("zhangsan,lisi,wangwu", ",");
system.out.println(arrays.tostring(names));

stream 对象

概念

什么是 stream ? 这里的 stream 不同于 io 中的 inputstream 和 outputstream,stream 位于包 java.util.stream 中, 也是 java 8 新加入的,stream 只的是一组支持串行并行聚合操作的元素,可以理解为集合或者迭代器的增强版。什么是聚合操作?简单举例来说常见的有平均值、最大值、最小值、总和、排序、过滤等。

stream 的几个特征:

单次处理。一次处理结束后,当前stream就关闭了。
支持并行操作
常见的获取 stream 的方式

从集合中获取

collection.stream();
collection.parallelstream();

静态工厂

arrays.stream(array)
stream.of(t …)
intstream.range()
这里只对 stream 做简单的介绍,下面会有具体的应用。要说 stream 与 lambda 表达式有什么关系,其实并没有什么特别紧密的关系,只是 lambda 表达式极大的方便了 stream 的使用。如果没有 lambda 表达式,使用 stream 的过程中会产生大量的匿名类,非常别扭。

举例

以下的demo依赖于 employee 对象,以及由 employee 对象组成的 list 对象。

public class employee {

  private string name;
  private string sex;
  private int age;

  public employee(string name, string sex, int age) {
    super();
    this.name = name;
    this.sex = sex;
    this.age = age;
  }
  public string getname() {
    return name;
  }

  public string getsex() {
    return sex;
  }
  public int getage() {
    return age;
  }
  @override
  public string tostring() {
    stringbuilder builder = new stringbuilder();
    builder.append("employee {name=").append(name).append(", sex=").append(sex).append(", age=").append(age)
        .append("}");
    return builder.tostring();
  } 
}
list<employee> employees = new arraylist<>();
employees.add(new employee("张三", "男", 25));
employees.add(new employee("李四", "女", 24));
employees.add(new employee("王五", "女", 23));
employees.add(new employee("周六", "男", 22));
employees.add(new employee("孙七", "女", 21));
employees.add(new employee("刘八", "男", 20));

打印所有员工

collection 提供了 foreach 方法,供我们逐个操作单个对象。

employees.foreach(e -> system.out.println(e));
或者
employees.stream().foreach(e -> system.out.println(e));

按年龄排序

collections.sort(employees, (e1, e2) -> e1.getage() - e2.getage());
employees.foreach(e -> system.out.println(e));
或者
employees.stream().sorted((e1, e2) -> e1.getage() - e2.getage()).foreach(e -> system.out.println(e));
打印年龄最大的女员工

max/min 返回指定排序条件下最大/最小的元素

employee maxagefemaleemployee = employees.stream()
    .filter(e -> "女".equals(e.getsex()))
    .max((e1, e2) -> e1.getage() - e2.getage())
    .get();
system.out.println(maxagefemaleemployee);

打印出年龄大于20 的男员工

filter 可以过滤出符合条件的元素

employees.stream()
        .filter(e -> e.getage() > 20 && "男".equals(e.getsex()))
        .foreach(e -> system.out.println(e));
打印出年龄最大的2名男员工

limit 方法截取有限的元素

employees.stream()
    .filter(e -> "男".equals(e.getsex()))
    .sorted((e1, e2) -> e2.getage() - e1.getage())
    .limit(2)
    .foreach(e -> system.out.println(e));

打印出所有男员工的姓名,使用 , 分隔

map 将 stream 中所有元素的执行给定的函数后返回值组成新的 stream

string maleemployeesnames = employees.stream()
    .map(e -> e.getname())
    .collect(collectors.joining(","));
system.out.println(maleemployeesnames);

统计信息

intsummarystatistics, doublesummarystatistics, longsummarystatistics 包含了 stream 中的汇总数据。

intsummarystatistics stat = employees.stream()
    .maptoint(employee::getage).summarystatistics();
system.out.println("员工总数:" + stat.getcount());
system.out.println("最高年龄:" + stat.getmax());
system.out.println("最小年龄:" + stat.getmin());
system.out.println("平均年龄:" + stat.getaverage());

总结

lambda 表达式确实可以减少很多代码,能提高生产力,当然也有弊端,就是复杂的表达式可读性会比较差,也可能是还不是很习惯的缘故吧,如果习惯了,相信会喜欢上的。凡事都有两面性,就看我们如何去平衡这其中的利弊了,尤其是在一个团队中。

以上就是对java8 javalambda 的资料整理,后续继续补充相关资料谢谢大家对本站的支持!

如您对本文有疑问或者有任何想说的,请点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网