我有一个下面的构建器类,该类是我在多线程应用程序中使用的,因此我使它成为线程安全的。为了简单起见,我仅在此处显示几个字段来演示该问题。

public final class ClientKey {
  private final long userId;
  private final int clientId;
  private final String processName;
  private final Map<String, String> parameterMap;

  private ClientKey(Builder builder) {
    this.userId = builder.userId;
    this.clientId = builder.clientId;
    this.processName = builder.processName;
    // initializing the required fields
    // and below line throws exception once I try to clone the `ClientKey` object
    builder.parameterMap.put("is_clientid", (clientId == 0) ? "false" : "true");
    this.parameterMap = builder.parameterMap.build();
  }

  public static class Builder {
    private final long userId;
    private final int clientId;
    private String processName;
    private ImmutableMap.Builder<String, String> parameterMap = ImmutableMap.builder();

    // this is for cloning
    public Builder(ClientKey key) {
      this.userId = key.userId;
      this.clientId = key.clientId;
      this.processName = key.processName;
      this.parameterMap =
          ImmutableMap.<String, String>builder().putAll(key.parameterMap);
    }

    public Builder(long userId, int clientId) {
      this.userId = userId;
      this.clientId = clientId;
    }

    public Builder parameterMap(Map<String, String> parameterMap) {
      this.parameterMap.putAll(parameterMap);
      return this;
    }

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

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

  // getters
}

下面是我制作ClientKey的方法,它工作正常。
Map<String, String> testMap = new HashMap<String, String>();
testMap.put("hello", "world");
ClientKey keys = new ClientKey.Builder(12345L, 200).parameterMap(testMap).build();

现在,当我尝试如下所示克隆keys对象时,它将引发异常。
ClientKey clonedKey = new ClientKey.Builder(keys).processName("hello").build();

它将引发异常,并显示错误消息:java.lang.IllegalArgumentException: Multiple entries with same key: is_clientid=true and is_clientid=true
builder.parameterMap.put("is_clientid", (clientId == 0) ? "false" : "true");
// from below line exception is coming
this.parameterMap = builder.parameterMap.build();

我该如何解决这个问题?我想使 map 不可变,但我也想用必填字段进行初始化,而且我只能在ClientKey类的构造函数中进行初始化。并且在克隆ClientKey对象时会引发异常。

最佳答案

当您构造ClientKey时,"is_clientid" key 被放置在 map 中。因此,如果您调用ClientKey.Builder(ClientKey)构造函数,则putAll调用会将其复制到新的ImmutableMap.Builder实例中。然后,当您构建克隆的ClientKey时,ClientKey构造函数将再次尝试向 map 添加相同的键,这会导致异常。
ImmutableMap.Builder可以用不同的方式编写,但事实并非如此。如果要使用它,则必须使用它。

一种解决方案是不使用"is_clientid"键将条目复制到ImmutableMap.Builder的构造函数中的新Builder中。而不是this.parameterMap = ImmutableMap.<String, String>builder().putAll(key.parameterMap);,您可以编写:

this.parameterMap = new ImmutableMap.Builder<>();
for (Map.Entry<String,String> entry : key.parameterMap.entrySet()) {
    if (!"is_clientid".equals(entry.getKey()) {
        this.parameterMap.put(entry.getKey(), entry.getValue());
    }
}

另一个解决方案是不使用Guava的ImmutableMap.Builder,而是使用普通的Java HashMap(当您尝试将重复的 key 放入其中时,它不会引发异常,只是简单地覆盖了旧条目)。然后在ClientKey构造函数中编写:
this.parameterMap = Collections.unmodifiableMap(builder.parameterMap);

您还可以编写:
this.parameterMap = ImmutableMap.copyOf(builder.parameterMap);

但这会制作完整的 map 副本,对于非常大的 map 可能要花费一些时间。

结束语:如果您只想复制ClientKey,则不需要构建器;惯用的Java将使用复制构造函数或clone()方法(尽管有些人不建议使用后者)。

10-06 01:53