函数式编程

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

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

函数式编程的核心思想

函数式编程的两大支柱:

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

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

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

@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 表达式是函数式接口的简写形式,语法为:

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

语法规则

  • 参数列表

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

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

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

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

与传统实现的对比

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

// 函数式接口
@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)

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()

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)

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)

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 实现阶乘计算

@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)对象::实例方法

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

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)类::静态方法

引用类的静态方法。

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 第一个参数是方法的调用者,第二个参数是方法的参数)。

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 表达式。

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)。
  • 事件处理(如按钮点击回调)。
  • 并发编程(避免共享状态修改)。
  • 函数式接口的快速实现(如自定义排序、条件判断)。