什么是Lambda表达式
Lambda表达式,是一种紧凑的、传递行为的方式.能够使代码产生更多的灵活性.
所以Oracle 在Java8添加了Lambda的支持
函数式的简单使用
我们先来看一个简单的例子
当我们需要创建一个临时的、可被Runner我们可能会这样编写1
2
3
4
5
6new Runnable() {
public void run() {
System.out.println("do some thing");
}
};
但在使用Lambda的情况下,可以这样进行编写1
Runnable runnable = () -> System.out.println("do some thing");
这是由于Runnable的接口定义中,有且仅有一个抽象方法1
2
3
4
5// @FunctionalInterface 仅是标注这个接口将会是一个函数式接口,编译时会进行检查,是否存在多个方法或者其他条件
public interface Runnable {
public abstract void run();
}
因为当接口中有且仅有一个抽象方法时,我们称其为函数式接口
函数式接口
我们可以在方法签名中使用函数式接口作为参数或者返回类型.
例如我们常常会做这样的事情,任务A和任务B周围都环绕着进行同一段冗余的代码(准备/清理)
假如现在你要读取一个文件中的一行,你可能会这样写1
2
3
4
5public static String processFile() throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {// 真正需要的是这行代码
return br.readLine();
}
}
但现在你的需求变成了,需要读取两行.如果将该方法复制一份,将会导致很多重复的代码
有了Lambda你可以将你想要的操作参数化1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public static String processFile(Function<BufferedReader, String> action) {
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {// 真正需要的是这行代码
return action.apply(br);
}
}
// 这样不管是想要读取一行
String oneLine = processFile(br -> {
try {
return br.readLine();
} catch (IOException ignore) {}
return "";
});
// 或者是想要读取两行 都能轻松实现
String twoLine = processFile((BufferedReader br) -> {
try {
return br.readLine() + br.readLine();
} catch (IOException ignore) {}
return "";
}));
自定义函数接口
当然上述的代码看起来有些别扭,这时因为自带的函数式接口的签名中因为没有定义将会抛出错误的类型,而br.readLine()会抛出错误,所以需要进行catch
不过为了美观,我们也能进行自定义函数式接口,大体上像Runnable一样1
2
3
4
public interface BufferedReaderProcessor {
String process(BufferedReader b) throws IOException;
}
然后将方法的函数签名改成1
2
3
4
5
6
7
8public static String processFile(BufferedReaderProcessor p) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) {
return p.process(br);
}
}
// 这样就可以直接使用一行进行表达了
String oneLine = processFile(br -> br.readLine());
String twoLines = processFile(br -> br.readLine() + br.readLine());
看了上述的示例后是否觉得似曾相识ヾ(•ω•`)o
其实lambda能和匿名内部类相互替换(当然还是要遵守仅有一个抽象方法)1
2
3
4
5
6String twoLines = processFile(new BufferedReaderProcessor() {
public String process(BufferedReader br) throws IOException {
return br.readLine() + br.readLine();
}
});
在Lambda中使用局部变量
如果你曾使用过匿名内部类,也许会遇到这样的情况,需要引用它所在方法里的变量。这时,需要将变量声明为final1
2
3
4
5
6final String name = getUserName();
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
System.out.println("hi " + name);
}
});
在Lambda虽然放松了这一限制,可以不使用final修饰变量,但是该变量实际上不能重复赋值,否则编译器将会报错1
2
3String name = getUserName();
name = formatUserName(name);
button.addActionListener(event -> System.out.println("hi " + name)); // 此处编译时将会报错
方法引用
当一个方法的签名符合一个使用了函数式接口的参数时,可以使用方法引用进行简写,使调用更加易懂和自然。
例如在上文的例子中1
2
3String oneLine = processFile(br -> br.readLine());
// 可以使用以下方式进行表示
String oneLine = processFile(BufferedReader::readLine);
lambda的复合使用
可以将几个Lambda组合一起形成复杂的语句进行使用
比较复合
- 逆序
1
inventory.sort(comparing(Apple::getWeight).reversed());
- 比较链
1
2
3
4
5inventory.sort(
comparing(Apple::getWeight)
.reversed() // 重量递减排序
.thenComparing(Apple::getCountry) //当一样重时根据国家排序
);
谓词复合
- 非
1
Predicate<Apple> notRedApple = redApple.negate();
- 且
1
Predicate<Apple> redAndHeavyApple = redApple.and(a -> a.getWeight() > 150);
- 或
1
2
3
4// 表示 (红苹果 且 重量大于150) 或 绿苹果
Predicate<Apple> redAndHeavyAppleOrGreen = redApple
.and(a -> a.getWeight() > 150)
.or(a -> "green".equals(a.getColor()));
函数复合
1 | Function<Integer, Integer> plus2 = x -> x + 2; |
一些技巧
以下是除了上述的能力之外拓展的一些小技巧
缓存
1 | // 其中的T可以是一些复杂对象 |
延迟求值
1 | // 假设当前有个方法a比较耗时 |
此时将会导致无论b方法中的条件是否满足,都需要对a进行调用.
此时就能使用lambda的延迟满足进行优化代码1
2
3
4
5
6
7
8// 修改b的签名
public void b(Supplier<String> s) {
if (some condition) {
System.out.println(s.get());
}
}
// 这样 仅在b满足条件时,才会回调方法 执行a方法
b(() -> "some thing" + a());
参考书籍(顺便推荐)
- 《Java8实战》
- 《Java8函数式编程》