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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/41195885/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 05:44:34  来源:igfitidea点击:

Multiple entries with same key immutable map error

javamultithreadingguavabuilderbuilder-pattern

提问by Hoopje

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 ClientKeyand 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 keysobject 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 ClientKeyclass. And it throws exception while cloning the ClientKeyobject.

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

回答by Hoopje

When you construct a ClientKey, the "is_clientid"key is put in the map. Therefore, if you call your ClientKey.Builder(ClientKey)constructor the putAllcall will copy it to your new ImmutableMap.Builderinstance. When you then build your cloned ClientKey, the ClientKeyconstructor will again try to add the same key to the map, which causes the exception.

当您构造 a 时ClientKey"is_clientid"键被放入映射中。因此,如果您调用ClientKey.Builder(ClientKey)构造函数,该putAll调用会将其复制到您的新ImmutableMap.Builder实例。当您然后构建您的 cloned 时ClientKeyClientKey构造函数将再次尝试将相同的键添加到映射中,这会导致异常。

The ImmutableMap.Buildercould have been written in a different way, but it wasn't. If you want to use it, you'll have to live with it.

ImmutableMap.Builder会已被写入以不同的方式,但事实并非如此。如果你想使用它,你就必须忍受它。

One solution is to not copy the entry with the "is_clientid"key to the new ImmutableMap.Builderin the constructor of your Builder. Instead of this.parameterMap = ImmutableMap.<String, String>builder().putAll(key.parameterMap);you write:

一种解决方案是不可复制与进入"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());
    }
}

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). Then in your ClientKeyconstructor you write:

另一个解决方案是不使用 Guava 的ImmutableMap.Builder,而是使用普通的 Java HashMap(当您尝试将重复的键放入其中时它不会抛出异常,旧条目会被简单地覆盖)。然后在你的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; idiomatic Java would use a copy constructor or the clone()method (although the latter is discouraged by some).

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

回答by errantlinguist

You are getting an exception because you're trying to set a value for the key is_clientidin the same ImmutableMap.Builderused by your single ClientKey.Builderclass:

你得到一个例外,因为你想为重点设置的值is_clientid在同ImmutableMap.Builder你单独使用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;
}