Java Lambda 表达式使用

Java8 引入了 Lambda 表达式,使用 Lambda 表达式可以让代码更加简洁。Lambda 表达式其实也就是一个匿名函数,我们可以用它去代替匿名函数,我们先来看一个例子

Lambda 表达式语法

我们用接口 Runnable 举个例子

1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {

// 匿名函数
new Thread(new Runnable() {
public void run() {
System.out.println("anonymous function");
}
}).start();

// lambda 表达式
new Thread(() -> System.out.println("lambda")).start();
}

从上的例子可以看出我们使用 () -> {} 的代码块代替了整个匿名函数

Lambda 表达式的语法格式如下

  • ( parameters ) -> expression

  • ( parameters ) -> {statements;}

  • 可以不需要声明参数类型,编译器可以统一识别参数值。
  • 一个参数时可以不定义圆括号,但多个参数需要定义圆括号。
  • 如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值(如果接口方法返回类型为 void 则不用)。

Lambda 表达式的简单例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 无参数,返回值为 10
() -> 10

// 单参数(数字类型),返回其2倍的值
x -> 2 * x

// 多个参数(数字),并返回他们的差值
(x, y) -> x – y

// 声明参数类型,返回他们的和
(int x, int y) -> return x + y

// 声明参数类型,并在控制台打印,不返回任何值(接口方法返回类型为 void)
(String s) -> System.out.print(s)

Lambda 行为参数化和变量作用域

我们可以将 lambda 表达式作为参数传递给方法,也就是说允许把函数作为一个方法的参数。

创建一个 Calculator 的函数接口。 在 Calculator 中有一个称为 calculate 的方法,它接受两个 int 参数并返回一个 int 值。
在 engine 方法中,它接受函数接口 Calculator 作为参数。在主方法中,用不同的lambda表达式调用 engine 方法四次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class LambdaTest {

public static void main(String[] argv) {
engine((x, y) -> x + y);
engine((x, y) -> x * y);
engine((x, y) -> x / y);
engine((x, y) -> x % y);
}

private static void engine(Calculator calculator) {
int x = 2, y = 4;
int result = calculator.calculate(x, y);
System.out.println(result);
}
}

@FunctionalInterface
interface Calculator {
int calculate(int x, int y);
}

lambda 表达式不定义自己的范围。
如果我们在 lambda 中使用关键字 thissuperthis 代表着 lambda 表达式所被包含的类

变量作用域

lambda 表达式与其外部方法具有相同的范围。 lambda表达式不会创建自己的作用域。

lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。

1
2
3
4
5
6
7
8
9
public static void main(String args[]) {
final int num = 1;
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2); // 输出结果为 3
}

public interface Converter<T1, T2> {
void convert(int i);
}

lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)

1
2
3
4
5
int num = 1;  
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2);
num = 5;
// Local variable num defined in an enclosing scope must be final or effectively final

在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量

1
2
String first = "";  
Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length()); //编译会出错

函数式接口

函数式接口只是具有一个方法的接口,用作 lambda 表达式的类型。
通常会使用 @FunctionalInterface 注解来标注了函数式接口

在 java8 中为我们定义了很多常用的函数式接口,它们都放在java.util.function包下面,一般有以下常用的四大核心接口

函数式接口 参数类型 返回类型 描述
Consumer(消费型接口) T void 对类型为T的对象应用操作。void accept(T t)
Supplier(供给型接口) T 返回类型为T的对象。 T get();
Function<T, R>(函数型接口) T R 对类型为T的对象应用操作并返回R类型的对象。R apply(T t);
Predicate(断言型接口) T boolean 确定类型为T的对象是否满足约束。boolean test(T t);

方法引用

方法引用通过方法的名字来指向一个方法。

方法引用可以使语言的构造更紧凑简洁,减少冗余代码。

方法引用使用一对冒号 ::

构造函数引用

使用构造函数引用的语法是 ClassName::new

1
2
3
Function<String,String> func1  = str ->  new String(str);

Function<String,String> func2 = String::new;

静态方法引用

方法引用的一般语法是 Qualifier::MethodName

1
2
3
Function<Integer, String> func1  = x -> Integer.toBinaryString(x);

Function<Integer, String> func2 = Integer::toBinaryString;

实例方法引用

1
2
3
Supplier<Integer> supplier  = () ->  "www.w3cschool.cn".length(); 

Supplier<Integer> supplier1 = "www.w3cschool.cn"::length;

通用方法引用

1
2
3
Function<String[],List<String>> asList = Arrays::<String>asList;

System.out.println(asList.apply(new String[]{"a","b","c"}));