认识 JDK8 新特性之 Lambda 表达式

说来挺惭愧的,JDK8 已经出来这么多年,在实际项目中却很少真正使用 Lambda 表达式。其实工作中的项目很早就从开发、测试、生产,全面使用 JDK8+Tomcat8 了。

所以看来是时候要重新认识下这个奇怪的东西。

没错,当第一次看到 Lambda 表达式时,我就认为这是个奇怪的东西,->写法让我感觉这是在写其它语言,说好的一切皆对象呢?

表达式语法

这是 Lambda 表达式的基本语法:

1
2
3
4
5
6
(Type1 param1, Type2 param2, ..., TypeN paramN) -> {
statment1;
statment2;
//......
return statmentM;
}

参数列表是一个逗号分隔的形式参数列表,这些参数与功能接口中单一方法的形式参数相对应。指定参数类型是可选项;如果未指定参数类型,将从上下文推断。参数列表必须用括号括起来,但当指定的单一参数不带参数类型时除外;指定单一形式参数时可以不带括号。如果功能接口方法不指定任何形式参数,则必须指定空括号。

语句块必须包含在大括号内,除非语句块是一个方法调用语句。

举个简单例子

对于无参无返回值,可以有如下写法:

1
2
3
4
5
6
7
public interface TestIntf {
public void sayHello();
}

// -> 前的一对小括号和后面的一对大括号都不能去掉。
TestIntf intf = () -> {System.out.println("HelloWorld!!!");};
intf.sayHello();

对于有参有返回值,可以有如下写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public interface TestIntf {
public String sayHello(String word);
}

// 完整写法
TestIntf intf = (String word) -> {return "Hello" + word;};

// 参数 word 的类型可以推断时,可以去掉类型定义:
TestIntf intf = (word) -> {return "Hello" + word;};

// 甚至当参数只有一个时,可以去掉左边的小括号:
TestIntf intf = word -> {return "Hello" + word;};

// 右侧的方法体只有一个表达式时,可以去掉大括号,甚至连 return 都可以不要:
TestIntf intf = word -> "Hello" + word;

变量作用域

Lambda 表达式内部可以访问外部的变量,但需要注意:

局部变量无论是在 Lambda 表达式主体中声明,还是在封闭作用域中声明,使用之前都必须先初始化。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
interface TestIntf {
public String sayHello(String word);
}

public class App {
public static void main(String[] args) {
String str;
TestIntf intf = (String word) -> {
System.out.println(str); // 编译失败,提示 str 变量未被初始化。
return "Hello" + word;
};
}
}

Lambda 表达式不会定义新的作用域;Lambda 表达式的作用域与封闭作用域相同。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
interface TestIntf {
public String sayHello(String word);
}

public class App {
public static void main(String[] args) {
String str = "word";
TestIntf intf = (String word) -> {
String str = ""; // 编译失败,提示 str 变量重复定义了。
return "Hello " + word;
};
}
}

而且也不能修改外部变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
interface TestIntf {
public String sayHello(String word);
}

public class App {
public static void main(String[] args) {
String str = "word";
TestIntf intf = (String word) -> {
str = "word!!!"; // 编译报错:Local variable str defined in an enclosing scope must be final or effectively final
return "Hello" + word;
};
}
}

当然,引用变量是可以通过内部方法达到数据修改的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface TestIntf {
public String sayHello(StringBuilder word);
}

public class App {
public static void main(String[] args) {
StringBuilder str = new StringBuilder("word");
TestIntf intf = (StringBuilder word) -> {
word.append("!!!");
return "Hello" + word;
};
System.out.println(intf.sayHello(str)); // Hello word!!!
System.out.println(str); // word!!!
}
}

Lambda 表达式中的 this

在 Lambda 表达式中 this 引用的不是 Lambda 表达式对象,而是声明它的外部对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
interface TestIntf {
public String sayHello();
}

public class App {

public void test() {
TestIntf intf = () -> "Hello" + this.getClass().getSimpleName();
System.out.println(intf.sayHello());
}

public static void main(String[] args) {
App app = new App();
app.test(); // Hello App
}
}

参考文章

https://www.oracle.com/technetwork/cn/articles/java/lambda-1984522-zhs.html