文章目录
一、Lambda表达式:
“Lambda 表达式”(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction)。
Lambda表达式是Java SE 8中一个重要的新特性。允许你通过表达式来代替功能接口,其几乎解决了匿名内部类带来的所有问题。同时Lambda表达式还增强了集合库,Java SE 8添加了2个对集合数据进行批量操作的包:java.util.function 包以及java.util.stream 包。
虽然看着很先进,其实Lambda表达式的本质只是一个“语法糖”,由编译器推断并帮你转换包装为常规的代码,因此你可以使用更少的代码来实现同样的功能。
语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·兰丁发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。语法糖让程序更加简洁,有更高的可读性。
语法:(argument) -> (body)
简单示例:
1. 参数为String类型,返回值为int类型。Lambda 表达式没有return语句,因为已经隐含了return。
(String s) -> s.length()
2. 参数为Dog类型,返回值为Boolean类型。(判断狗的年龄是否大于18岁)
(Dog d) -> d.age() > 18
3. 参数为两个int,无返回值(void)。Lambda 表达式可以包含多行语句。
(int x, int y) -> {
System.out.print("和为:");
System.out.println(x + y);
}
4. 无传入参数,返回值为int。
() -> 66
总的来讲,lambda表达式的参数的类型和数量必须与函数式接口内的抽象方法的参数兼容;返回类型必须兼容;并且lambda表达式可能抛出的异常必须能被该方法接受。
语法细节:
①一个Lambda表达式可以有零个或多个参数。
②参数的类型既可以明确声明,也可以根据上下文来推断。
③所有参数需包含在圆括号内,参数之间用逗号相隔。
④空圆括号代表参数集为空。
⑤当只有一个参数,且其类型可推导时,圆括号()可省略。
⑥Lambda表达式的主体可包含零条或多条语句。
⑦当Lambda表达式的主体只有一条语句时,花括号{}可省略。匿名函数的返回类型与该主体表达式一致。
⑧如果 Lambda 表达式的主体包含一条以上语句,则表达式必须包含在花括号{}中(形成代码块)。匿名函数的返回类型与代码块的返回类型一致,若没有返回则为空。
⑨对于Lambda表达式中的多个参数,如果需要显示声明一个参数的类型,那么必须为所有的参数都声明类型。
作用域:
⑴访问局部变量:
①在 Lambda 表达式中可以直接访问外层的局部变量。
注意:此处的局部变量必须不可被后面的代码修改(即隐性的具有final的语义)。
原因:此时更改变量不是线程安全的。假设有一系列并发的任务,每个线程都会更新一个共享的计数器。
②在 Lambda 表达式中被引用的变量的值不可被更改。
③在 Lambda 表达式中不允许声明一个与局部变量同名的参数或者局部变量。
⑵访问对象字段(成员变量)与静态变量:
Lambda 表达式对于对象字段(成员变量)以及静态变量是即可读又可写的。
⑶访问接口的默认方法:
Lambda表达式中是无法访问到默认方法的。
⑷this的使用:
Lambda 表达式中使用 this 会引用创建该 Lambda 表达式的方法的 this 参数。
public class ThisTest {
public static void main(String[] args) {
ThisTest thisTest = new ThisTest();
thisTest.method();
}
// 定义一个线程,运行输出this指代
public void method() {
Runnable runnable = () -> {
System.out.println(this);
};
new Thread(runnable).start();
}
}
由结果可知,此处this指代的是ThisTest
Lambda表达式的Java实现:第一个就是Lambda表达式自身,第二个是函数式接口。
Lambda表达式本质上就是一个匿名方法。但是这个方法是不能独立执行的,而是用于实现由函数式接口定义的一个方法。因此,Lambda表达式会导致产生一个匿名类。
二、函数式接口:
函数式接口就是一个具有一个方法的普通接口。像这样的接口,可以被隐式转换为lambda表达式。
在实际使用过程中,函数式接口是容易出错的:如有某个人在接口定义中增加了另一个方法,这时,这个接口就不再是函数式的了,并且编译过程也会失败。
为了克服函数式接口的这种脆弱性并且能够明确声明接口作为函数式接口的意图,Java 8增加了一种特殊的注解@FunctionalInterface(Java 8中所有类库的已有接口都添加了@FunctionalInterface注解)。
注意:
⑴只包含一个抽象方法的接口,不是只有一个方法。也就是说函数式接口除了一个抽象方法外,还可以有默认方法和静态方法。
⑵函数式接口可以定义Object定义的任何公有方法,例如equals(),而不影响其作为“函数式接口”的状态。Object的公有方法被视为函数式接口的隐式成员,因为函数式接口的实例会默认自动实现它们。
正确的使用:
@FunctionalInterface
public interface MyFunctionalInterface {
public void doSomeThing();
}
错误的使用:
@FunctionalInterface
public interface MyFunctionalInterface {
public void doOneThing();
public void doAnotherThing();
}
三、方法引用:
Lambda 表达式允许我们定义一个匿名的方法,并将它作为 Functional interface 的一个实例。方法引用跟 Lambda 表达式很像,他们都需要一个目标类型,但是不同的是方法引用不提供方法的实现,它引用一个已经存在的类或者对象的方法。
方法引用使用 :: (双冒号操作符,double colon operator)来表示对某个类或对象的某个方法的引用。
简单示例:
static class Dog {
// 定义一个bark方法
static void bark() {
System.out.println("我是一条快乐的单身狗狗");
}
public static void main(String[] args) {
// 定义一个线程运行Dog的bark方法
new Thread(new Runnable() {
@Override
public void run() {
Dog.bark();
}
});
// 方式一:使用Lambda表达式
new Thread(() -> Dog.bark()).start();
// 方式二:使用方法引用
new Thread(Dog::bark).start();
}
}
方法引用的唯一用途是支持Lambda的简写。当Lambda表达式中只是执行一个方法调用时,不用Lambda表达式,直接通过方法引用的形式可读性更高一些。方法引用是一种更简洁易懂的Lambda表达式。
使用细节:
⑴静态方法引用:
和静态方法调用相比,只是把 . 换为 :: 即可。
Collections::max;
⑵实例方法引用:
①实例上的实例方法引用:
可以捕获this指针。
this::equals
②类型上的实例方法引用:
在泛型类或泛型方法中,也可以使用方法引用。
interface MyFunction<T> {
int function(T[] values, T value);
}
// 定义一个数组工具类
class MyArrayUtils {
// 定义一个工具类方法,计算匹配次数
public static <T> int countMatching(T[] values, T value) {
int count = 0;
for (int i = 0; i < values.length; i++) {
if (values[i] == value) {
count++;
}
}
return count;
}
}
class GenericMethodDemo {
public static <T> int test(MyFunction<T> myFunction, T[] values, T value) {
return myFunction.function(values, value);
}
public static void main(String[] args) {
Integer[] values = {1, 2, 3, 4, 2, 3, 4, 4, 5};
String[] strings = {"One", "Two", "Three", "Two"};
int count;
count = test(MyArrayUtils::<Integer>countMatching, values, 4);
System.out.println("values contains " + count + " 4s");
count = test(MyArrayUtils::<String>countMatching, strings, "Two");
System.out.println("strings contains " + count + " Twos");
}
}
⑶构造方法引用:
String::new
int[]::new