如何只在构造函数中初始化一个变量一次?
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 并将其传递给我们的库。 processId
、clientId
、deviceId
会随每次调用而变化,但 maximumCacheSize
和 expireAfterWrite
会在每次调用时保持不变。正如你在上面看到的,我在这里使用番石榴缓存,因为他们每次都在创建 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;
}
}
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 缺少可选参数。
这就是构建器很常见的原因:它们允许我们通过方法指定可选数据。
您正在将缓存设置等关键信息指定为可选参数(生成器方法)。如果您不需要这些信息,您应该只使用构建器方法,而缓存信息绝对是应该需要的。我会质疑 deviceId
和 clientId
是如何可选的,看看唯一需要的数据是 productId
.
我有一个构建器模式,在该模式中,我从客户那里获取了一些参数,并在此基础上构建了我的构建器 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 并将其传递给我们的库。 processId
、clientId
、deviceId
会随每次调用而变化,但 maximumCacheSize
和 expireAfterWrite
会在每次调用时保持不变。正如你在上面看到的,我在这里使用番石榴缓存,因为他们每次都在创建 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;
}
}
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 缺少可选参数。
这就是构建器很常见的原因:它们允许我们通过方法指定可选数据。
您正在将缓存设置等关键信息指定为可选参数(生成器方法)。如果您不需要这些信息,您应该只使用构建器方法,而缓存信息绝对是应该需要的。我会质疑 deviceId
和 clientId
是如何可选的,看看唯一需要的数据是 productId
.