1. 引言

在处理需要执行昂贵或慢速方法的资源时(如数据库查询或REST调用),我们通常会使用本地缓存或私有字段。Lambda函数允许我们将方法作为参数传递,从而延迟方法执行或完全省略执行。

本文将展示如何使用Lambda函数实现字段的延迟初始化。

2. Lambda替代方案

首先实现基础版本。创建LambdaSupplier类:

public class LambdaSupplier<T> {

    protected final Supplier<T> expensiveData;

    public LambdaSupplier(Supplier<T> expensiveData) {
        this.expensiveData = expensiveData;
    }

    public T getData() {
        return expensiveData.get();
    }
}

LambdaSupplier通过延迟执行Supplier.get()实现字段延迟初始化。**但每次调用getData()都会触发Supplier.get()**,行为与Supplier接口完全一致。

验证该行为的单元测试:

@Test
public void whenCalledMultipleTimes_thenShouldBeCalledMultipleTimes() {
    @SuppressWarnings("unchecked") Supplier<String> mockedExpensiveFunction = Mockito.mock(Supplier.class);
    Mockito.when(mockedExpensiveFunction.get())
        .thenReturn("expensive call");
    LambdaSupplier<String> testee = new LambdaSupplier<>(mockedExpensiveFunction);
    Mockito.verify(mockedExpensiveFunction, Mockito.never())
        .get();
    testee.getData();
    testee.getData();
    Mockito.verify(mockedExpensiveFunction, Mockito.times(2))
        .get();
}

测试验证Supplier.get()被调用了两次,符合预期。

3. 延迟Supplier

为解决多次调用问题,升级实现保证昂贵方法只执行一次。LazyLambdaSupplier扩展LambdaSupplier,增加缓存字段:

public class LazyLambdaSupplier<T> extends LambdaSupplier<T> {

    private T data;

    public LazyLambdaSupplier(Supplier<T> expensiveData) {
        super(expensiveData);
    }

    @Override
    public T getData() {
        if (data != null) {
            return data;
        }
        return data = expensiveData.get();
    }

}

实现将返回值存储在私有字段data中,后续调用直接复用。

验证单次调用的测试:

@Test
public void whenCalledMultipleTimes_thenShouldBeCalledOnlyOnce() {
    @SuppressWarnings("unchecked") Supplier<String> mockedExpensiveFunction = Mockito.mock(Supplier.class);
    Mockito.when(mockedExpensiveFunction.get())
        .thenReturn("expensive call");
    LazyLambdaSupplier<String> testee = new LazyLambdaSupplier<>(mockedExpensiveFunction);
    Mockito.verify(mockedExpensiveFunction, Mockito.never())
        .get();
    testee.getData();
    testee.getData();
    Mockito.verify(mockedExpensiveFunction, Mockito.times(1))
        .get();
}

测试验证函数仅被调用一次。

⚠️ 非线程安全验证:

@Test
public void whenCalledMultipleTimesConcurrently_thenShouldBeCalledMultipleTimes() throws InterruptedException {
    @SuppressWarnings("unchecked") Supplier mockedExpensiveFunction = Mockito.mock(Supplier.class);
    Mockito.when(mockedExpensiveFunction.get())
        .thenAnswer((Answer) invocation -> {
            Thread.sleep(1000L);
            return "Late response!";
        });
    LazyLambdaSupplier testee = new LazyLambdaSupplier<>(mockedExpensiveFunction);
    Mockito.verify(mockedExpensiveFunction, Mockito.never())
        .get();

    ExecutorService executorService = Executors.newFixedThreadPool(4);
    executorService.invokeAll(List.of(testee::getData, testee::getData));
    executorService.shutdown();
    if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
        executorService.shutdownNow();
    }

    Mockito.verify(mockedExpensiveFunction, Mockito.times(2))
        .get();
}

并发测试显示Supplier.get()被调用两次。Thread.sleep()确保两个线程同时进入getData()data仍为null

4. 线程安全方案

使用同步访问和AtomicReference解决线程安全问题:

public class LazyLambdaThreadSafeSupplier<T> extends LambdaSupplier<T> {

    private final AtomicReference<T> data;

    public LazyLambdaThreadSafeSupplier(Supplier<T> expensiveData) {
        super(expensiveData);
        data = new AtomicReference<>();
    }

    public T getData() {
        if (data.get() == null) {
            synchronized (data) {
                if (data.get() == null) {
                    data.set(expensiveData.get());
                }
            }
        }
        return data.get();
    }

}

双重检查锁定解析:

  • 外层检查:避免初始化后线程阻塞在同步块
  • 内层检查:确保只有一个线程执行初始化

验证线程安全的测试:

@Test
public void whenCalledMultipleTimesConcurrently_thenShouldBeCalledOnlyOnce() throws InterruptedException {
    @SuppressWarnings("unchecked") Supplier mockedExpensiveFunction = Mockito.mock(Supplier.class);
    Mockito.when(mockedExpensiveFunction.get())
        .thenAnswer((Answer) invocation -> {
            Thread.sleep(1000L);
            return "Late response!";
        });
    LazyLambdaThreadSafeSupplier testee = new LazyLambdaThreadSafeSupplier<>(mockedExpensiveFunction);
    Mockito.verify(mockedExpensiveFunction, Mockito.never())
        .get();

    ExecutorService executorService = Executors.newFixedThreadPool(4);
    executorService.invokeAll(List.of(testee::getData, testee::getData));
    executorService.shutdown();
    if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
        executorService.shutdownNow();
    }

    Mockito.verify(mockedExpensiveFunction, Mockito.times(1))
        .get();
}

测试验证并发场景下函数仅执行一次。

5. 总结

本文展示了使用Lambda函数实现字段延迟初始化的多种方案:

  1. 基础方案:简单但多次执行
  2. 延迟方案:缓存结果但非线程安全
  3. 线程安全方案:双重检查锁定保证单次执行

这些方案可作为本地缓存或Project Lombok的@Getter(lazy=true)的替代实现。完整代码示例可在GitHub获取。


原始标题:Lazy Field Initialization with Lambdas | Baeldung