如何只在构造函数中初始化一个变量一次?

How to initialize a variable in the constructor only once?

我有一个构建器模式,在该模式中,我从客户那里获取了一些参数,并在此基础上构建了我的构建器 class,然后该构建器 class 被传递到我们的底层库,并且然后我的图书馆将使用它。

public final class KeyHolder {
  private final String clientId;
  private final String deviceId;
  private final int processId;
  private final Cache<String, List<Response>> userCache;
  private static final long MAXIMUM_CACHE_SIZE = 5000000;
  private static final long EXPIRE_AFTER_WRITE = 120; // this is in seconds

  private KeyHolder(Builder builder) {
    this.clientId = builder.clientId;
    this.deviceId = builder.deviceId;
    this.processId = builder.processId;
    this.maximumCacheSize = builder.maximumCacheSize;
    this.expireAfterWrite = builder.expireAfterWrite;

    // how to execute this line only once
    this.userCache =
        CacheBuilder
            .newBuilder()
            .maximumSize(maximumCacheSize)
            .expireAfterWrite(expireAfterWrite, TimeUnit.SECONDS)
            .removalListener(
                RemovalListeners.asynchronous(new CustomListener(),
                    Executors.newSingleThreadScheduledExecutor())).build();

  }

  public static class Builder {
    protected final int processId;
    protected String clientId = null;
    protected String deviceId = null;
    protected long maximumCacheSize = MAXIMUM_CACHE_SIZE;
    protected long expireAfterWrite = EXPIRE_AFTER_WRITE;


    public Builder(int processId) {
      this.processId = processId;
    }

    public Builder setClientId(String clientId) {
      this.clientId = clientId;
      return this;
    }

    public Builder setDeviceId(String deviceId) {
      this.deviceId = deviceId;
      return this;
    }

    public Builder setMaximumCacheSize(long size) {
      this.maximumCacheSize = size;
      return this;
    }

    public Builder setExpiryTimeAfterWrite(long duration) {
      this.expireAfterWrite = duration;
      return this;
    }

    public KeyHolder build() {
      return new KeyHolder(this);
    }
  }

 // getters here
}

每次调用我们的库时,他们都会创建一个新的 KeyHolder 生成器 class 并将其传递给我们的库。 processIdclientIddeviceId 会随每次调用而变化,但 maximumCacheSizeexpireAfterWrite 会在每次调用时保持不变。正如你在上面看到的,我在这里使用番石榴缓存,因为他们每次都在创建 KeyHolder 构建器 class 我如何确保下面的行在我的构造函数中只执行一次?

    this.userCache =
        CacheBuilder
            .newBuilder()
            .maximumSize(maximumCacheSize)
            .expireAfterWrite(expireAfterWrite, TimeUnit.SECONDS)
            .removalListener(
                RemovalListeners.asynchronous(new CustomListener(),
                    Executors.newSingleThreadScheduledExecutor())).build();

由于现在使用当前代码,每次调用都会执行它,每次我都会在我的库中获得一个新的 guava 缓存,因此无论之前使用此 guava 缓存在我的库中缓存的任何条目都将丢失.

如何只初始化一个特定的变量,然后它应该忽略传递给它的值?

更新:

public class DataClient implements Client {
    private final ExecutorService executor = Executors.newFixedThreadPool(10);

    // for synchronous call
    @Override
    public List<Response> executeSync(KeyHolder key) {
        Cache<String, List<Response>> userCache = key.getUserCache();
        List<Response> response = userCache.getIfPresent(key.getUUID());
        if (CollectionUtils.isNotEmpty(response)) {
          return response;
        }
        // if not in cache, then normally call the flow and populate the cache
        List<Response> dataResponse = null;
        Future<List<Response>> future = null;
        try {
            future = executeAsync(key);
            dataResponse = future.get(key.getTimeout(), TimeUnit.MILLISECONDS);
            userCache.put(key.getUUID(), dataResponse);
        } catch (TimeoutException ex) {
            // log error and return DataResponse
        } catch (Exception ex) {
            // log error and return DataResponse
        }

        return dataResponse;
    }
}

首先,make it static

private static Cache<String, List<Response>> userCache;

然后,没有初始化的才初始化

private KeyHolder(Builder builder) {
    ...

    if (userCache == null) {
        userCache = CacheBuilder
            .newBuilder()
            .maximumSize(maximumCacheSize)
            .expireAfterWrite(expireAfterWrite, TimeUnit.SECONDS)
            .removalListener(
                RemovalListeners.asynchronous(new CustomListener(), Executors.newSingleThreadScheduledExecutor())
            ).build();
    }
}

如果您真的不需要在 run-time 自定义缓存(通过使用传入的参数,我建议使用类似

的内容
// initialize the cache while defining it
// replace maximumCacheSize and expireAfterWrite with constants
private static final Cache... = CacheBuilder.newBuilder()...;

并将其从构造函数中删除。

您可以将变量设置为静态,如果它为 null,则仅在第一次调用时对其进行初始化。像这样:

public final class KeyHolder {
  private final String clientId;
  private final String deviceId;
  private final int processId;

  //this var is now static, so it is shared across all instances
  private static Cache<String, List<Response>> userCache = null;  

  private static final long MAXIMUM_CACHE_SIZE = 5000000;
  private static final long EXPIRE_AFTER_WRITE = 120; // this is in seconds

  private KeyHolder(Builder builder) {
    this.clientId = builder.clientId;
    this.deviceId = builder.deviceId;
    this.processId = builder.processId;
    this.maximumCacheSize = builder.maximumCacheSize;
    this.expireAfterWrite = builder.expireAfterWrite;

    //this will be executed only the first time, when the var is null
    if (userCache == null) {
      userCache =
        CacheBuilder
            .newBuilder()
            .maximumSize(maximumCacheSize)
            .expireAfterWrite(expireAfterWrite, TimeUnit.SECONDS)
            .removalListener(
                RemovalListeners.asynchronous(new CustomListener(),
                    Executors.newSingleThreadScheduledExecutor())).build();

  }

  //rest of your class below

如果您只想设置一次缓存,为什么每个 KeuHolder 对象都尝试构建它?事实上,连KeyHolder#Builder都公开了帮助构建缓存的方法,这些方法只能用一次。

这是非常值得怀疑的。如果第一个 KeyHolder 没有指定缓存详细信息怎么办?我的意思是,它不是被迫的(你没有正确使用构建器模式,最后会详细说明)。

解决此问题的第一步是确保在开始创建 KeyHolder 对象之前设置缓存。您可以通过创建一个静态工厂并使 userCache static:

来做到这一点
class KeyHolder {
    private static Map<String, List<Response>> userCache;

    public static KeyHolder.Builder newBuilder(int id) {
        if(userCache == null) {
            userCache = ...;
        }

        return new Builder(id);
    }
}

但是您可能已经从我的评论中了解到,这只是一个 band-aid 问题。每次我们想要创建一个新的 KeyHolder 时都会检查 userCache,这不需要发生。

相反,您应该将缓存与 KeyHolder 完全分离。为什么它无论如何都需要知道缓存?

您的缓存属于 DataClient:

class DataClient {
    private Map<String, List<Response>> userCache;

    public List<Response> executeSync(KeyHolder key) {
        List<Response> response = userCache.getIfPresent(key.getUUID());
        //...
    }
}

您可以通过 DataClient 构造函数接受设置,或者使用已经指定的设置将缓存传递到 DataClient


关于构建器模式的使用,请记住我们使用它的原因:Java 缺少可选参数。

这就是构建器很常见的原因:它们允许我们通过方法指定可选数据。

您正在将缓存设置等关键信息指定为可选参数(生成器方法)。如果您不需要这些信息,您应该只使用构建器方法,而缓存信息绝对是应该需要的。我会质疑 deviceIdclientId 是如何可选的,看看唯一需要的数据是 productId.