一、Lambda表达式
1.什么是Lambda表达式
Lambda表达式,也可称为闭包。它是 Java 8 引入的一种新特性,它可以以更简洁的方式定义匿名函数。Lambda 表达式的主要作用是在需要函数对象的地方提供一种更简洁、更易读的语法。
在之前的Java版本中,如果想要传递一个代码块(函数)作为参数,通常需要通过匿名内部类来实现,这样的代码往往显得冗长,不易读。Lambda表达式的引入使得我们可以更直接地传递函数式代码,使代码更加简洁、清晰。
我们可以把Lambda表达式理解为一段可以传递的代码(将代码像数据一样进行传递)。Lambda允许把函数作为一个方法的参数,使用Lambda表达式可以写出更简洁、更灵活的代码,而其作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
Lambda表达式的基本结构是:
(参数列表) -> 表达式或代码块
其中,参数列表定义了传入的参数,箭头 -> 后面是具体的表达式或代码块。Lambda表达式的类型是一个函数式接口,这个接口只需要一个抽象方法需要被实现。
使用Lambda表达式以更简洁的方式定义匿名函数。Lambda 表达式的主要作用是在需要函数对象的地方提供一种更简洁、更易读的语法。
在Java 8里面,所有的Lambda的类型都是一个接口,而Lambda表达式本身,也就是”那段代码“,需要是这个接口的实现。
把“一块代码”赋给一个Java变量,移除一些没用的声明
2.函数式接口
这种只有一个接口函数需要被实现的接口类型,我们叫它”函数式接口“。
为了避免后来的人在这个接口中增加接口函数导致其有多个接口函数需要被实现,变成"非函数接口”,我们可以在这个上面加上一个声明@FunctionalInterface
, 这样别人就无法在里面添加新的接口函数了。
3. lambda表达式的使用
public class Test01 {
public static void main(String[] args) {
ZuanHuoQuan zuanhuoquan =(cat, times) -> System.out.println("有一只猫" + cat + "钻了" + times + "次火圈");
zuanhuoquan.zuan("小财",3);
}
}
interface ZuanHuoQuan{
void zuan(String cat, Integer times);
}
以上代码定义了一个函数式接口 ZuanHuoQuan
,它只有一个抽象方法 zuan
,接受一个字符串和一个整数作为参数。然后使用Lambda表达式来实现了这个接口,相当于定义了一个可以接受两个参数并输出结果的函数。通过这种方式可以更简洁地传递代码逻辑,不再需要使用繁琐的匿名内部类。
4.lambda进阶(结合forEach stream …)
Lambda 表达式的使用和应用场景可以分为以下几个方面:
函数式接口的实现: Lambda 表达式主要用于实现函数式接口(只有一个抽象方法的接口),可以将 Lambda 表达式赋值给这个接口的变量。
1、使用函数式接口
首先,让我们来看看函数式接口的用法。在 Java 8 中,我们不再需要显式地定义诸如 NameChecker
和 Executor
这样的函数式接口。Java 8 的 java.util.function
包中已经包含了许多预定义的函数式接口,其中一些与我们之前定义的接口非常相似,如 Predicate
和 Consumer
。让我们使用这些接口来简化代码。
Predicate 的使用
Predicate
是一个判断型的函数式接口,它有一个用于判断的抽象方法 test(T t)
。我们可以利用 Predicate
来进行集合数据的筛选。
Consumer 的使用
Consumer
是一个消费型的函数式接口,它有一个用于消费的抽象方法 accept(T t)
,接收一个泛型参数,不返回任何值。我们可以利用 Consumer
来对集合中的元素进行操作,而不需要再使用传统的循环。
- Consumer接口是一个消费型的接口,只要实现它的accept方法,就能作为消费者来输出信息。
- lambda、方法引用都可以是一个Consumer类型,因此他们可以作为forEach的参数,用来协助Stream输出信息。
2、使用 forEach() 取代 for 循环
集合操作: Lambda 表达式可以极大地简化集合的遍历和操作。通过在集合的 forEach
方法中传入 Lambda 表达式,可以对集合中的每个元素执行相应的操作。它可以取代传统的 for
循环,使代码更加简洁。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.forEach(number -> System.out.println(number));
通过利用预定义的函数式接口如 Predicate
和 Consumer
,我们可以更加直观地进行条件判断和数据操作。同时,forEach()
方法的引入让遍历集合变得更加简洁和优雅。
public class Test02 {
public static void main(String[] args) {
fix(Arrays.asList(
new Student("唐","悠悠",22),
new Student("曾","小贤",21),
new Student("张","伟",23)
),
student -> student.getLastName().contains("小"),
student -> System.out.println(student.getFistName() + student.getLastName())
);
}
public static void fix(List<Student> students, Predicate<Student> checker, Consumer<Student> executor){
students.forEach(student -> {
if (checker.test(student)) {
executor.accept(student);
}
});
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class Student{
private String fistName;
private String lastName;
private int age;
}
代码示例很好地展示了使用Lambda表达式以及Java 8的函数式接口的实际应用。
代码中使用了fix
方法来筛选学生信息并执行相应的操作。在fix
方法中,使用了Predicate<Student>
来进行学生信息的筛选,使用Consumer<Student>
来执行对筛选后的学生信息的操作。
具体来说,Predicate<Student>
用于检查学生的姓名是否包含关键字,如果包含则返回true
,否则返回false
。Consumer<Student>
则用于输出符合条件的学生信息。
3、使用 Stream API 简化操作
在 Java 8 中,可以使用 Stream API 来处理集合数据。Stream 是一个对数据序列进行一系列操作的抽象。通过链式操作,我们可以将多个操作串联在一起,从而实现更加简洁的代码。
import java.util.List;
public class StreamExample {
public static void main(String[] args) {
List<Student> students = List.of(
new Student("唐","悠悠",22),
new Student("曾","小贤",21),
new Student("张","伟",23)
);
students.stream()
.filter(student -> student.getLastName().contains("小"))
.forEach(student -> System.out.println(student.getFistName() + student.getLastName()));
}
}
class Student {
private String fistName;
private String lastName;
private int age;
// Constructor and getters
}
在上面的代码中,我们使用了 Stream 的 filter()
方法来筛选出姓名包含"小"的学生,然后使用 forEach()
方法将结果打印出来。这种链式的操作方式使代码更具可读性。
4、使用 Method Reference 简化 Lambda
方法引用的概念
方法引用(Method Reference)是 Java 8 提供的一个强大特性,它允许我们通过已经存在的方法来替代 Lambda 表达式。这种方式不仅简化了代码,还提高了可读性。
(关于方法引用 Method Reference 详细内容见目录[二、方法引用])
import java.util.List;
public class MethodReferenceExample {
public static void main(String[] args) {
List<String> fruits = List.of("Apple", "Banana", "Orange", "Mango");
fruits.forEach(System.out::println);
}
}
在上述代码中,我们使用了方法引用 System.out::println
来替代 Lambda 表达式,直接将元素打印到控制台。这种方式更加简洁,同时减少了冗余代码。
结合 Stream 和 Method Reference
使用 Stream API 和方法引用可以将代码进一步简化,增加可读性。
import java.util.List;
public class StreamAndMethodReference {
public static void main(String[] args) {
List<String> fruits = List.of("Apple", "Banana", "Orange", "Mango");
fruits.stream()
.forEach(System.out::println);
}
}
在这个例子中,我们将集合数据转换为 Stream,然后使用方法引用 System.out::println
将每个元素打印到控制台。通过结合这两个特性,我们可以以更少的代码实现相同的功能,同时保持代码的可读性和简洁性。
总结
Java 8 的 Stream API 和方法引用为我们提供了更加简洁、易读的编程方式。通过使用 Stream API 处理集合数据,我们可以链式地组合多个操作,提高了代码的表达力。而方法引用则可以替代部分 Lambda 表达式,减少了冗余代码。将这两个特性结合起来,可以编写出更加精炼和可维护的代码。
总结起来,Lambda表达式的使用主要有以下优点:
- 更简洁的代码:可以通过更紧凑的语法来表达函数式代码,减少样板代码。
- 更清晰的逻辑:Lambda表达式的形式更接近于自然语言,更容易理解代码的含义。
- 更高效的代码编写:在一些简单的场景下,可以避免定义多余的类和方法,直接传递逻辑代码。
需要注意的是,虽然Lambda表达式带来了很多好处,但并不是在所有情况下都适用,有些场景仍然需要传统的方法和类的定义。
二、方法引用
方法引用是 Java 8 中与 Lambda 表达式一同引入的一种特性,它提供了一种更简洁的方式来调用已经存在的方法。方法引用可以看作是 Lambda 表达式的一种简化写法,用于直接调用已经存在的方法。
1、方法引用的注意事项:
在使用方法引用时,有一些注意事项需要考虑,包括传入参数的个数、方法的签名和适用条件等。下面是一些方法引用的注意事项:
-
方法引用的格式: 方法引用的格式是
对象::方法
,或者是类名::静态方法
,或者是类名::实例方法
。对于对象的实例方法引用,方法引用的左边是对象,右边是方法名。对于类的静态方法引用,直接使用类名和方法名即可。 -
参数个数与类型必须匹配: 方法引用中传递的参数个数和类型必须与被调用方法的参数个数和类型匹配。例如,如果方法引用需要传递一个参数,那么被调用的方法也必须接受一个参数。
-
方法签名必须一致: 方法引用的方法签名必须与目标方法的方法签名一致。方法签名包括方法名、参数个数和参数类型。
-
实例方法引用的目标对象: 对于实例方法引用,方法引用的左边是对象。这个对象会成为方法调用的目标对象,也就是方法的调用者。
-
构造方法引用: 构造方法引用是一种特殊的方法引用,用于创建对象。构造方法引用的格式是
类名::new
。 -
数组引用: 数组引用是一种特殊的方法引用,用于创建数组。例如,
int[]::new
表示创建一个整数数组。 -
注意方法重载: 如果存在方法重载,编译器可能无法确定方法引用指向哪个方法。在这种情况下,需要根据方法引用的上下文进行适当的解析。
-
Lambda 表达式与方法引用的选择: 在一些情况下,使用 Lambda 表达式比方法引用更清晰。方法引用通常适用于简单的方法调用,而 Lambda 表达式适用于需要执行复杂操作的情况。
-
方法引用与可读性: 方法引用可以使代码更加简洁,但有时可能会降低代码的可读性,特别是对于不太熟悉方法引用的人来说。在选择使用方法引用还是 Lambda 表达式时,要考虑代码的可读性和维护性。
2、方法引用的几种情况:
-
静态方法引用: 引用静态方法。
// Lambda 表达式 Function<Integer, String> intToString = num -> Integer.toString(num); // 静态方法引用 Function<Integer, String> intToString = Integer::toString;
-
实例方法引用: 引用某个对象的实例方法。
// Lambda 表达式 BiPredicate<String, String> startsWith = (str, prefix) -> str.startsWith(prefix); // 实例方法引用 BiPredicate<String, String> startsWith = String::startsWith;
-
对象方法引用: 引用特定对象的实例方法。
List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); names.forEach(System.out::println); // 等同于 names.forEach(name -> System.out.println(name));
System.out::println
是一个典型的方法引用示例,它引用了System.out
对象的println
方法。这个方法引用可以与 Java 中的 Lambda 表达式结合使用,用于简化输出语句。下面是一个使用方法引用的例子,演示了如何通过方法引用将一些字符串输出到控制台:
import java.util.Arrays; import java.util.List; public class MethodReferenceExample { public static void main(String[] args) { List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); // 使用 Lambda 表达式 names.forEach(name -> System.out.println(name)); // 使用方法引用 names.forEach(System.out::println); } }
在上面的代码中,
names.forEach(System.out::println)
实际上是引用了System.out
对象的println
方法,将列表中的每个元素逐一输出到控制台。这种方法引用的方式更加简洁,不需要编写循环或者逐一调用输出方法,代码更加精炼,易于阅读和维护。方法引用使得代码更加函数式,同时也避免了不必要的重复。
-
构造方法引用: 引用构造方法来创建对象。
// Lambda 表达式 Supplier<List<String>> listSupplier = () -> new ArrayList<>(); // 构造方法引用 Supplier<List<String>> listSupplier = ArrayList::new;
在使用方法引用时,可以直接引用已经存在的方法,而不需要编写冗长的 Lambda 表达式,从而使代码更加简洁和易读。方法引用和 Lambda 表达式一起使用,可以进一步简化代码,提高代码的可读性和可维护性。