简体   繁体   English

具有相同键不变映射错误的多个条目

[英]Multiple entries with same key immutable map error

I have a below builder class which I am using from multithread application so I have made it thread safe. 我在多线程应用程序中使用了下面的构建器类,因此使它成为线程安全的。 Just for simplicity, I am showing only few fields here to demonstrate the problem. 为了简单起见,我仅在此处显示几个字段来演示该问题。

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
}

Below is how I make ClientKey and it works fine. 下面是我制作ClientKey ,它工作正常。

Map<String, String> testMap = new HashMap<String, String>();
testMap.put("hello", "world");
ClientKey keys = new ClientKey.Builder(12345L, 200).parameterMap(testMap).build();

Now when I try to clone the keys object as shown below, it throws exception. 现在,当我尝试克隆如下所示的keys对象时,它将引发异常。

ClientKey clonedKey = new ClientKey.Builder(keys).processName("hello").build();

It throws exception with error message as: java.lang.IllegalArgumentException: Multiple entries with same key: is_clientid=true and is_clientid=true 它将引发异常,并带有以下错误消息: 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();

How can I fix this problem? 我该如何解决这个问题? I want to make my map immutable but I also want to initialize with required fields as well and that I can only do it in the constructor of ClientKey class. 我想使地图不可变,但我也想用必填字段进行初始化,而且只能在ClientKey类的构造函数中进行ClientKey And it throws exception while cloning the ClientKey object. 并且在克隆ClientKey对象时会引发异常。

When you construct a ClientKey , the "is_clientid" key is put in the map. 构造ClientKeyClientKey "is_clientid"键放入地图中。 Therefore, if you call your ClientKey.Builder(ClientKey) constructor the putAll call will copy it to your new ImmutableMap.Builder instance. 因此,如果调用ClientKey.Builder(ClientKey)构造函数,则putAll调用会将其复制到新的ImmutableMap.Builder实例。 When you then build your cloned ClientKey , the ClientKey constructor will again try to add the same key to the map, which causes the exception. 然后,当您构建克隆的ClientKeyClientKey构造函数将再次尝试将相同的密钥添加到映射,这将导致异常。

The ImmutableMap.Builder could have been written in a different way, but it wasn't. ImmutableMap.Builder可以用不同的方式编写,但事实并非如此。 If you want to use it, you'll have to live with it. 如果要使用它,则必须使用它。

One solution is to not copy the entry with the "is_clientid" key to the new ImmutableMap.Builder in the constructor of your Builder . 一种解决方案是不使用"is_clientid"键将条目复制到Builder的构造函数中的新ImmutableMap.Builder Instead of this.parameterMap = ImmutableMap.<String, String>builder().putAll(key.parameterMap); 代替this.parameterMap = ImmutableMap.<String, String>builder().putAll(key.parameterMap); you write: 你写:

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());
    }
}

Another solution is to not use Guava's ImmutableMap.Builder , but a normal Java HashMap (it does not throw exception when you try to put a duplicate key in it, the old entry is simply overwritten). 另一个解决方案是不使用Guava的ImmutableMap.Builder ,而是使用普通的Java HashMap (当您尝试在其中放置重复键时它不会引发异常,只是简单地覆盖了旧条目)。 Then in your ClientKey constructor you write: 然后在ClientKey构造函数中编写:

this.parameterMap = Collections.unmodifiableMap(builder.parameterMap);

You could also write: 您还可以编写:

this.parameterMap = ImmutableMap.copyOf(builder.parameterMap);

but this makes an entire copy of the map, which may take some time for very large maps. 但这会制作完整的地图副本,对于非常大的地图可能要花一些时间。

A concluding remark: if all you want to do is copy a ClientKey , you do not need a builder; 结束语:如果您只想复制ClientKey ,则不需要构建器; idiomatic Java would use a copy constructor or the clone() method (although the latter is discouraged by some). 惯用的Java将使用复制构造函数或clone()方法(尽管有些人不建议使用后者)。

You are getting an exception because you're trying to set a value for the key is_clientid in the same ImmutableMap.Builder used by your single ClientKey.Builder class: 因为要在单个ClientKey.Builder类使用的同一ImmutableMap.Builder尝试为键is_clientid设置值, is_clientid出现了ClientKey.Builder

builder.parameterMap.put("is_clientid", (clientId == 0) ? "false" : "true");

As seen in the documentation : 文档所示

Associates key with value in the built map. 将键与已构建映射中的值相关联。 Duplicate keys are not allowed, and will cause build() to fail. 不允许使用重复的密钥,这将导致build()失败。

Don't re-use the same instance of ImmutableMap.Builder . 不要重复使用ImmutableMap.Builder的相同实例。

You can clone an object sort of like this instead: 您可以改为克隆这样的对象:

public ClientKey(ClientKey copyee) {
    // Copy fields here
    this.parameterMap = ImmutableMap.copyOf(copyee.parameterMap);
}

If you want to use some sort of builder object, you could do something like this: 如果要使用某种构建器对象,则可以执行以下操作:

public Builder(ClientKey copyee) {
    this.oldParameterMap = copyee.parameterMap;
}

public ClientKey build() {
    // Create new map here and pass it to new ClientKey somehow
    ImmutableMap.copyOf(oldParameterMap);
    return newKey;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM