lambda
lambda表达式本质上就是一个匿名方法。但是,这个方法不是独立执行的,而是用于实现由函数接口定义的另一个方法。因此,lambda表达式会导致产生一个匿名类。lambda表达式也常被称为闭包。
函数式接口是仅包含一个抽象方法的接口。一般来说,这个方法指明了接口的目标用途。因此,函数式接口通常表示单个动作。此外,函数式接口定义了lambda表达式的目标类型。特别注意:lambda表达式只能用于其模板类型已被指定的上下文中。另外,函数式接口有时候被称为SAM类型,意思是单抽象方法(Single Abstract Method)。
Java定义了两种lambda体。一种包含单独一个表达式,另一种包含一个代码块。
函数式接口
函数式接口是仅指定了一个抽象方法的接口。lambda表达式不是独立执行的,而是构成了一个函数式接口定义的抽象方法的实现,该函数式接口定义了它的模板类型。接口,只有在定义了lambda表达式的目标类型的上下文中,才能使用该表达式。当把一个lambda表达式赋给一个函数式接口引用时,就创建了这样的上下文。其他模板类型上下文包括变量初始化、return语句和方法参数等。
当模板类型上下文中出现lambda表达式时,会自动创建实现了函数式接口的一个类的实例,函数式接口声明的抽象方法的行为由lambda表达式定义。当通过目标调用该方法时,就会执行lambda表达式。因此lambda表达式提供了一种将代码片段转换为对象的方法。
为了在目标类型上下文中使用lambda表达式,抽象方法的类型和lambda表达式地类型必须兼容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| interface NumericTest { boolean test(int n); }
public class LearnLambda1 { public static void main(String args[]) { NumericTest isEven = (n) -> (n % 2)==0; if(isEven.test(10)) { System.out.println("10 is even"); } NumericTest isEven2 = (n)->{ if(n % 2 == 0) { return true; } else { return false; } }; if(isEven2.test(8)) { System.out.println("8 is even"); } } }
|
泛型函数式接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| interface SomeFunc<T> { T func(T t); }
public class LearnLambda2 { public static void main(String args[]) { SomeFunc<String> reverse = (str) -> { String result = ""; int i; for(i = str.length() -1; i >= 0; i--) { result += str.charAt(i); } return result; }; System.out.println(reverse.func("Hello world!")); } }
|
作为参数传递lambda表达式
为了将lambda表达式作为参数传递,接受lambda表达式的参数的类型必须是与该lambda表达式兼容的函数式接口的类型。
lambda表达式抛出异常
lambda表达式可以抛出异常。但是,如果抛出经检查的异常,该异常就必须与函数式接口的抽象方法的throws子句列出的异常兼容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| interface DoubleNumericArrayFunc { double func(double[] n) throws EmptyArrayException; }
class EmptyArrayException extends Exception { EmptyArrayException() { super("Array empty"); } }
public class LearnLambda3 { public static void main(String args[]) throws EmptyArrayException { double values[] = {1.0, 2.0, 3.0}; DoubleNumericArrayFunc average = (n)->{ double sum = 0; if(n.length == 0) throw new EmptyArrayException(); for(int i = 0; i < n.length; ++i) { sum += n[i]; } return sum / n.length; }; System.out.println("The average is " + average.func(values)); } }
|
lambda表达式和变量捕获
在lambda表达式中,可以访问其外层作用域内定义的变量。例如,lambda表达式使用其外层类定义的实例或静态变量。lambda表达式也可以隐式地访问this变量,该变量引用labmda表达式外层类的调用实例。因此,lambda表达式可以获取或设置其外层类的实例或静态变量的值,以及调用其外层类定义的方法。
但是,当lambda表达式使用其外层作用域内定义的局部变量时,会产生一种特殊的情况,称为变量捕获。在这种情况下,lambda表达式只能使用实质上final的局部变量。实质上final的变量时指在第一次赋值以后,值不再发生变量的变量。没有必要显示地将这种变量声明为final,不过那样做也不是错误(外层作用域的this参数自动是实质上final变量,lambda表达式没有自己的this参数)。
lambda表达式不能修改外层作用域内的局部变量,修改局部变量会移除实质上的final状态,从而使捕获该变量变得不合法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| interface MyFunc { int func(int n); }
public class LearnLambda4 { public static void main(String args[]) { int num = 10; MyFunc mylambda = (n) -> { int v = num + n; return v; }; } }
|
方法引用
有一个重要的特性与lambda表达式相关,叫做方法引用。方法引用提供了一种引用而不执行方法的方式。这种特性与lambda表达式相关,因为它也需要由兼容的函数式接口构成的目标类型上下文。
- 静态方法引用
- 实例方法引用
- 泛型方法引用
- 构造函数引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| interface StringFunc { String func(String n); }
class MyStringOps { static String StrTest(String str) { return str; } String StrTest2(String str) { return str; } }
public class LearnLambda5 { public static void main(String args[]) { String str = "lambda test"; MyStringOps ops = new MyStringOps(); StringFunc func1 = MyStringOps::StrTest; StringFunc func2 = ops::StrTest2; System.out.println(func1.func(str)); System.out.println(func2.func(str)); } }
|
预定义的函数式接口
JDK8中包含了新包java.util.function,其中提供了一些预定义的函数式接口。
接口 |
用途 |
UnaryOperator |
对类型为T的对象应用一元运算,并返回结果。结果的类型也是T。包含的方法名为apply()。 |
BinaryOperator |
对类型为T的两个对象应用操作,并返回结果。结果的类型也是T。包含的方法名为apply()。 |
Consumer |
对类型为T的对象应用操作。包含的方法名为accept() |
Supllier |
返回类型为T的对象。包含的方法名为get() |
Function<T,R> |
对类型为T的对象应用操作,并返回结果。结果是类型为R的对象。包含的方法名为apply() |
Predicate |
确定类型为T的对象是否满足某种约束,并返回指出结果的布尔值。包含的方法名为test() |