Lambda表达式(Lambda expressions)是Java 8中的一个新特性,用于函数式编程。为了快速创建一个接口的实现,一般我们会使用到匿名类(anonymous classes),比如:
public class Anonymous {
public interface TaskInterface {
void run();
void preRun();
}
public void createAnonymous(){
TaskInterface task = new TaskInterface() {
@Override
public void run() {
System.out.println("Task is running...");
}
@Override
public void preRun() {
System.out.println("Before the task is running...");
}
};
task.preRun();
task.run();
}
public static void main(String[] args) {
new Anonymous().createAnonymous();
}
}
对于仅有一个方法的接口,此时使用匿名类不是那么明智了,我们可以使用Lambda更简洁地实现,将函数作为参数传递给方法,比如:
首先定义一个函数式接口(functional interface),接口:
@FunctionalInterface
public interface TaskInterface {
public void run(String message);
}
创建一个类,调用该接口方法:
public class TaskExecuter {
public void execut(TaskInterface task) {
task.run("Task is running...");
}
}
将函数作为参数传递给方法执行:
public class Lambda {
public static void main(String[] args) {
new TaskExecuter().execut((String message) -> System.out.println(message));
}
}
运行Lambda类,输出:
Task is running...
函数式接口(functional interface)是仅有一个抽象方法的接口,但是可以有一个或多个默认方法或者静态方法。因为函数式接口仅有一个抽象方法,所以可以使用能够Lambda表达更加简洁的实现。
函数式接口很大程度区别于参数和返回值,你可以根据需求声明自己的接口,但Java 8也提供了现成,从参数和返回值可以判断这些是否合适自己:
接口 | 方法 | 适用条件 |
---|---|---|
Function |
R apply(T t); | 有参数和返回值 |
BiFunction |
R apply(T t, U u); | 有两个参数和返回值 |
BinaryOperator |
T apply(T t1, T t2); | 有两个参数和返回值,且他们的类型相同,继承自 BiFunction |
– | – | – |
Consumer |
void accept(T t); | 有参数无返回值 |
BiConsumer |
void accept(T t, U u); | 有两个参数无返回值 |
– | – | – |
Predicate |
boolean test(T t); | 有参数,且返回值是boolean类型 |
BiPredicate |
boolean test(T t, U u); | 有两个参数,且返回值是boolean类型 |
– | – | – |
Supplier |
R get(); | 有返回值但无参数 |
java.util.function包下还有很多其他函数式接口,都是各种返回值或者参数为基本类型的情景。
(String message) -> System.out.println(message)
(String m1,String m2) -> System.out.println(m1+m2)
如果参数只有一个,括号和参数类型可以省略;如果参数类型相同,可省略参数类型:
message-> System.out.println(message)
BiConsumer function = (y1,y2)->System.out.println(y1+y2);
在Lambda表达式中,可以访问作用域中的变量(方法中的参数,成员变量),但是Lambda表达式变量没有屏蔽(Shadowing)作用, 简单的说,Lambda表达式不能对作用域的变量进行重新赋值,以下代码会出现编译错误提示,因为x = 0;使得x不再是final的。
Local variable x defined in an enclosing scope must be final or effectively final
public class Lambda {
public void run(int x) {
Consumer<String> function = (String y) -> {
x = 0;
System.out.println("x=" + x);
System.out.println("y=" + y);
};
}
}
屏蔽(Shadowing)
public class ShadowTest {
public int x = 0;
class FirstLevel {
public int x = 1;
void methodInFirstLevel(int x) {
System.out.println("x = " + x);
System.out.println("this.x = " + this.x);
System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
}
}
public static void main(String... args) {
ShadowTest st = new ShadowTest();
ShadowTest.FirstLevel fl = st.new FirstLevel();
fl.methodInFirstLevel(23);
}
}
运行输出:
x = 23
this.x = 1
ShadowTest.this.x = 0
例子中定义了3个x变量:1)ShadowTest中的成员变量 2)内部类FirstLevel成员变量 3)methodInFirstLevel方法参数。
methodInFirstLevel方法参数中的变量会遮蔽掉内部类FirstLevel成员变量,因此挡在methodInFirstLevel方法中使用x时,实在使用方法参数。
为了使用内部类FirstLevel成员变量,应当:
System.out.println("this.x = " + this.x);
应当通过类名来使用作用域更大的成员变量,以此分辨他们的归属,比如在methodInFirstLevel方法中使用ShadowTest中的成员变量:
System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
在使用Lambda表达式中,很多时候我们仅仅是为了调用一个方法,比如前面使用的:
Consumer consumer = msg -> System.out.println(msg);
方法引用可以更加简洁的书写:
Consumer consumer = System.out::println;
方法引用(Method References)顾名思义就是调用一个方法,以此作为Lambda的实现体,至于能调用什么方法和怎么调用,一方面要遵循调用该方法的参数和返回参数应当与函数式接口的抽象方法保持一致,另一方面遵循语法规则:
类型 | 模型 | 例子 |
---|---|---|
引用一个静态方法 | ContainingClass::staticMethodName | System.out::println |
引用构造方法 | ClassName::new | ArrayList::new |
引用实例方法 | containingObject::instanceMethodName | String str = new String(); Function |
引用特定类型中的实例方法 | ContainingType::methodName |
引用特定类型中的实例方法比较绕,是引用函数式接口的抽象函数的第一个参数类型的实例方法,引用过程类似于引用一个静态方法,但是该方法实际上是实例方法。假设现在有接口
public interface Finder {
String find(String str, int start, int end);
}
此接口为了截取字符,可以使用Lambda这样实现:
Finder finder = String::substring;
等价于:
Finder finder = (String str, int start, int end) -> str.substring(start, end);
从上面例子可以看到,其实
Finder finder = String::substring;
的作用相当于调用了
(String str, int start, int end)
第一个参数的类型中的一个方法substring,并将其余的参数作为substring的参数。
另一个典型的例子是排序:
假设有一个类IdName,对多个该对象进行排序:
class IdName{
private String id;
private String name;
public IdName(String id,String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Arrays.asList(new IdName("He", "asd"), new IdName("QW", "Kl"), new IdName("OP", "GL")).stream()
.sorted(Comparator.comparing(idName -> idName.getId())).forEach(idName -> {
System.out.println(idName.getId());
});
例子中根据IdName的id进行排序Comparator.comparing(idName -> idName.getId())
,因为返回的结果为IdName.getId(),即调用参数的实例方法而且是直接返回的,因此可以使用方法引用调用该类的方法:
Arrays.asList(new IdName("He", "asd"), new IdName("QW", "Kl"), new IdName("OP", "GL")).stream()
.sorted(Comparator.comparing(IdName::getId)).forEach(idName -> {
System.out.println(idName.getId());
});
另外一个排序例子:
Arrays.asList("Hl", "WU", "Ao", "Uo", "Nu", "Ni")
.stream()
.sorted((s1, s2) -> s1.compareTo(s2))
.forEach(System.out::println);
Arrays.asList("Hl", "WU", "Ao", "Uo", "Nu", "Ni")
.stream()
.sorted(String::compareTo)
.forEach(System.out::println);
引用:
https://docs.oracle.com/javase/tutorial/java/javaOO/anonymousclasses.html
https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html
https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html
http://tutorials.jenkov.com/java/lambda-expressions.html#zero-parameter