一、lambda 表达式介绍

lambda 表达式是 Java 8 的一个新特性,可以取代大部分的匿名内部类,简化了匿名委托的使用,让你让代码更加简洁,优雅。

比较官方的定义是这样的:

lambda 表达式是一个可传递的代码块(或者匿名函数),可以在以后执行一次或多次。

这个匿名函数没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。lambda 表达式也可称为闭包

在 Java 中传递一个代码段并不容易,你不能直接传递代码段。Java 是一种面向对象语言,所以必须构造一个对象,这个对象的类需要有一个方法包含所需的代码。接下来就看看 Java 是怎么来处理代码块的。

二、lambda 表达式的语法

ava 中有一个 Comparator 接口用来排序。这是 Java 8 以前的代码形式:

public class LengthComparator implements Comparator<String> {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
}
String[] strArr = new String[]{"abcde", "qwer"};
Arrays.sort(strArr, new LengthComparator());

我们需要定义一个实现了 Comparator 接口的类,并实现里面的 compare() 方法,然后把这个类当做参数传给 sort 方法。

而我们使用 lambda 表达式就可以这样来写:

Arrays.sort(strArr, (String a, String b) -> a.length() - b.length());

其中的 (String a, String b) -> a.length() - b.length() 就是一个 lambda 表达式。

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)

再看一个例子加深理解:

// 用匿名内部类的方式来创建线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello world");
}
}); // 使用Lambda来创建线程
new Thread(() -> System.out.println("hello world"));

  

注意:

如果一个 lambda 表达式只在某些分支返回一个值,而另外一些分支不返回值,这是不合法的。
例如,(int x) -> { if (x>= 0) return 1; } 就不合法

三、函数式接口

Java 中有很多封装代码块的接口,比如上面的 ComparatorActionListener,lambda 表达式与这些接口是兼容的。

但并不是所有的接口都可以使用 lambda 表达式来实现。lambda 规定接口中只能有一个需要被实现的方法(只包含一个抽象方法),不是规定接口中只能有一个方法。 这种接口就称为函数式接口

Java 8 中有另一个新特性:default, 被 default 修饰的方法会有默认实现,不是必须被实现的方法,所以不影响 Lambda 表达式的使用。

上面的 ComparatorActionListener,包括 Runnable 就是只有一个需要被实现的方法的接口。即函数式接口

@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used...
*/
public abstract void run();
}

我们来观察下 Runnable 接口,接口上面有一个注解 @FunctionalInterface

通过观察 @FunctionalInterface 这个注解的源码,可以知道这个注解有以下特点:

  1. 该注解只能标记在有且仅有一个抽象方法的接口上。

  2. JDK8 接口中的静态方法和默认方法,都不算是抽象方法。

  3. 接口默认继承 java.lang.Object,所以如果接口显示声明覆盖了 Object 中方法,那么也不算抽象方法。

  4. 该注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。

我们再来看一下 Comparator 接口的源码:

@FunctionalInterface
public interface Comparator<T> { int compare(T o1, T o2); boolean equals(Object obj); default Comparator<T> reversed() {
return Collections.reverseOrder(this);
} default Comparator<T> thenComparing(Comparator<? super T> other) {
Objects.requireNonNull(other);
return (Comparator<T> & Serializable) (c1, c2) -> {
int res = compare(c1, c2);
return (res != 0) ? res : other.compare(c1, c2);
};
} public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) {
return new Comparators.NullComparator<>(true, comparator);
}
}

  

这里只贴出来了部分代码,可以看到排除掉接口的中的静态方法、默认方法和覆盖的 Object 中的方法之后,就剩下一个抽象方法 int compare(T o1, T o2); 符合 lambda 函数式接口的规范。

JDK 中提供一些其他的函数接口如下:

四、方法引用

Java awt 包中有一个 Timer 类,作用是经过一段时间就执行一次。 用 lambda 表达式来处理:

Timer timer = new Timer(1000, event -> System.out.println("this time is " + new Date()));

这里面的 lambda 表达式可以这样表示:

Timer timer = new Timer(1000, System.out::println);

表达式 System.out::println 就是一个方法引用(method reference),它指示编译器生成一个函数式接口的实例,覆盖这个接口的抽象方法来调用给定的方法。

方法引用需要用 ::运算符分隔方法名与对象或类名。主要有3种情况:

1. object::instanceMethod
2. Class::instanceMethod
3. Class::staticMethod

具体解释这里不再叙述,有兴趣的可以看看《Java 核心技术卷1》。

注意:

只有当 lambda 表达式的体只调用一个方法而不做其他操作时,才能把 lambda 表达式重写为方法引用

五、构造器引用

构造器引用与方法引用很类似,只不过方法名 new。例如,Person::new 是 Person 构造器的一个引用。

假如有一个字符串列表。可以把它转换为一个 Person 对象数组,为此要在各个字符串上调用构造器:

ArrayList<String> names = ... ;
Stream<Persion> stream = names.stream().map(Person::new);
List<Person> people = stream.collect(Collectors.toList());

其中,map 方法会为各个列表元素调用 Person(String) 构造器。这里的 streammap 会在下一篇博客中学习,这篇暂不讨论。

六、变量作用域

看下面这个例子:

public static void repeatMessage(String text, int delay){
ActionListener listener = event ->
{
System.out.printLn(text);
};
new Timer(delay, listener).start();
} // 调用
repeatMessage("Hello", 1000);

可以看到, lambda 表达式可以捕获外围作用域中变量的值。在 Java 中,要确保所捕获的值是明确定义的,这里有一个重要的限制。在 lambda 表达式中,只能引用值不会改变的变量。这是为了保证并发执行过程的安全。

lambda 表达式中捕获的变量必须实际上是事实最终变量。就是这个变量初始化之后就不会再为它赋新值。

参考资料:

  1.《Java 核心技术卷1-2》
  2.https://juejin.cn/post/7035997996936331294

 
 
 
 
 
 

Java8新特性: lambda 表达式介绍的更多相关文章

  1. Java8新特性-Lambda表达式是什么?

    目录 前言 匿名内部类 函数式接口 和 Lambda表达式语法 实现函数式接口并使用Lambda表达式: 所以Lambda表达式是什么? 实战应用 总结 前言 Java8新特性-Lambda表达式,好 ...

  2. 乐字节-Java8新特性-Lambda表达式

    上一篇文章我们了解了Java8新特性-接口默认方法,接下来我们聊一聊Java8新特性之Lambda表达式. Lambda表达式(也称为闭包),它允许我们将函数当成参数传递给某个方法,或者把代码本身当作 ...

  3. java8新特性——Lambda表达式

    上文中简单介绍了一下java8得一些新特性,与优点,也是为本次学习java8新特性制定一个学习的方向,后面几篇会根据上文中得新特性一一展开学习.本文就从java8新特性中比较重要的Lambda表达式开 ...

  4. Java8新特性 - Lambda表达式 - 基本知识

    A lambda expression is an unnamed block of code (or an unnamed function) with a list of formal param ...

  5. java8新特性-lambda表达式和stream API的简单使用

    一.为什么使用lambda Lambda 是一个 匿名函数,我们可以把 Lambda表达式理解为是 一段可以传递的代码(将代码像数据一样进行传递).可以写出更简洁.更灵活的代码.作为一种更紧凑的代码风 ...

  6. Java8 新特性lambda表达式(一)初始

    本篇参考Richard Warburton的 java8 Lambdas :Functional Programming for the Masses 学习lambda表达式之前,需要知道什么是函数式 ...

  7. Java8新特性——Lambda 表达式

    Lambda 表达式 ​ ​ ​ ​ ​ ​ ​ ​ Lambda 表达式的实质属于函数式编程. ​ ​ ​ ​ ​ ​ ​ ​ 语法格式为:(parameters) -> expression ...

  8. Java8新特性-Lambda表达式

    1.  什么是Lambda表达式? Lambda表达式就是可以把函数作为参数传递,或者说把代码作为数据传递给函数. 2. Lambda表达式的语法格式 基本语法格式如下: 基本语法下多个变体的说明: ...

  9. Java8新特性——lambda表达式.(案例:词频统计)

    需求:读入一个文本文件,确定所有单词的使用频率并从高到低排序,打印出所有单词及其频率的排序列表 先用传统方法解: package cn._1.wordfrequency; import java.ut ...

  10. Java8新特性Lambda表达式

    List<RoleDO> allRoles = roleService.list(); //获取角色中备注不是app的集合List<RoleDO> webRoles = all ...

随机推荐

  1. Hibernate,一对一外键单向 记录。Timestamp 的一个坑。

    首先是2张表 表A: 表B: 其中表B中的FormBaseId对应表A中的SubjectID. 数据库中没有设置外键关系. 下面是2个对应的实体 package questionnaire.model ...

  2. js获取手机联网状态

    window.addEventListener("offline", function() { alert('offline') }, false); window.addEven ...

  3. 【Android测试】【第一节】ADB——初识和用法

    ◆版权声明:本文出自胖喵~的博客,转载必须注明出处.  转载请注明出处:http://www.cnblogs.com/by-dream/p/4630046.html 写在前面的话 感觉自己进入Andr ...

  4. LINQ to SQL的一些简单用法

    static void Main(string[] args) { var personList = new List<Person> { new Person() { PersonID= ...

  5. C++ 全局构造函数调用的顺序

    C++的全局类和静态类的构造函数是在main函数之前调用的.但是,不同的类的构造函数以什么顺序调用呢? 对于g++编译器来说,这个顺序是由链接时,文件顺序决定的. 我们用一个例子来说明这一点. 我们有 ...

  6. Ubuntu16.04删除客人会话

    1.按下 Ctrl+Alt+T - 打开终端 2.输入以下指令: sudo gedit /etc/lightdm/lightdm.conf 3.源代码之后添加如下代码,然后保存.关闭,重启电脑即可. ...

  7. 在foxmail和outlook中设置QQ邮箱、gmail邮箱、新浪邮箱、微软邮箱、网易邮箱等的方法

    怎么用邮件客户端如outlook和foxmail来设置各种邮箱 很多人平时都是在网页上面收发邮件,这个很简单,不用其他的设置,不过在客户端上设置收发邮件还是很不错的,今天就来讲讲各种邮箱在outloo ...

  8. 每天一个linux命令(3):ls命令

    1.命令简介 ls(list 列出目录内容)命令用来列出显示指定目录里的文件及文件夹清单,缺省下ls用来打印出当前目录的清单.通过ls 命令不仅可以查看linux文件夹包含的文件,而且可以查看文件权限 ...

  9. 【BZOJ2427】【HAOI2010】软件安装

    无力吐槽…… 原题: 现在我们的手头有N个软件,对于一个软件i,它要占用Wi的磁盘空间,它的价值为Vi.我们希望从中选择一些软件安装到一台磁盘容量为M计算机上,使得这些软件的价值尽可能大(即Vi的和最 ...

  10. uva146-枚举,排列

    题意: 输入最多150个小写字母,在字典序增大的方向,求下一个排列是什么. 模拟枚举,最后一个字符是递归的最后一层(n层),那么把它弹出栈(还剩n-1层),如果n-1层的字符比第n层小,说明把n层的字 ...