Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
使用 Lambda 表达式可以使代码变的更加简洁紧凑。
lambda 表达式的语法格式如下:
(parameters) -> expression
或
(parameters) ->{ statements; }
以下是lambda表达式的重要特征:
Lambda 表达式的简单例子:
// 1. 不需要参数,返回值为 5
() - >5
//2. 接收一个参数(数字类型),返回其2倍的值
x - > 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x,y)-> x-y
// 4. 接收2个int型整数,返回他们的和
(int x,int y)-> x+y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s)->System.out.println(s)
在 Java8Tester.java 文件输入以下代码:
public class Java8Tester {
public static void main(String args[]){
Java8Tester tester = new Java8Tester();
// 类型声明
MathOperation addition = (int a, int b) -> a + b;
// 不用类型声明
MathOperation subtraction = (a, b) -> a - b;
// 大括号中的返回语句
MathOperation multiplication = (int a, int b) -> { return a * b; };
// 没有大括号及返回语句
MathOperation division = (int a, int b) -> a / b;
System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
System.out.println("10 / 5 = " + tester.operate(10, 5, division));
// 不用括号
GreetingService greetService1 = message ->
System.out.println("Hello " + message);
// 用括号
GreetingService greetService2 = (message) ->
System.out.println("Hello " + message);
greetService1.sayMessage("Runoob");
greetService2.sayMessage("Google");
}
interface MathOperation {
int operation(int a, int b);
}
interface GreetingService {
void sayMessage(String message);
}
private int operate(int a, int b, MathOperation mathOperation){
return mathOperation.operation(a, b);
}
}
执行以上脚本,输出结果为:
$ javac Java8Tester.java
$ java Java8Tester
10 + 5 = 15
10 - 5 = 5
10 x 5 = 50
10 / 5 = 2
Hello Runoob
Hello Google
使用 Lambda 表达式需要注意以下两点:
Lambda 表达式主要用来定义行内执行的方法类型接口,例如,一个简单方法接口。在上面例子中,我们使用各种类型的Lambda表达式来定义MathOperation接口的方法。然后我们定义了sayMessage的执行。
Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。
变量作用域
lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。
在 Java8Tester.java 文件输入以下代码:
public class Java8Tester {
final static String salutation = "Hello! ";
public static void main(String args[]){
GreetingService greetService1 = message ->
System.out.println(salutation + message);
greetService1.sayMessage("Runoob");
}
interface GreetingService {
void sayMessage(String message);
}
}
执行以上脚本,输出结果为:
$ javac Java8Tester.java
$ java Java8Tester
Hello! Runoob
我们也可以直接在 lambda 表达式中访问外层的局部变量:
Java8Tester.java 文件
public class Java8Tester {
public static void main(String args[]) {
final int num = 1;
Converter s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2); // 输出结果为 3
}
public interface Converter {
void convert(int i);
}
}
lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)
int num = 1;
Converter 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 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。
String first = "";
Comparator comparator = (first, second) -> Integer.compare(first.length(), second.length()); //编译会出错
System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
这里还有另外一种写法
System.out.println("10+5=" + addition.operation(10, 5));
interface MathOperation { int operation(int a, int b); }
此接口要求必须是函数式接口,如果其中有两个方法则lambda表达式会编译错误。但java8的新特性如许实现如下写法:
interface MathOperation {
int operation(int a, int b);
default int addition(int a, int b){
return a+b;
}
}
lambdas 实现 Runnable 接口
下面是使用 lambdas 来实现 Runnable 接口的示例:
// 1.1使用匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello world !");
}
}).start();
// 1.2使用 lambda expression
new Thread(() -> System.out.println("Hello world !")).start();
// 2.1使用匿名内部类
Runnable race1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello world !");
}
};
// 2.2使用 lambda expression
Runnable race2 = () -> System.out.println("Hello world !");
// 直接调用 run 方法(没开新线程哦!)
race1.run();
race2.run();
Runnable 的 lambda 表达式,使用块格式,将五行代码转换成单行语句。 接下来,在下一节中我们将使用 lambdas 对集合进行排序。
使用 Lambdas 排序集合
在 Java 中,Comparator 类被用来排序集合。 在下面的例子中,我们将根据球员的 name、
surname、name 长度以及最后一个字母。 和前面的示例一样,先使用匿名内部类来排序,然后再使用 lambda 表达式精简我们的代码。
在第一个例子中,我们将根据name来排序list。 使用旧的方式,代码如下所示:
String[] players = {"Rafael Nadal", "Novak Djokovic",
"Stanislas Wawrinka", "David Ferrer",
"Roger Federer", "Andy Murray",
"Tomas Berdych", "Juan Martin Del Potro",
"Richard Gasquet", "John Isner"};
// 1.1 使用匿名内部类根据 name 排序 players
Arrays.sort(players, new Comparator() {
@Override
public int compare(String s1, String s2) {
return (s1.compareTo(s2));
}
});
使用 lambdas,可以通过下面的代码实现同样的功能:
// 1.2 使用 lambda expression 排序 players
Comparator sortByName = (String s1, String s2) -> (s1.compareTo(s2));
Arrays.sort(players, sortByName);
// 1.3 也可以采用如下形式:
Arrays.sort(players, (String s1, String s2) -> (s1.compareTo(s2)));
其他的排序如下所示。 和上面的示例一样,代码分别通过匿名内部类和一些lambda表达式来实现Comparator :
// 1.1 使用匿名内部类根据 surname 排序 players
Arrays.sort(players, new Comparator() {
@Override
public int compare(String s1, String s2) {
return (s1.substring(s1.indexOf(" ")).compareTo(s2.substring(s2.indexOf(" "))));
}
});
// 1.2 使用 lambda expression 排序,根据 surname
Comparator sortBySurname = (String s1, String s2) ->
( s1.substring(s1.indexOf(" ")).compareTo( s2.substring(s2.indexOf(" ")) ) );
Arrays.sort(players, sortBySurname);
// 1.3 或者这样,怀疑原作者是不是想错了,括号好多...
Arrays.sort(players, (String s1, String s2) ->
( s1.substring(s1.indexOf(" ")).compareTo( s2.substring(s2.indexOf(" ")) ) )
);
// 2.1 使用匿名内部类根据 name lenght 排序 players
Arrays.sort(players, new Comparator() {
@Override
public int compare(String s1, String s2) {
return (s1.length() - s2.length());
}
});
// 2.2 使用 lambda expression 排序,根据 name lenght
Comparator sortByNameLenght = (String s1, String s2) -> (s1.length() - s2.length());
Arrays.sort(players, sortByNameLenght);
// 2.3 or this
Arrays.sort(players, (String s1, String s2) -> (s1.length() - s2.length()));
// 3.1 使用匿名内部类排序 players, 根据最后一个字母
Arrays.sort(players, new Comparator() {
@Override
public int compare(String s1, String s2) {
return (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1));
}
});
// 3.2 使用 lambda expression 排序,根据最后一个字母
Comparator sortByLastLetter =
(String s1, String s2) ->
(s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1));
Arrays.sort(players, sortByLastLetter);
// 3.3 or this
Arrays.sort(players, (String s1, String s2) -> (s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1)));
new 父类或接口() {
重写的方法;
}
比如在使用匿名内部类实现多线程的代码中。
因为 Thread 构造方法中需要传递 Runnable 接口类型的参数,所以我们不得不 new Runnable。
因为要重写 Runnable 中的 run 方法,所以不得不写了public void run。
整个匿名内部类中最关键的东西是方法,方法中最关键的有前中后三点。
前:参数。
中:方法体
后:返回值
最好的情况是只关注匿名内部类中最核心的这些内容(方法参数,方法体,返回值)如果使用Lambda表达式,可以只关注最核心的内容,Lambda 表达式是匿名内部类的简化写法。
Lambda 属于函数式编程思想。
面向对象思想:怎么做。
函数式编程思想:做什么。
public class Demo01Inner {
public static void main(String[] args) {
//使用匿名内部类的方式实现多线程。
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行了");
}
}).start();
//使用Lambda表达式实现多线程
new Thread(() -> System.out.println(Thread.currentThread().getName() + "执行了")).start();
}
}
匿名内部类可以省去单独创建 .java 文件的操作。但是匿名内部类也是有缺点的,写法太冗余了,里面有很多多余的部分。
匿名内部类也有简化写法,匿名内部类的简化写法是 Lambda 表达式匿名内部类中最关键的内容是方法的参数,方法体,以及返回值,而在 Lambda 表达式中,关注的就是这三个关键的东西。
Lambda 表达式的标准格式:
(参数类型 参数名) -> {
方法体;
return 返回值;
}
格式解释:
小括号中的参数和之前方法的参数写法一样,可以写任意个参数,如果多个参数,要使用逗号隔开。
-> 是一个运算符,表示指向性动作。
大括号中的方法体以及 return 返回值的写法和之前方法的大括号中的写法一样。
Lambda 表达式是函数式编程思想。
函数式编程:可推导,就是可省略。
因为在 Thread 构造方法中需要 Runnable 类型的参数,所以可以省略 new Runnable。
因为 Runnable 中的只有一个抽象方法 run,所以重写的必然是这个 run 方法,所以可以省略 run 方法的声明部分(public void run)
Lambda 表达式可以省略面向对象中的一些条条框框,让我们只关注最核心的内容。
Lambda 使用条件
Lambda 表达式的使用前提:
public class Demo05BeforeLambda {
//使用接口当做参数
public static void method(MyInterface m) {//m = s -> System.out.println(s)
m.printStr("HELLO");
}
public static void main(String[] args) {
//使用接口当做参数,然后传递Lambda表达式。
//method(s -> System.out.println(s));
//使用匿名内部类方式创建对象
/*
MyInterface m = new MyInterface() {
@Override
public void printStr(String str) {
System.out.println(str);
}
};
*/
MyInterface m = str -> System.out.println(str);
m.printStr("Hello");
}
}
λ表达式本质上是一个匿名方法。让我们来看下面这个例子:
public int add(int x, int y) {
return x + y;
}
转成λ表达式后是这个样子:
(int x, int y) -> x + y;
参数类型也可以省略,Java编译器会根据上下文推断出来:
(x, y) -> x + y; //返回两数之和
或者
(x, y) -> { return x + y; } //显式指明返回值
可见λ表达式由三部分组成:参数列表,箭头(->),以及一个表达式或语句块。
下面这个例子里的λ表达式没有参数,也没有返回值(相当于一个方法接受0个参数,返回void,其实就是Runnable里run方法的一个实现):
() -> { System.out.println("Hello Lambda!"); }
如果只有一个参数且可以被Java推断出类型,那么参数列表的括号也可以省略:
list -> { return list.size(); }
λ表达式可以被当做是一个Object(注意措辞)。λ表达式的类型,叫做“目标类型(target type)”。λ表达式的目标类型是“函数接口(functional interface)”,这是Java8新引入的概念。它的定义是:一个接口,如果只有一个显式声明的抽象方法,那么它就是一个函数接口。一般用@FunctionalInterface标注出来(也可以不标)。举例如下:
@FunctionalInterface
public interface Runnable { void run(); }
public interface Callable { V call() throws Exception; }
public interface ActionListener { void actionPerformed(ActionEvent e); }
public interface Comparator { int compare(T o1, T o2); boolean equals(Object obj); }
注意最后这个Comparator接口。它里面声明了两个方法,貌似不符合函数接口的定义,但它的确是函数接口。这是因为equals方法是Object的,所有的接口都会声明Object的public方法——虽然大多是隐式的。所以,Comparator显式的声明了equals不影响它依然是个函数接口。
你可以用一个λ表达式为一个函数接口赋值:
Runnable r1 = () -> {System.out.println("Hello Lambda!");};
然后再赋值给一个Object:
Object obj = r1;
但却不能这样干:
Object obj = () -> {System.out.println("Hello Lambda!");}; // ERROR! Object is not a functional interface!
必须显式的转型成一个函数接口才可以:
Object o = (Runnable) () -> { System.out.println("hi"); }; // correct
一个λ表达式只有在转型成一个函数接口后才能被当做Object使用。所以下面这句也不能编译:
System.out.println( () -> {} ); //错误! 目标类型不明
必须先转型:
System.out.println( (Runnable)() -> {} ); // 正确
假设你自己写了一个函数接口,长的跟Runnable一模一样:
@FunctionalInterface
public interface MyRunnable {
public void run();
}
那么
Runnable r1 = () -> {System.out.println("Hello Lambda!");};
MyRunnable2 r2 = () -> {System.out.println("Hello Lambda!");};
都是正确的写法。这说明一个λ表达式可以有多个目标类型(函数接口),只要函数匹配成功即可。
但需注意一个λ表达式必须至少有一个目标类型。
JDK预定义了很多函数接口以避免用户重复定义。最典型的是Function:
@FunctionalInterface
public interface Function {
R apply(T t);
}
这个接口代表一个函数,接受一个T类型的参数,并返回一个R类型的返回值。
另一个预定义函数接口叫做Consumer,跟Function的唯一不同是它没有返回值。
@FunctionalInterface
public interface Consumer {
void accept(T t);
}
还有一个Predicate,用来判断某项条件是否满足。经常用来进行筛滤操作:
@FunctionalInterface
public interface Predicate {
boolean test(T t);
}
综上所述,一个λ表达式其实就是定义了一个匿名方法,只不过这个方法必须符合至少一个函数接口。
λ表达式主要用于替换以前广泛使用的内部匿名类,各种回调,比如事件响应器、传入Thread类的Runnable等。看下面的例子:
Thread oldSchool = new Thread( new Runnable () {
@Override
public void run() {
System.out.println("This is from an anonymous class.");
}
} );
Thread gaoDuanDaQiShangDangCi = new Thread( () -> {
System.out.println("This is from an anonymous method (lambda exp).");
} );
注意第二个线程里的λ表达式,你并不需要显式地把它转成一个Runnable,因为Java能根据上下文自动推断出来:一个Thread的构造函数接受一个Runnable参数,而传入的λ表达式正好符合其run()函数,所以Java编译器推断它为Runnable。
从形式上看,λ表达式只是为你节省了几行代码。但将λ表达式引入Java的动机并不仅仅为此。Java8有一个短期目标和一个长期目标。短期目标是:配合“集合类批处理操作”的内部迭代和并行处理(下面将要讲到);长期目标是将Java向函数式编程语言这个方向引导(并不是要完全变成一门函数式编程语言,只是让它有更多的函数式编程语言的特性),也正是由于这个原因,Oracle并没有简单地使用内部类去实现λ表达式,而是使用了一种更动态、更灵活、易于将来扩展和改变的策略(invokedynamic)。
上文提到了集合类的批处理操作。这是Java8的另一个重要特性,它与λ表达式的配合使用乃是Java8的最主要特性。集合类的批处理操作API的目的是实现集合类的“内部迭代”,并期望充分利用现代多核CPU进行并行计算。
Java8之前集合类的迭代(Iteration)都是外部的,即客户代码。而内部迭代意味着改由Java类库来进行迭代,而不是客户代码。例如: