lambda表达式在jdk1.8中被加入进来,给古老的java语言注入了新鲜的活力。先说句题外话,我看了一些早期spark(大数据计算框架)的书,当时spark支持scala,java和python,其中,spark的很多api(如map,flatmap,filter等)在scala和python中都可以使用lambda表达式轻松搞定,很简洁,无奈的java使用匿名内部类,冗长的包名,复杂的嵌套,最内侧的方法可能只是一个简单的表达式,看着实在是别扭。自从java8支持了lambda表达式以后,不仅使java增加了新的语言特性,还因此新增了一些其他api库,如并行编程和stram api等。本篇博客主要讨论lambda表达式的语法和使用,关于并行编程和stream api会在以后的博客中专门讨论。
在java中使用lambda表达式的话,需要注意两点:一个是lambda表达式本身的语法,另一个是函数式接口(functional interface)。
java8新增了一个操作符->
,称为lambda操作符或箭头操作符,它将lambda表达式分为两部分,箭头之前的是参数,箭头之后的是动作。例如,
(Integer n1,Integer n2) -> n1 + n2
这个就相当于
Integer method(Integer n1, Integer n2) {
return n1 + n2;
}
参数的类型可以省略,因为可以推断出来。
(n1,n2) -> n1 + n2
如果只有一个参数的话,圆括号也可以省略,像这样
n1 -> n1 * n1
如果没有参数的话,箭头前面的圆括号是不能省略的,例如:
() -> 5; //固定返回5
箭头符号右侧是动作,除了我们看到的这种单行的形式之外,还可以声明一个代码段作为动作,像下面这样
n -> {
return n * n;
}
这个代码段可以写的很长很长。如果有返回值的话,需要使用return
语句返回。
在java中,lambda表达式一定要结合functional interface来使用,functional interface是指一个只包含一个抽象方法的接口。下面来看一个例子。这是一个functional interface:
public interface MyInterface {
int doSomething(int number);
}
然后,声明一个此类型的lambda表达式
MyInterface myInterface = n -> n + 1;
int number = myInterface.doSomething(5);
可以理解为,lambda表达式从语法上简化了匿名内部类,但底层的实现好像都一样。再来看一个两个参数的例子。
public interface MyInterface2 {
int doSomething(int num1, int num2);
}
下面来声明一个该类型的lambda表达式
MyInterface2 myInterface2 = (n1, n2) -> n1 + n2;
number = myInterface2.doSomething(5, 9);
输出的结果是14。
我还可以使用一个更加复杂的lambda表达式
myInterface2 = (n1, n2) -> {
int max = 0;
if (n1 > max) {
max = n1;
}
if (n2 > max) {
max = n2;
}
return max;
};
这个lambda表达式返回n1,n2和0中较大的一个。
myInterface2.doSomething(10, 20); // return 20
myInterface2.doSomething(-10, -20); // return 0
说到lambda表达式,有一个话题是绕不开的,就是闭包。但这个问题在java中被简化了好多。这个问题可以分为两种情况来讨论:
1. lambda表达式可以访问到所在的类中定义的字段(filed),也可以修改这个字段。
2. lambda表达式可以访问到外层代码块(enclosing scope)中定义的本地变量(local varable),但不能修改他们,并且,如果一个本地变量在lambda表达式中被读取的话,这个变量必须是final或事实上final(变量赋值以后就不能再任何地方再修改了)。
看下面的例子
public class App {
private int filed1 = 10;
void method1() {
int varable1 = 10;
MyInterface myInterface = n -> {
filed1 += 2; //可读取,可修改
int m = varable1; //可读取
//varable1 += 2; //不可修改
return 1;
};
//varable1 += 2; //已经在lambda表达式中被读取了,就是final了,不能被修改。
}
}
在这个例子中,filed1
是一个字段,所以在lambdda表达式中可读可写,而varable1
是一个本地变量,在lambda表达式中只能被读取而不能被修改,并且,一旦这个变量在lambda表达式中被读取了,那么在任何地方就不能被修改了。
lambda表达式的本质是一个匿名方法,但如果有一个方法的签名(参数列表和返回值)和functional interface的签名一样并且逻辑正好是你需要的,那么你可以使用方法引用的方式来将它赋值给你的functional interface,而无需再编写lambda表达式。方法引用是jdk1.8中被引入的新语法,它跟lambda表达式息息相关,从字面意思来看,方法引用指向一个方法,但不调用它。其实这个特性在很多编程语言中都已经支持了,java也终于支持了。像其它编程语言一样,加括号就是调用方法,不加括号就是引用方法。
方法引用可以引用四种类型的方法,分别是:静态方法,实例方法,泛型方法,构造方法,引种每种方法的语法有略有区别。下面分别看个例子。下面这个类包含三个方法,对应前三种类型
public class MyClass {
public int instanceMethod(int number) {
return number * 2;
}
public static int staticMethod(int number) {
return number * 2;
}
public int genericMethod(T param) {
return param.intValue() * 2;
}
}
方法引用的操作符是两个冒号`::`,这也是一个新的操作符。
引用静态方法的语法是ClassName::methodName
myInterface = MyClass::staticMethod;
myInterface.doSomething(5);
应用实例方法的语法是instance::methodName
MyClass myClass = new MyClass();
myInterface = myClass::instanceMethod;
myInterface.doSomething(5);
MyClass myClass = new MyClass();
myInterface = myClass::<Integer>genericMethod;
myInterface.doSomething(5);
引用构造方法的语法是ClassName::new
构造方法的返回值是它所在的类的类型。下面编写一个functional interface和一个构造函数。先来一个类和它的构造函数
public class Foo {
String msg1, msg2;
public Foo(String msg1, String msg2) {
this.msg1 = msg1;
this.msg2 = msg2;
}
}
在来一个functional interface
interface FooInterface {
Foo fooMethod(String m1, String m2);
}
这个里面的fooMethod的返回值是Foo,参数列表是两个String对象,与Foo类的构造函数的签名一致。下面是引用这个构造方法的方式
FooInterface fooInterface = Foo::new;
Foo fooObj = fooInterface.fooMethod("hello", "world");