Java8新特性(1):Lambda表达式
Lambda表达式可以理解为一种匿名函数:没有名称,但有参数列表、函数主体、返回类型。它是行为参数化的一种实现,行为参数化是指将不同的行为作为参数传递给方法,方法的所具备的能力取决于它接收的行为参数。使用Lambda表达式使我们不必为这些行为去编写一堆固定的实现类就能应对不断变化的需求,在1.8之前,可以使用匿名内部类的方式达到相同的效果,只是相对于Lambda表达式来说,匿名内部类的方式会显得啰嗦。
函数式接口
Lambda表达式的使用依赖于函数式接口,只有在接受函数式接口的地方才可以使用Lambda表达式。函数式接口是指只声明了一个抽象方法的接口,可以有多个静态方法、默认方法,如下所示:
@FunctionalInterface
public interface Calculation {
int calculate(int a, int b);
}
@FunctionalInterface注解表示被标注的接口将被设计成一个函数式接口,不是必须的,它主要是在接口违背函数式接口原则时会出现编译错误。比如修改Calculation接口,再添加一个抽象方法就会出现Multiple non-overriding abstract methods found in interface com.cf.demo.lambda.Calculation编译错误:
//编译错误:Multiple non-overriding abstract methods found in interface com.cf.demo.lambda.Calculation
@FunctionalInterface
public interface Calculation {
int calculate(int a, int b);
int calculate2(int a, int b);
}
注意:Object类的方法是特例,即使接口声明了多个Object类的方法,也不会被算入“只声明了一个抽象方法”的计数中。如下Calculation接口是正确的函数式接口:
@FunctionalInterface
public interface Calculation {
int calculate(int a, int b);
boolean equals(Object obj);
}
Java8提供了一些常用的函数式接口,位于java.util.function包下,并且为了避免装箱操作,还提供了和基本类型对应的接口,我们在实际使用时,可以优先使用这些内置的函数式接口。当然在某些情况我们也需要使用自定义的函数式接口,如需要在Lambda表达式中抛异常时,这种情况就需要自定义一个函数式接口,并声明异常。
Lambda表达式语法
Lambda表达式由参数列表、箭头(Lambda操作符)、Lambda主体三个部分组成。Lambda表达式的参数列表要和函数式接口的参数列表相对应,Lambda主体的返回值也要和函数式接口的返回类型相对应。现在有如下doArithmetic方法,接收两个整型参数以及一个Calculation,doArithmetic方法的行为是由传递的Calculation来决定的,我们可以调用该方法传递不同的Calculation来完成不同的计算:
public static int doArithmetic(int a, int b, Calculation calculation){
return calculation.calculate(a, b);
}
现在要计算两个数的乘积,用内部类的方式:
int result = doArithmetic(3, 2, new Calculation() {
@Override
public int calculate(int a, int b) {
return a * b;
}
});
System.out.println(result);//6
用Lambda表达式的方式要更简洁:
int result = doArithmetic(3, 2, (int a, int b) -> a * b);
System.out.println(result);//6
(int a, int b)是Lambda表达式的参数列表部分,只有一个参数的时候可以省略小括号,这里有多个参数,所以要保留小括号。参数类型可以省略,因为Java编译器能通过上下文推断出数据类型,无需显示的声明:
int result = doArithmetic(3, 2, (a, b) -> a * b);
System.out.println(result);//6
Lambda主体只有一个语句时,可以省略{}和return,(int a, int b) -> a * b)就是省略之后的写法,我们也可以使用完整的写法:
int result = doArithmetic(3, 2, (a, b) -> {
return a * b;
});
System.out.println(result);//6
当需要在Lambda表达式中使用‘外部局部变量’时,这个‘外部局部变量’默认是final的,‘外部局部变量’这里是指非Lambda表达式内部定义的局部变量。修改doArithmetic方法,添加一个‘外部局部变量’,为乘积赋个初始值,以下代码是编译不通过的:
int initialValue = 1;
int result = doArithmetic(3, 2, (a, b) -> a * b + initialValue);
initialValue = 2;//Variable used in lambda expression should be final or effectively final
System.out.println(result);
方法引用
方法引用可以对‘某种特殊情况’下的Lambda表达式进行简化,‘某种特殊情况’是指Lambda表达式要做的事情别的方法实现了,那我们就可以直接使用这个方法,然后像Lambda表达式一样传递即可。方法引用的语法为目标引用放在分隔符::前,方法的名称放在后面,目标引用可以是类名也可以是对象名。通过以下三个例子来介绍方法引用的三种使用方法,新增Arithmetic类,Arithmetic类包含一个静态方法和一个实例方法:
public class Arithmetic {
public static int multiply(int a, int b){
return a * b;
}
public int add(int a, int b){
return a + b;
}
}
1.指向静态方法的方法引用
int multiplyResult = doArithmetic(3, 2, Arithmetic::multiply);
System.out.println(multiplyResult);//6
2.指向现有对象的实例方法的方法引用
Arithmetic arithmetic = new Arithmetic();
int addResult = doArithmetic(3, 2, arithmetic::add);
System.out.println(addResult);//5
3.指向任意类型实例方法的方法引用,这种情况有个特点,就是在引用一个对象的方法,而这个对象本身是Lambda的一个参数。比如现在需要实现比较两个数的大小,首先修改calculate方法参数类型为包装类型Integer:
@FunctionalInterface
public interface Calculation {
int calculate(Integer a, Integer b);
}
比较a和b的大小可以这样写:
int result = doArithmetic(3, 2, Integer::compareTo);//Integer::compareTo等于a.compareTo(b)
System.out.println(result);//1
构造函数引用
对于一个现有构造函数,可以使用它的名称和new来创建一个它的引用: ClassName::new。再使用构造函数引用时,需要调用的构造器参数列表要和函数式接口的抽象方法的参数要一致。举个例子,现在添加了两个生成String对象的方法:
public static String generateString(Supplier<String> supplier) {
return supplier.get();
}
public static String generateString(String value, Function<String, String> function) {
return function.apply(value);
}
分别使用构造函数引用:
String result = generateString(String::new);//调用String()构造方法
System.out.println(result);
result = generateString("hello Lambda", String::new);//调用String(String original)构造方法
System.out.println(result);