java-doc-lambda

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;

// ERROR
// num++;

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()