Java8新特性之01Lambda表达式
Java函数式编程
面向对象思想需要关注用什么对象完成什么事情,而函数式编程思想就类似于我们数学中的函数,它主要关注的是对数据进行了什么操作。
函数式编程的优势
- 易于使用并发编程,大数据量下,集合处理效率高:可以使用并行流,自动使用多线程方式处理。
- 代码可读性高
- 消灭嵌套地狱
例如,要查询未成年作家的评分在70分以上的作家的书籍,由于洋流影响所以作家和书籍可能出现重复,需要进行去重。
如果按照原来的写法,需要这么写:
List<Author> authors = new ArrayList<>();
List<Book> bookList = new ArrayList<>();
Set<Book> uniqueBookValues = new HashSet<>();
Set<Author> uniqueAuthorValues = new HashSet<>();
for (Author author : authors) {
if (uniqueAuthorValues.add(author)) {
if (author.getAge() < 18) {
List<Book> books = author.getBooks();
for (Book book : books) {
if (book.getScore() > 70) {
if (uniqueBookValues.add(book)) {
bookList.add(book);
}
}
}
}
}
}
System.out.println(bookList);
但如果改用函数式编程,代码会变得非常简单:
List<Author> authors = new ArrayList<>();
List<Book> collect = authors.stream()
.distinct()
.filter(author -> author.getAge() < 18)
.map(author -> author.getBooks())
.flatMap(Collection::stream)
.filter(book -> book.getScore() > 70)
.distinct()
.collect(Collectors.*toList*());
System.out.println(collect);
01 Lambda表达式
概述
Lambda是IDK8中一个语法糖,他可以对 某些匿名内部类(这类匿名内部类是一个接口,并且接口中只有一个抽象方法需要被重写) 的写法进行简化。
它是函数式编程思想的一个重要体现,让我们不用关注是什么对象,而是更关注我们对数据进行了什么操作,即只关注匿名内部类中抽象方法的 **参数(数据)**及 函数体(操作)。
核心原则:可推导可省略。参数类型如果可以推导出来,就可以省略。
基本格式:
(参数列表) -> {代码}
使用案例
例一
在创建线程并启动时,可以使用匿名内部类的写法:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("传统方式创建线程");
}
}).start();
因为使用到了匿名内部类 Runnable 接口,并且该接口中只有 run() 一个抽象方法,所以可以使用Lambda的格式对其进行修改,如下所示:
// () -> {...} 中 () 相当于 run() 方法的参数列表, {...} 相当于 run() 方法的函数体
new Thread(() -> {
System.*out*.println("Lambda方式创建线程");
}).start();
例二
例如,有如下一个方法:
public static int calculateNum(IntBinaryOperator op) {
int a = 10;
int b = 20;
return op.applyAsInt(a, b);
}
该方法接收一个 IntBinaryOperator 类型的参数,并调用该参数中的 applyAsInt() 方法。IntBinaryOperator 是 JDK 内置的一个接口,接口源码如下:
@FunctionalInterface
public interface IntBinaryOperator {
int applyAsInt(int left, int right);
}
可以看到该接口只有一个抽象方法,因此在调用 calculateNum() 方法时,传入自定义的 IntBinaryOperator 实现时,就可以使用 lambda 表达式来实现。
calculateNum((a, b) -> {
return a + b;
});
甚至可以进一步简化:
calculateNum((a, b) -> a + b);
Tips 在 IDEA 中,可以通过 Alt + Enter 快捷键来进行匿名内部类和Lambda表达式格式之间的转换。 在匿名内部类格式下,使用快捷键可以将其转为Lambda表达式; 在Lambda格式下,使用快捷键可以将其转为匿名内部类格式;
例三
现有方法定义如下:
public static void printNum(IntPredicate p) {
int[] a = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
for (int i : a) {
if (p.test(i)) {
System.out.println(i);
}
}
}
其中 IntPredicate 是一个接口,接口源码如下:
@FunctionalInterface
public interface IntPredicate {
boolean test(int value);
default IntPredicate and(IntPredicate other) {
Objects.requireNonNull(other);
return (value) -> test(value) && other.test(value);
}
default IntPredicate negate() {
return (value) -> !test(value);
}
default IntPredicate or(IntPredicate other) {
Objects.requireNonNull(other);
return (value) -> test(value) || other.test(value);
}
}
先使用匿名内部类的方式调用该方法:
printNum(new IntPredicate() {
@Override
public boolean test(int value) {
return value % 2 == 0;
}
});
该接口中虽然不止一个方法,但是只有一个 test() 抽象方法必须被重写,其他方法是默认方法不需要被重写,因此,也可以使用 Lambda 表达式实现:
printNum((int value) -> {
return value % 2 == 0;
});
// 进一步简写
printNum(value -> value % 2 == 0)
省略规则
在例二和例三的代码中,给出了两种 Lambda 表达式,一种是单纯省略方法名和接口名,保留完整参数列表和方法体;第二种是省略参数类型,以及return 关键字等。
省略的规则如下:
- 参数类型可以省略;
- 方法只有一个参数时,参数列表的括号可以省略;
- 方法体只有一句代码时,大括号、return关键字和唯一一句代码的分号可以省略;