0%

函数式编程

Java 函数式编程:核心思想与实践

函数式编程(Functional Programming)是一种以函数为核心的编程范式,强调不可变数据无副作用,从根本上解决了并发编程中的数据竞争问题。Java 8 引入 Lambda 表达式和函数式接口,正式支持函数式编程风格,大幅简化了代码并提升了可读性。本文将从核心思想出发,详解 Lambda 表达式、函数式接口、方法引用等关键特性。

函数式编程的核心思想

函数式编程的两大支柱:

  1. 不可变数据(Immutability)
    数据一旦创建就不可修改,任何操作都只会生成新数据,不会改变原始值。这避免了多线程环境下的数据竞争(多个线程同时修改同一数据)。
  2. 无副作用(No Side Effects)
    函数的执行不会影响外部状态(如全局变量、输入参数),相同输入始终产生相同输出。这种 “纯函数” 特性让代码更易测试和推理。

函数式接口:Lambda 表达式的载体

函数式接口是函数式编程的基础,指仅包含一个抽象方法的接口(可包含默认方法或静态方法)。Java 8 提供 @FunctionalInterface 注解用于标记此类接口,编译器会检查其是否符合规范。

1
2
3
4
5
6
7
8
9
10
11
12
@FunctionalInterface // 编译器验证:仅含一个抽象方法
public interface Calculator {
int compute(int a, int b); // 唯一抽象方法

default void printResult(int result) { // 默认方法(允许)
System.out.println("结果:" + result);
}

static void log() { // 静态方法(允许)
System.out.println("计算完成");
}
}

关键:Lambda 表达式本质是函数式接口的匿名实现,简化了接口实现的代码。

Lambda 表达式:简洁的函数实现

Lambda 表达式是函数式接口的简写形式,语法为:

1
(参数列表) -> { 函数体 }

语法规则

  • 参数列表

    • 单个参数可省略括号:a -> { ... }
    • 无参数需用空括号:() -> { ... }
    • 多个参数用逗号分隔:(a, b) -> { ... }
  • 函数体

    • 单行代码可省略花括号和 return(a, b) -> a + b

    • 多行代码需用花括号包裹,并显式return:

      1
      2
      3
      4
      (a, b) -> {
      int sum = a + b;
      return sum * 2;
      }

与传统实现的对比

Lambda 表达式简化了接口实现的写法,对比传统方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 函数式接口
@FunctionalInterface
interface Greeting {
String sayHello(String name);
}

public class LambdaDemo {
public static void main(String[] args) {
// 1. 传统:匿名内部类
Greeting anonymous = new Greeting() {
@Override
public String sayHello(String name) {
return "Hello, " + name;
}
};

// 2. Lambda 表达式(简化)
Greeting lambda = name -> "Hello, " + name;

System.out.println(anonymous.sayHello("Alice")); // Hello, Alice
System.out.println(lambda.sayHello("Bob")); // Hello, Bob
}
}

可见,Lambda 表达式消除了匿名内部类的模板代码,直接聚焦核心逻辑。

Java 内置四大函数式接口

Java 8 在 java.util.function 包中提供了常用函数式接口,覆盖大多数场景,避免重复定义。

Consumer<T>:消费型接口

功能:接收类型 T 的参数,执行操作(无返回值)。
抽象方法void accept(T t)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.function.Consumer;

public class ConsumerDemo {
public static void main(String[] args) {
// 消费一个字符串并打印
Consumer<String> printer = s -> System.out.println("消费:" + s);
printer.accept("测试数据"); // 输出:消费:测试数据

// 链式消费(andThen 组合多个 Consumer)
Consumer<String> upperCase = s -> System.out.println(s.toUpperCase());
printer.andThen(upperCase).accept("hello");
// 输出:消费:hello → HELLO
}
}

Supplier<T>:供给型接口

功能:无参数,返回类型 T 的结果(提供数据)。
抽象方法T get()

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.util.function.Supplier;

public class SupplierDemo {
public static void main(String[] args) {
// 提供一个随机整数
Supplier<Integer> randomInt = () -> (int) (Math.random() * 100);
System.out.println("随机数:" + randomInt.get()); // 如:随机数:42

// 提供一个字符串
Supplier<String> defaultName = () -> "默认名称";
System.out.println(defaultName.get()); // 输出:默认名称
}
}

Function<T, R>:函数型接口

功能:接收类型 T 的参数,返回类型 R 的结果(数据转换)。
抽象方法R apply(T t)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.function.Function;

public class FunctionDemo {
public static void main(String[] args) {
// 将字符串转换为其长度
Function<String, Integer> strLength = s -> s.length();
System.out.println(strLength.apply("hello")); // 输出:5

// 链式转换(andThen 先执行当前函数,再执行后续函数)
Function<Integer, Integer> doubleIt = n -> n * 2;
int result = strLength.andThen(doubleIt).apply("test");
// 步骤:"test" → 4 → 8 → 输出:8
}
}

Predicate<T>:断言型接口

功能:接收类型 T 的参数,返回布尔值(条件判断)。
抽象方法boolean test(T t)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.util.function.Predicate;

public class PredicateDemo {
public static void main(String[] args) {
// 判断数字是否为偶数
Predicate<Integer> isEven = n -> n % 2 == 0;
System.out.println(isEven.test(4)); // 输出:true

// 组合断言(and:与操作;or:或操作;negate:非操作)
Predicate<Integer> isGreaterThan5 = n -> n > 5;
// 同时满足:偶数且大于5
boolean result = isEven.and(isGreaterThan5).test(6);
System.out.println(result); // 输出:true
}
}

Lambda 递归:特殊用法

Lambda 表达式支持递归,但需注意:递归调用的函数必须是实例变量或静态变量(避免初始化时的循环依赖)。

示例:用 Lambda 实现阶乘计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@FunctionalInterface
interface IntCalculator {
int calculate(int n);
}

public class LambdaRecursion {
public static void main(String[] args) {
// 声明一个静态变量存储 Lambda 表达式
IntCalculator factorial;
// 递归实现:n! = n * (n-1)!,终止条件 n=0 时返回 1
factorial = n -> n == 0 ? 1 : n * factorial.calculate(n - 1);

// 测试:计算 5! = 120
System.out.println("5! = " + factorial.calculate(5)); // 输出:120
}
}

原理:factorial 变量先声明后赋值,Lambda 表达式中引用自身时,变量已完成声明(但未初始化),避免了编译错误。

方法引用:Lambda 的简化形式

当 Lambda 表达式的逻辑已通过现有方法实现时,可通过方法引用进一步简化代码。语法为 类名/对象名::方法名,无需参数列表。

三种方法引用形式

(1)对象::实例方法

引用现有对象的实例方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MethodRefDemo {
public static void main(String[] args) {
// 实例对象
StringFormatter formatter = new StringFormatter();

// Lambda 表达式:s -> formatter.format(s)
Function<String, String> lambda = s -> formatter.format(s);

// 方法引用:简化为 对象::方法名
Function<String, String> methodRef = formatter::format;

System.out.println(methodRef.apply("test")); // 输出:FORMATTED: test
}
}

class StringFormatter {
public String format(String s) {
return "FORMATTED: " + s;
}
}
(2)类::静态方法

引用类的静态方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
import java.util.function.Function;

public class StaticMethodRef {
public static void main(String[] args) {
// Lambda 表达式:n -> Math.abs(n)
Function<Integer, Integer> lambda = n -> Math.abs(n);

// 方法引用:类::静态方法
Function<Integer, Integer> methodRef = Math::abs;

System.out.println(methodRef.apply(-100)); // 输出:100
}
}
(3)类::实例方法

引用类的实例方法(特殊场景:Lambda 第一个参数是方法的调用者,第二个参数是方法的参数)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.function.BiPredicate;

public class ClassInstanceMethodRef {
public static void main(String[] args) {
// 需求:判断两个字符串是否相等
// Lambda 表达式:(s1, s2) -> s1.equals(s2)
BiPredicate<String, String> lambda = (s1, s2) -> s1.equals(s2);

// 方法引用:类::实例方法(s1 是调用者,s2 是参数)
BiPredicate<String, String> methodRef = String::equals;

System.out.println(methodRef.test("a", "a")); // 输出:true
}
}

构造器引用

引用类的构造器,语法为 类名::new,等价于创建对象的 Lambda 表达式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import java.util.function.Supplier;
import java.util.function.Function;

public class ConstructorRef {
static class User {
private String name;

public User() { // 无参构造器
this.name = "默认用户";
}

public User(String name) { // 有参构造器
this.name = name;
}

public String getName() { return name; }
}

public static void main(String[] args) {
// 引用无参构造器:() -> new User()
Supplier<User> noArgCtor = User::new;
System.out.println(noArgCtor.get().getName()); // 输出:默认用户

// 引用有参构造器:name -> new User(name)
Function<String, User> argCtor = User::new;
System.out.println(argCtor.apply("Alice").getName()); // 输出:Alice
}
}

函数式编程的优势与适用场景

优势:

  1. 代码简洁:Lambda 和方法引用消除模板代码,逻辑更清晰。
  2. 易于并发:不可变数据和无副作用避免了线程安全问题。
  3. 函数组合:通过 andThencompose 等方法组合多个函数,实现复杂逻辑。
  4. 适配 Stream API:Stream 的中间操作(如 filtermap)依赖函数式接口,实现声明式数据处理。

适用场景:

  • 集合数据处理(结合 Stream API)。
  • 事件处理(如按钮点击回调)。
  • 并发编程(避免共享状态修改)。
  • 函数式接口的快速实现(如自定义排序、条件判断)。

欢迎关注我的其它发布渠道