Lombok 其他特性

Lombok 中部分特性使用频率不高,这里主要介绍核心包中其他特性。

val

val 本身并不是一个类型,它是一种特殊的变量声明方式,用于声明一个不可变的局部变量。

🏷 版本

val在 Lombok 0.10 中引入。

Lombok 1.18.22 中的新功能:val被替换为final var

📋 概述

val 可以用作局部变量的类型,而不必实际指定类型。执行此操作时,将从设定的初始值表达式推断类型。局部变量也将被定为 final 变量。此功能仅适用于局部变量和 foreach 循环,并且需要初始化表达式。

val实际上是一种“类型”,并且作为Lombok包中的类真实存在。你必须导入它才能使 val 工作(或使用lombok.val类型)。局部变量使用 val 会触发添加 final 关键字以及通过初始化表达式的类型来确定字段类型。

限制

此功能当前在 NetBeans 中不起作用。

原生写法

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class ValExample {
  public String example() {
    final ArrayList<String> example = new ArrayList<String>();
    example.add("Hello, World!");
    final String foo = example.get(0);
    return foo.toLowerCase();
  }

  public void example2() {
    final HashMap<Integer, String> map = new HashMap<Integer, String>();
    map.put(0, "zero");
    map.put(5, "five");
    for (final Map.Entry<Integer, String> entry : map.entrySet()) {
      System.out.printf("%d: %s\n", entry.getKey(), entry.getValue());
    }
  }
}

Lombok 简化

import java.util.ArrayList;
import java.util.HashMap;
import lombok.val;

public class ValExample {
  public String example() {
    val example = new ArrayList<String>();
    example.add("Hello, World!");
    val foo = example.get(0);
    return foo.toLowerCase();
  }

  public void example2() {
    val map = new HashMap<Integer, String>();
    map.put(0, "zero");
    map.put(5, "five");
    for (val entry : map.entrySet()) {
      System.out.printf("%d: %s\n", entry.getKey(), entry.getValue());
    }
  }
}

🛠 配置

支持的配置:

lombok.val.flagUsage= [ warning| error]

默认:未设置。如果配置的话,Lombok 会将使用val标记为警告或错误。

🔔 说明

对于复合类型,将推断最常见的父类,而不是公共接口。例如,bool ? new HashSet() : new ArrayList()是具有复合类型的表达式:结果可以是AbstractCollection以及Serializable。推断的类型将为AbstractCollection,因为它是一个类,而Serializable是一个接口。

在不明确的情况下,例如当初始化表达式为null时,会被推断为 java.lang.Object

var

var 允许声明一个其类型由初始化表达式推断的变量。需要注意的是,var 声明的变量不是 final 的,这意味着它们的值在初始化后可以被改变。

🏷 版本

var 在 Lombok 1.16.12 中作为实验功能引入。

var 在 Lombok 1.16.20 中晋升为主特性。

📋 概述

var工作方式与 val 完全相同,只是局部变量未标记为final。

字段类型由初始化表达式确定,并且任何进一步的赋值虽然现在是合法的(因为该变量不再是final),但是如果再次赋值的类型不匹配将会导致编译错误。

例如,var x = "Hello"; x = Color.RED;不起作用;x 的类型将被推断为java.lang.String,因此x = Color.RED分配将失败。如果推断出的 x 类型是 java.lang.Object ,则可以正常编译此代码,但这不太合理。

原生写法

public class MyLombok {

    public static void main(String[] args) {
        Map<String, Integer> demo = new HashMap<>();
        demo.put("A", 32);
        System.out.println(demo);
    }
}

Lombok 简化

import lombok.var;

public class MyLombok {

    public static void main(String[] args) {
        var demo = new HashMap<>();
        demo.put("A", 32);
        System.out.println(demo);
    }
}

🛠 配置

lombok.var.flagUsage= [ warning| error]

默认:未设置。如果配置的话,Lombok 会将使用var标记为警告或错误。

@Cleanup

@Cleanup 注解用于确保在代码执行路径退出当前范围之前,自动清理给定资源。如果资源本身实现了 AutoCloseable 接口,Lombok 会自动调用其 close() 方法进行清理。

📋 概述

@Cleanup 用于确保在代码执行路径退出当前范围之前,自动清理给定资源。可以给任意局部变量添加 @Cleanup 注解,例如:@Cleanup InputStream in = new FileInputStream("some/file");
这表示,在方法退出之前,将调用 in.close()方法。并且该调用将被 try/finally 代码块包裹。

如果想要清理的对象没有close()方法,而是有其他一些无参方法,则可以像这样指定该方法的名称:
@Cleanup("dispose") org.eclipse.swt.widgets.CoolBar bar = new CoolBar(parent, 0);

原生写法

import java.io.*;

public class CleanupExample {
  public static void main(String[] args) throws IOException {
    InputStream in = new FileInputStream(args[0]);
    try {
      OutputStream out = new FileOutputStream(args[1]);
      try {
        byte[] b = new byte[10000];
        while (true) {
          int r = in.read(b);
          if (r == -1) break;
          out.write(b, 0, r);
        }
      } finally {
        if (out != null) {
          out.close();
        }
      }
    } finally {
      if (in != null) {
        in.close();
      }
    }
  }
}

Lombok 简化

import lombok.Cleanup;
import java.io.*;

public class CleanupExample {
  public static void main(String[] args) throws IOException {
    @Cleanup InputStream in = new FileInputStream(args[0]);
    @Cleanup OutputStream out = new FileOutputStream(args[1]);
    byte[] b = new byte[10000];
    while (true) {
      int r = in.read(b);
      if (r == -1) break;
      out.write(b, 0, r);
    }
  }
}

🛠 配置

lombok.cleanup.flagUsage= [ warning| error]

默认:未设置。如果配置的话,Lombok 会将使用@Cleanup标记为警告或错误。

🔔 说明

在 finally 代码块中,仅当给定资源不是 null 时才会调用清理方法。但是,如果在代码上使用了 delombok ,则会插入lombok.Lombok.preventNullAnalysis(Object o)的调用,以防止在静态代码分析确定是否需要非空检查时出现警告。使用 lombok.jar 在类路径上编译会删除该方法调用,因此不存在运行时依赖关系。

如果代码引发异常,并且随后触发的清理方法调用也引发异常,则原始异常将被清理调用引发的异常隐藏。其实,Lombok 希望生成代码是这样的:如果主体抛出异常,则 close 调用抛出的任何异常都会被默默吞掉;如果主体正常退出,则 close 方法出现的异常不被默默吞掉。但是目前还没有技术方案能实现该效果。

开发者仍然需要处理清理方法可能生成的任何异常!

@Value

@Value 是 @Data 的不可变形式,并且所有字段默认是 private final 的。此外, @Value 注解同样会生成一个全参构造函数,除非类中存在 final 字段。

🏷 版本

@Value 在 Lombok v0.11.4 中作为实验性功能引入。

@Value 从 Lombok v0.12.0 开始提升为主 Lombok 包。

📋 概述

@Value 是 @Data 的不可变形式:默认情况下,所有字段都是private final的,并且不会生成 setter,类本身也将是 final 的,不可被继承。

一般认为 @Value 注解代表了 final @ToString @EqualsAndHashCode @AllArgsConstructor @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @Getter,如果类中已经包含需要生成的方法,则不会生成该方法,也不会发出警告。例如,如果需要编写自己的 toString ,则不会发生错误,并且 Lombok 不会生成 toString方法。此外,任何显式构造函数,无论参数列表如何,都意味着 Lombok 不会生成构造函数。如果确实希望 Lombok 生成无参构造函数,则需要添加 @AllArgsConstructor 注解。

如果 ‘@Builder’ 和 ‘@Value’ 都在一个类上,则‘@Builder’生成的构造函数优先级更高。

如果不想字段被 final 修饰,可以在字段上使用@NonFinal注解。

如果不想生成的类被 final 修饰,可以在类上使用@NonFinal注解。

可以显示添加 @ToString/@Getter等注解来覆盖 @Value 注解的默认行为。

原生写法

import java.util.Arrays;

public final class ValueExample {
  private final String name;
  private int age;
  private final double score;
  protected final String[] tags;

  @java.beans.ConstructorProperties({"name", "age", "score", "tags"})
  public ValueExample(String name, int age, double score, String[] tags) {
    this.name = name;
    this.age = age;
    this.score = score;
    this.tags = tags;
  }

  public String getName() {
    return this.name;
  }

  public int getAge() {
    return this.age;
  }

  public double getScore() {
    return this.score;
  }

  public String[] getTags() {
    return this.tags;
  }

  @java.lang.Override
  public boolean equals(Object o) {
    if (o == this) return true;
    if (!(o instanceof ValueExample)) return false;
    final ValueExample other = (ValueExample)o;
    final Object this$name = this.getName();
    final Object other$name = other.getName();
    if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false;
    if (this.getAge() != other.getAge()) return false;
    if (Double.compare(this.getScore(), other.getScore()) != 0) return false;
    if (!Arrays.deepEquals(this.getTags(), other.getTags())) return false;
    return true;
  }

  @java.lang.Override
  public int hashCode() {
    final int PRIME = 59;
    int result = 1;
    final Object $name = this.getName();
    result = result * PRIME + ($name == null ? 43 : $name.hashCode());
    result = result * PRIME + this.getAge();
    final long $score = Double.doubleToLongBits(this.getScore());
    result = result * PRIME + (int)($score >>> 32 ^ $score);
    result = result * PRIME + Arrays.deepHashCode(this.getTags());
    return result;
  }

  @java.lang.Override
  public String toString() {
    return "ValueExample(name=" + getName() + ", age=" + getAge() + ", score=" + getScore() + ", tags=" + Arrays.deepToString(getTags()) + ")";
  }

  ValueExample withAge(int age) {
    return this.age == age ? this : new ValueExample(name, age, score, tags);
  }

  public static final class Exercise<T> {
    private final String name;
    private final T value;

    private Exercise(String name, T value) {
      this.name = name;
      this.value = value;
    }

    public static <T> Exercise<T> of(String name, T value) {
      return new Exercise<T>(name, value);
    }

    public String getName() {
      return this.name;
    }

    public T getValue() {
      return this.value;
    }

    @java.lang.Override
    public boolean equals(Object o) {
      if (o == this) return true;
      if (!(o instanceof ValueExample.Exercise)) return false;
      final Exercise<?> other = (Exercise<?>)o;
      final Object this$name = this.getName();
      final Object other$name = other.getName();
      if (this$name == null ? other$name != null : !this$name.equals(other$name)) return false;
      final Object this$value = this.getValue();
      final Object other$value = other.getValue();
      if (this$value == null ? other$value != null : !this$value.equals(other$value)) return false;
      return true;
    }

    @java.lang.Override
    public int hashCode() {
      final int PRIME = 59;
      int result = 1;
      final Object $name = this.getName();
      result = result * PRIME + ($name == null ? 43 : $name.hashCode());
      final Object $value = this.getValue();
      result = result * PRIME + ($value == null ? 43 : $value.hashCode());
      return result;
    }

    @java.lang.Override
    public String toString() {
      return "ValueExample.Exercise(name=" + getName() + ", value=" + getValue() + ")";
    }
  }
}

Lombok 简化

import lombok.AccessLevel;
import lombok.experimental.NonFinal;
import lombok.experimental.Value;
import lombok.experimental.With;
import lombok.ToString;

@Value public class ValueExample {
  String name;
  @With(AccessLevel.PACKAGE) @NonFinal int age;
  double score;
  protected String[] tags;

  @ToString(includeFieldNames=true)
  @Value(staticConstructor="of")
  public static class Exercise<T> {
    String name;
    T value;
  }
}

🛠 配置

lombok.value.flagUsage = [warning | error]

默认:未设置。如果配置的话,Lombok 会将使用@Value标记为警告或错误。

lombok.noArgsConstructor.extraPrivate = [true | false]

默认:false。如果设置为 true,lombok 将为任意带有 @Value 注解的类生成一个私有的无参构造函数,该构造函数将所有字段设置为默认值 (null / 0 / false)。

🔔 说明

使用 @Value(staticConstructor=”of”) 可以生成一个私有构造函数,并生成一个名为 of 的公有静态方法,该方法是一个围绕此私有构造函数的包装器。

不能在字段上使用 @FieldDefaults 来“撤消” private 行为和 final 行为,如果想覆盖此行为需要使用 @NonFinal 和 @PackagePrivate。

@SneakyThrows

@SneakyThrows 注解用于静默处理 checked exception, @SneakyThrows 注解实际上会在运行时将 checked exception 转换为 unchecked exception。

📋 概述

@SneakyThrows 用于静默处理 checked exception,这样就不用显示在方法上使用 throws 抛出异常了。

Lombok 并不是忽略、包装、替换或以其他方式修改抛出的选中异常;它只是伪造了编译器。在 JVM(class文件)级别上,无论方法是否有异常,都可以抛出异常,Lombok 通过这种方式工作的。

@SneakyThrows 可以用于下面两个场景:

  • 不需要严格处理的接口:例如 Runnable ,在 run() 方法中,无论抛出何种异常,对于调用线程都是无法处理的。捕获已检查的异常并将其包装在某种 RuntimeException 类型中只会掩盖问题的真正原因。
  • 不可能发生的异常:例如 new String(someByteArray, "UTF-8");声明它可以抛出一个 UnsupportedEncodingException。但根据 JVM 规范,UTF-8 是始终可用的,也就是不会出现这个异常,也就不需要处理它。

其实还有一种情形也适用:使用 lambda 语法 (arg -> action) 时,不需要处理某些异常。不过 Lambda 表达式无法添加注解,因此没法使用了。

@SneakyThrows 注解可以传入指定的异常,如果不给参数,则可以是任意的异常。

原生写法

import lombok.Lombok;

public class SneakyThrowsExample implements Runnable {
  public String utf8ToString(byte[] bytes) {
    try {
      return new String(bytes, "UTF-8");
    } catch (UnsupportedEncodingException e) {
      throw Lombok.sneakyThrow(e);
    }
  }

  public void run() {
    try {
      throw new Throwable();
    } catch (Throwable t) {
      throw Lombok.sneakyThrow(t);
    }
  }
}

Lombok 简化

import lombok.SneakyThrows;

public class SneakyThrowsExample implements Runnable {
  @SneakyThrows(UnsupportedEncodingException.class)
  public String utf8ToString(byte[] bytes) {
    return new String(bytes, "UTF-8");
  }

  @SneakyThrows
  public void run() {
    throw new Throwable();
  }
}

🛠 配置

lombok.sneakyThrows.flagUsage = [warning | error]

默认:未设置。如果配置的话,Lombok 会将使用@SneakyThrows标记为警告或错误。

🔔 说明

因为 @SneakyThrows 是实现细节,而不是方法签名的一部分,因此不应该在没有抛出异常的方法上使用该注解。@SneakyThrows 不支持继承。

如果在构造函数上使用 @SneakyThrows,则无法适用于 super 中的方法,因为 Java 要求 super 必须位于构造函数的第一行,且不能有 try/catch。

@SneakyThrows 在空方法上,或者无参构造函数或仅调用同级/父类构造函数时,不会产生 try/catch 块和警告。

@Synchronized

@Synchronized 注解锁定在名为 $lock 的字段上。

📋 概述

@Synchronized 可以让你更为安全地实现 synchronized 关键字所需要的功能,可以让你不暴露锁。与 synchronized 一样,注解只能用于静态方法和实例方法。它的操作与 synchronized 关键字类似,但它锁定不同的对象。关键字锁定在 this 上,但注解锁定在名为 $lock 的字段上,该字段是私有的。

如果该字段不存在,则会创建一个。如果在 static 方法添加该注解,则会锁定名为 $LOCK 的静态字段。

如果需要,你可以自己创建这些锁。如果您已经自己创建了 $lock$LOCK 字段,则不会生成它们。您还可以选择锁定另一个字段,方法是将其指定为注解 @Synchronized 的参数。在此用法变体中,不会自动创建字段,你必须自己显式创建它们,否则将发出错误。

锁定 this 或者当前对象可能产生一些意外的情况,因为不受控制的其他代码也会锁定这些对象,这可能会导致争用条件和其他令人讨厌的线程相关的 bug。

如果更喜欢 java.util.concurrent.locks 样式锁(如果使用的是虚拟线程,则建议使用),可以查看 @Locked 。

原生写法

public class SynchronizedExample {
  private static final Object $LOCK = new Object[0];
  private final Object $lock = new Object[0];
  private final Object readLock = new Object();

  public static void hello() {
    synchronized($LOCK) {
      System.out.println("world");
    }
  }

  public int answerToLife() {
    synchronized($lock) {
      return 42;
    }
  }

  public void foo() {
    synchronized(readLock) {
      System.out.println("bar");
    }
  }
}

Lombok 简化

import lombok.Synchronized;

public class SynchronizedExample {
  private final Object readLock = new Object();

  @Synchronized
  public static void hello() {
    System.out.println("world");
  }

  @Synchronized
  public int answerToLife() {
    return 42;
  }

  @Synchronized("readLock")
  public void foo() {
    System.out.println("bar");
  }
}

🛠 配置

lombok.synchronized.flagUsage = [warning | error]

默认:未设置。如果配置的话,Lombok 会将使用@Synchronized标记为警告或错误。

🔔 说明

如果 $lock$LOCK是自动生成的,则字段将使用空 Object[] 数组初始化,而不仅仅是 new Object()。Lombok 这样做是因为新对象不可序列化,但 0 大小数组是可序列化的。因此,使用 @Synchronized 不会阻止对象序列化。

在类中至少有一个 @Synchronized 方法意味着将有一个锁定字段,但如果以后删除此类方法,则将不再有锁定字段。这意味着需要预先确定 serialVersionUID 的值。如果你打算通过 java 的序列化机制长期存储它们,则建议始终将 serialVersionUID 添加到你的类中。如果这样做,从方法中删除所有 @Synchronized 注解不会中断序列化。

如果您想知道为什么在为锁定对象选择自己的名称时不会自动生成字段:因为在字段名称中打错字将导致很难找到错误。

@Locked

@Locked 注解使用 java.util.concurrent.locks.ReentrantLock,@Locked 注解可以提高性能,因为它允许多个线程同时读取。

🏷 版本

@Locked 是在 Lombok v1.20 中引入的。

📋 概述

@Locked 将方法中的所有代码包装到一个 java.util.concurrent.locks.ReentrantLock 块中,并在退出该方法时将其解锁。这很像 @Synchronized。

你可以指定一个 ReentrantLock 类型的字段,Lombok 在该字段上加锁。否则,会对静态方法生成 $LOCK、示例方法生成 $lock 字段来加锁。要是字段不存在,会自动创建。

还有 @Locked.Read 和 @Locked.Write 注解,它们使用java.util.concurrent.locks.ReadWriteLock 具体是 ReentrantReadWriteLock,@Locked.Read 用在方法上表示读锁,@Locked.Write 用在方法上表示写锁。

如果使用了 Java 20 引入的虚拟线程,则建议使用该注解而不是 @Synchronized。

原生写法

public class LockedExample {
  private final ReadWriteLock lock = new ReentrantReadWriteLock();
  private final Lock baseLock = new ReentrantLock();
  private int value = 0;

  public int getValue() {
    this.lock.readLock().lock();
    try {
      return value;
    } finally {
      this.lock.readLock().unlock();
    }
  }

  public void setValue(int newValue) {
    this.lock.writeLock().lock();
    try {
      value = newValue;
    } finally {
      this.lock.writeLock().unlock();
    }
  }

  public void foo() {
    this.baseLock.lock();
    try {
      System.out.println("bar");
    } finally {
      this.baseLock.unlock();
    }
  }
}

Lombok 简化

import lombok.Locked;

public class LockedExample {
  private int value = 0;

  @Locked.Read
  public int getValue() {
    return value;
  }

  @Locked.Write
  public void setValue(int newValue) {
    value = newValue;
  }

  @Locked("baseLock")
  public void foo() {
    System.out.println("bar");
  }
}

🛠 配置

lombok.locked.flagUsage = [warning | error]

默认:未设置。如果配置的话,Lombok 会将使用@Locked标记为警告或错误。

🔔 说明

由于@Locked.Read 和 @Locked.Write 使用的锁类型不同,因此它们在指定字段名的情况下,不能同时出现在一个对象上。

@Locked 、 @Locked.Read 和 @Locked.Write 注解默认锁的字段名相同,因此不指定字段名时,不能将它们混合使用。

@With

在克隆对象的同时修改【不可变】字段值, @With 注解生成的方法会返回一个新的对象实例。

🏷 版本

@Wither 在 Lombok v0.11.4 中作为实验性功能引入。

Wither 在 Lombok v1.18.10 中重命名为 @With,并从实验性包中移出并进入核心包。

📋 概述

对于需要修改不可变字段的情形,可以通过克隆新的对象并给字段设置新值的方式实现。通过 @With 注解,可以生成 withFieldName(newValue) 方法来生成克隆的对象。

例如,如果创建 public class Point { private final int x, y; } ,则 setter 没有意义,因为字段是final字段。 @With 可以为您生成一个 withX(int newXValue) 方法,该方法将返回一个新的 Point,该Point的 x 和 y 值相同。

@With 依赖于全参构造器,如果此构造函数不存在,则 @With 注解将导致编译时错误。你可以使用 Lombok 自己的 @AllArgsConstructor 自动生成一个全参构造函数。当然,如果你手动编写这个构造函数,这也是可以接受的。

像 @Setter 一样,你可以指定一个访问级别,如果你希望生成的 with 方法不是 public:@With(level = AccessLevel.PROTECTED),也像 @Setter 一样,你还可以在class上添加 @With 注释,这意味着为每个字段(甚至是非 final 字段)生成一个 with 方法。

若要对生成的方法添加注解,可以使用 onMethod=@__({@AnnotationsHere})。不过要小心!这是一项实验性功能。

字段上的 javadoc 会被复制到 with 方法上,@param 会被移动到 with 方法上,移动的意思是:从字段的 javadoc 中删除。如果想单独设置内容,可以在独立的一行中添加被至少两个短横包裹的 WITH 单词,其后的内容会被复制到 with 方法上。

原生写法

import lombok.NonNull;

public class WithExample {
  private @NonNull final String name;
  private final int age;

  public WithExample(String name, int age) {
    if (name == null) throw new NullPointerException();
    this.name = name;
    this.age = age;
  }

  protected WithExample withName(@NonNull String name) {
    if (name == null) throw new java.lang.NullPointerException("name");
    return this.name == name ? this : new WithExample(name, age);
  }

  public WithExample withAge(int age) {
    return this.age == age ? this : new WithExample(name, age);
  }
}

Lombok 简化

import lombok.AccessLevel;
import lombok.NonNull;
import lombok.With;

public class WithExample {
  @With(AccessLevel.PROTECTED) 
  @NonNull 
  private final String name;

  @With 
  private final int age;

  public WithExample(@NonNull String name, int age) {
    this.name = name;
    this.age = age;
  }
}

🛠 配置

lombok.with.flagUsage = [warning | error]

默认:未设置。如果配置的话,Lombok 会将使用@With标记为警告或错误。

lombok.accessors.prefix += 字段前缀

默认:空列表。这是一个列表属性;可以使用 += 运算符添加条目。可以使用 -= 运算符删除从父配置文件中继承的前缀。Lombok 将从字段名称中去除任何匹配的字段前缀,以确定要生成的 getter/setter 的名称。例如,如果 m 是此设置中列出的前缀之一,则名为 mFoobar 的字段将生成的 getter 方法名为 getFoobar() ,而不是 getMFoobar() 。显式配置 @Accessors 注解的 prefix 参数优先于此设置。

lombok.accessors.capitalization = [ basic | beanspec ]

默认:basic。处理大写首字母的字段。例如,uShaped 默认生成的为 withUShaped ,修改为 beanspec 后为 withuShaped。

🔔 说明

无法为静态字段生成 With 方法,因为这没有意义。

可以为抽象类生成 With 方法,但这会生成具有适当签名的抽象方法。

@With 将跳过静态字段和名称以 $ 开头的字段。

生成的方法名称,字段的第一个字符会转换为大写字母,然后以 with 为前缀。

如果已存在具有相同名称(不区分大小写)和相同参数计数的任何方法,则不再生成 with 方法,并且会发出警告。

非空校验的注解会被复制到 with 方法的参数中。

@Getter(lazy=true)

@Getter(lazy=true) 注解会缓存字段值,这种方法适用于初始化代价高昂的字段。

🏷 版本

@Getter(lazy=true) 是在 Lombok v0.10 中引入的。

📋 概述

你可以让 Lombok 生成一个 getter 方法,该 getter 将在第一次调用此 getter 时计算一次值,并从那时起缓存它。如果计算该值占用大量 CPU,或者该值占用大量内存,则此值非常有用。要使用此功能,请创建一个 private final 变量,使用运行成本高昂的表达式对其进行初始化,然后使用 @Getter(lazy=true)。该字段将对代码的其余部分隐藏,并且在首次调用 getter 时,对表达式的计算次数不会超过一次。即使昂贵的计算结果是 null ,结果也会被缓存,并且昂贵的计算不需要线程安全,因为 Lombok 负责加锁。

如果初始化表达式很复杂,或者包含泛型,则建议将代码移动到私有(如果可能静态)方法,并改为调用该方法。

原生写法

public class GetterLazyExample {
  private final java.util.concurrent.AtomicReference<java.lang.Object> cached = new java.util.concurrent.AtomicReference<java.lang.Object>();

  public double[] getCached() {
    java.lang.Object value = this.cached.get();
    if (value == null) {
      synchronized(this.cached) {
        value = this.cached.get();
        if (value == null) {
          final double[] actualValue = expensive();
          value = actualValue == null ? this.cached : actualValue;
          this.cached.set(value);
        }
      }
    }
    return (double[])(value == this.cached ? null : value);
  }

  private double[] expensive() {
    double[] result = new double[1000000];
    for (int i = 0; i < result.length; i++) {
      result[i] = Math.asin(i);
    }
    return result;
  }
}

Lombok 简化

import lombok.Getter;

public class GetterLazyExample {
  @Getter(lazy=true) private final double[] cached = expensive();

  private double[] expensive() {
    double[] result = new double[1000000];
    for (int i = 0; i < result.length; i++) {
      result[i] = Math.asin(i);
    }
    return result;
  }
}

🛠 配置

lombok.getter.lazy.flagUsage = [warning | error]

默认:未设置。如果配置的话,Lombok 会将使用@Getter(lazy=true)标记为警告或错误。

🔔 说明

你不应该直接访问字段,而是使用 Lombok 生成的 getter,因为该字段的类型将被修改为 AtomicReference。不要尝试直接访问 AtomicReference ;如果它指向自身,则该值已计算完毕,并且是 null 。如果引用指向 null ,则尚未计算该值。此行为在将来的版本中可能会更改。因此,请始终使用生成的 getter 来访问您的字段!

其他 Lombok 注解,例如 @ToString 将会调用该 getter 方法,即使使用了 doNotUseGetters=true 也不会生效。

转载请注明出处:码谱记录 » Lombok 其他特性