简介
Java 8 引入的 Lambda 表达式是函数式编程在 Java 中的一个重要特性。Lambda 表达式允许你以更简洁的方式实现只有一个抽象方法的接口(即函数式接口)的匿名内部类。这使得 Java 代码更加简洁、易读和易维护。
函数式编程的概念
函数式编程(Functional Programming, FP)是一种编程范式,它强调将计算过程看作是函数之间的转换而不是状态的改变。函数式编程的核心思想是将计算机程序视为一系列数学函数的组合,通过函数来描述运算过程,并尽量不改变外界状态,从而避免多线程共享变量的问题。函数式编程的主要特性包括:
- 函数是一等公民:在函数式编程中,函数可以被当作参数传递给其他函数,也可以作为其他函数的返回值。
- 纯函数:纯函数是函数式编程中的核心概念,它指的是函数的输出仅由输入决定,不会对外部状态产生影响,也不会产生副作用。
- 不可变性:函数式编程通常使用不可变数据结构,这意味着数据一旦创建就不能被修改。
- 高阶函数:高阶函数是可以接受其他函数作为参数或返回一个函数的函数。
- 延迟计算:函数式编程支持延迟计算,即只有在需要的时候才进行计算,这可以避免不必要的计算。
- 递归(Recursion):使用递归而不是循环来实现重复的操作。
- 声明式编程(Declarative Programming):关注“做什么”而不是“怎么做”,与命令式编程(Imperative Programming)相对。
- 模式匹配(Pattern Matching):用于数据结构的解析和处理,如在 Haskell 或 Scala 中的模式匹配。
- 惰性评估(Lazy Evaluation):计算只有在需要时才执行,可以提高性能和内存效率。
- 并发编程(Concurrency):由于纯函数和不可变性的特性,函数式编程语言通常更容易处理并发和并行计算。
函数式编程的优点
函数式编程具有多个显著的优点,这些优点使得它在现代软件开发中越来越受到重视:
- 代码简洁性和可读性:函数式编程通过简洁的函数组合来表达复杂的逻辑,使得代码更加简洁易读。
- 模块化开发:函数式编程鼓励将程序拆分成多个小的函数,每个函数负责完成一个特定的任务,这有助于实现模块化开发,提高代码的可维护性。
- 可重用性:函数式编程中的函数可以被多次调用,提高了代码的可重用性,减少了代码重复。
- 易于测试和调试:由于函数式编程中的函数通常具有较小的依赖性和较少的副作用,因此更容易进行单元测试和调试。
- 并发安全性:由于函数式编程中的纯函数不会修改外部状态,因此可以更容易地实现并发编程,减少并发错误的发生。
- 支持数学和逻辑推导:函数式编程与数学和逻辑紧密相关,支持使用数学和逻辑推导来验证程序的正确性。
函数式接口
函数式接口是只有一个抽象方法的接口(可以有多个默认方法和静态方法)。@FunctionalInterface
注解是一个标记注解,用于表明接口是函数式接口,但这不是强制性的。如果接口不符合函数式接口的定义(即包含多个抽象方法),但使用了 @FunctionalInterface
注解,编译器会报错。
Lambda 表达式的优点和缺点
优点:
- 代码简洁,开发迅速
- 方便函数式编程
- 容易进行并行计算
- Java 引入 Lambda,改善了集合操作
缺点:
- 代码可读性变差
- 在非并行计算中,很多计算未必有传统 for 循环性能高
- 不容易进行调试
Lambda 表达式与接口实现的比较
-
传统接口实现方式
-
实现接口的类:
public interface MyFunctionalInterface { void doSomething(); } public class MyFunctionalInterfaceImpl implements MyFunctionalInterface { @Override public void doSomething() { System.out.println("Doing something!"); } }
-
匿名内部类:
MyFunctionalInterface myFunc = new MyFunctionalInterface() { @Override public void doSomething() { System.out.println("Doing something!"); } };
-
-
Lambda 表达式
Lambda 表达式的引入使得上述两种实现方式都变得更加简洁:
MyFunctionalInterface myFunc = () -> System.out.println("Doing something!");
-
测试
@Test
void stream() {
MyFunctionalInterfaceImpl myFunc = new MyFunctionalInterfaceImpl();
myFunc.doSomething();
MyFunctionalInterface myFunc1 = () -> System.out.println("Doing something2!");
myFunc1.doSomething();
MyFunctionalInterface myFunc2 = new MyFunctionalInterface() {
@Override
public void doSomething() {
System.out.println("Doing something3!");
}
};
myFunc2.doSomething();
}
1、Lambda 表达式的语法
1.1 Lambda 表达式提供了一种简洁的方式来表示匿名函数。其基本语法如下:
(参数列表) -> 表达式
(parameters) -> expression
或 (参数列表) -> { Lambda 体 }
(parameters) -> { statements; }
-
无参数:
Runnable runnable = () -> System.out.println("Hello, World!");
-
一个参数:
Function<String, Integer> stringLength = s -> s.length();
-
多个参数:
BinaryOperator<Integer> add = (a, b) -> a + b;
1.2 Java 7(使用匿名类)和 Java 8(使用 Lambda 表达式)之间参数形式的对比:
Java 7 (匿名类) | Java 8 (Lambda 表达式) |
---|---|
new Comparator<String>() { @Override public int compare(String s1, String s2) { return s1.compareTo(s2); } } | (s1, s2) -> s1.compareTo(s2) |
new Runnable() { @Override public void run() { System.out.println("Hello, World!"); } } | () -> System.out.println("Hello, World!") |
new Callable<String>() { @Override public String call() throws Exception { return "Hello, Lambda!"; } } | () -> "Hello, Lambda!" |
new Function<Integer, Integer>() { @Override public Integer apply(Integer t) { return t * t; } } | (t) -> t * t |
2、Lambda表达式规则
-
参数类型可以省略:如果 Lambda 表达式的参数类型可以从上下文中推断出来,那么可以省略参数类型,例如
(int a)
和(a)
效果相同。 -
单参数的小括号可以省略:如果 Lambda 表达式只有一个参数,那么可以省略这个参数周围的小括号,例如
(a)
与a
效果相同。 -
单行方法体可以省略大括号:如果 Lambda 表达式的方法体只包含一条语句,那么可以省略大括号。
-
单行方法体的返回语句可以省略 return 关键字:如果 Lambda 表达式的方法体只有一条返回语句,那么可以省略
return
关键字。
3、示例
-
简单的Lambda表达式:
// 使用Lambda表达式打印信息 Runnable r = () -> System.out.println("Hello, Lambda!"); r.run();
-
使用单个参数的Lambda表达式:
// 使用Lambda表达式对集合中的每个元素执行操作 List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); names.forEach(name -> System.out.println(name));
-
使用多个参数的Lambda表达式:
// 使用Lambda表达式比较两个字符串 List<String> strings = Arrays.asList("Apple", "Banana", "Cherry"); Collections.sort(strings, (s1, s2) -> s2.compareTo(s1)); strings.forEach(System.out::println);
-
Lambda表达式作为方法参数:
// 使用Lambda表达式作为方法的参数 List<Integer> numbers = Arrays.asList(1, 2, 3, 4); int sum = numbers.stream().reduce(0, (acc, item) -> acc + item); System.out.println("Sum: " + sum);
-
Lambda表达式与方法引用:
// 使用方法引用作为Lambda表达式 numbers.forEach(System.out::println);
-
Lambda表达式与构造器引用:
@Data @NoArgsConstructor @AllArgsConstructor class Person { private String name; private int age; } @Test void stream() { // 使用Lambda表达式创建对象 Supplier<Person> personSupplierLambda = () -> new Person(); // 使用构造器引用创建对象 Supplier<Person> personSupplierRef = Person::new; // 使用Supplier来创建Person对象 Person personSupplier1 = personSupplierLambda.get(); Person personSupplier2 = personSupplierRef.get(); personSupplier1.setAge(20); personSupplier1.setName("Alice"); personSupplier2.setAge(18); personSupplier2.setName("Bob"); // 如果Person有一个接受名字和年龄的构造器,我们可以这样使用它(假设存在一个合适的函数式接口) // 注意:这里需要自定义或找到一个合适的函数式接口,比如BiFunction<String, Integer, Person> // 但为了简单起见,我们仅展示概念 BiFunction<String, Integer, Person> personCreator = Person::new; Person personSupplier3 = personCreator.apply("Charlie", 30); System.out.println(personSupplier1); System.out.println(personSupplier2); System.out.println(personSupplier3); }
-
Lambda表达式与泛型:
// 使用Lambda表达式转换数据类型 List<String> stringList = Arrays.asList("1", "2", "3"); List<Integer> integerList = stringList.stream().map(Integer::parseInt).collect(Collectors.toList()); integerList.forEach(System.out::println);
-
Lambda表达式与高阶函数:
高阶函数的定义:
- 接受函数作为参数:如果一个函数可以接受另一个函数作为参数,那么这个函数就是高阶函数。
- 返回函数作为结果:如果一个函数可以返回另一个函数,那么这个函数也是高阶函数。
// 使用Lambda表达式作为高阶函数的参数 List<String> filtered = strings.stream() .filter(s -> s.startsWith("A")) .collect(Collectors.toList());
-
Lambda表达式与排序:
// 使用Lambda表达式进行复杂排序 @Data @NoArgsConstructor @AllArgsConstructor class Person { private String name; private int age; } @Test void stream() { List<Person> people = Arrays.asList(new Person("Alice", 30), new Person("Bob", 25)); people.sort((p1, p2) -> p1.age - p2.age); System.out.println(people); }
这些示例展示了Lambda表达式在Java 8中的多种用法,包括简单的操作、集合处理、方法引用、泛型以及高阶函数。Lambda表达式使得代码更加简洁和易于理解。