当我们使用需要执行昂贵或缓慢方法的资源(例如数据库查询或 REST 调用)时,我们倾向于使用本地缓存或私有字段。
lambda 函数允许我们使用方法作为参数并推迟方法的执行或完全省略它。
在本教程中,我们将展示如何使用 lambda 函数延迟初始化字段,注意不是延迟方法执行,而是实现字段懒加载。
但是在实现懒加载之前,先了解一些lambda 函数推迟方法的执行。
Lambda推迟方法执行
在Java中,Lambda表达式可以用于推迟方法的执行,特别是在与函数式接口(Functional Interface)一起使用时。
函数式接口是只包含一个抽象方法的接口。通过Lambda表达式,你可以将这个抽象方法的执行推迟到Lambda表达式被调用的时候。下面是一个简单的示例:
@FunctionalInterface interface MyFunctionalInterface { void myMethod(); }
public class LambdaDelayExecution { public static void main(String[] args) { // 使用Lambda表达式实现函数式接口 MyFunctionalInterface myFunction = () -> { System.out.println("方法执行!"); };
// 调用Lambda表达式中的方法 // 这里才会真正执行Lambda表达式中的myMethod方法 myFunction.myMethod(); } }
|
在上述示例中,MyFunctionalInterface是一个函数式接口,它定义了一个名为myMethod的抽象方法。通过Lambda表达式,我们可以实现这个接口并在Lambda表达式中定义myMethod的具体实现。在main方法中,当调用myFunction.myMethod()时,Lambda表达式中的方法才会真正执行。
需要注意的是,Lambda表达式常常与Java中的Streams API一起使用,这样可以更方便地实现推迟执行的效果。例如:
import java.util.Arrays;
public class LambdaDelayExecution { public static void main(String[] args) { // 使用Streams API中的forEach方法,延迟执行 Arrays.asList("One", "Two", "Three") .stream() .map(s -> { System.out.println("Mapping: " + s); return s.toUpperCase(); }) .forEach(s -> System.out.println("ForEach: " + s)); } }
|
在这个例子中,map方法中的Lambda表达式的执行被延迟到forEach方法调用时。这种延迟执行的方式可以有效地进行数据处理和转换。lambda 函数懒加载
Lambda函数只提供推迟方法执行,不提供字段懒初始化方式,但是我们可以转个弯使用。
import java.util.Optional; import java.util.function.Supplier; import java.util.concurrent.atomic.AtomicReference;
public class LazyLoadingExample { private final AtomicReference<String> data = new AtomicReference<>(); private Supplier<String> lazyField = () -> createSomeType();
private String createSomeType() { System.out.println("只执行一次哦"); return "Created"; } public String getLazyField() { if (data.get() == null) { synchronized (data) { if (data.get() == null) { data.set(lazyField.get()); } } } return data.get(); }
public static void main(String[] args) { LazyLoadingExample example = new LazyLoadingExample(); //在第一次访问时,实例被创建 String lazyField = example.getLazyField(); //在后续访问时,不会再次创建实例 String lazyFieldAgain = example.getLazyField(); } }
|
上面代码输出:
getLazyField()中使用了Double-Checked Locking(双重检查锁定)保证多线程安全性。
- 我们需要想象多个线程同时调用getData()方法。线程确实会阻塞,并且执行将按顺序进行,直到data.get()调用不为 null。一旦数据字段初始化完成,多个线程就可以并发访问它。
- 有人可能会认为getData()方法中的双 null 检查是多余的,但事实并非如此。事实上,外部 null 检查确保当data.get()不为 null 时,线程不会阻塞在同步块上。
结论
在本文中,我们展示了使用 lambda 函数延迟初始化字段的不同方法。通过使用这种方法,我们可以避免多次执行昂贵的调用并推迟它们。我们的示例可以用作本地缓存或Project Lombok的lazy-getter 的替代方案。