2016-12-17 4 views
0

私はマルチスレッドアプリケーションから使用しているビルダークラスを持っていますので、スレッドセーフにしました。簡単にするために、ここでは問題を示すためのフィールドをいくつか示しています。同じキー不変マップエラーの複数のエントリ

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

?私はマップを不変にしたいと思っていますが、私はまた、必要なフィールドでも初期化したいと思います。それは、ClientKeyクラスのコンストラクタでのみ行うことができます。 ClientKeyオブジェクトのクローン作成中に例外がスローされます。

+0

エラーメッセージがスローされる場所を明確にすることはできますか? 'ClientKey clonedKey = new ClientKey.Builder(keys).processName(" hello ")。build();'キーを持っていなくても例外をスローする 'is_clientid '。コードの次の行はどこで起こっていますか? – jamesw1234

+0

これは本当にビルダーパターンのインスタンスですか? - 私は誰かが新しいオブジェクトのフィールドを設定するために専用のコンストラクタに "ビルダー"を渡すことはありませんでした。 – errantlinguist

+0

@ keys_1234例外はこの '' this.parameterMap = builder.parameterMap.build(); 'であり、' keys'オブジェクトを新しい 'clonedKey'にクローンしようとした場合にのみスローされます –

答えて

1

:あなたは、ビルダーオブジェクトのいくつかの並べ替えを使用したい場合、あなたはこのような何かを行うことができ

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

ClientKey"is_clientid"キーがマップに配置されます。したがって、ClientKey.Builder(ClientKey)コンストラクタに電話すると、putAllコールで新しいImmutableMap.Builderインスタンスにコピーされます。クローンされたClientKeyを構築すると、ClientKeyコンストラクターは同じキーをマップに追加しようとしますが、例外が発生します。

ImmutableMap.Builderは別の方法で書かれている可能性がありますが、そうではありませんでした。あなたがそれを使いたいなら、あなたはそれを持って生きなければならないでしょう。

"is_clientid"キーを持つエントリを、Builderのコンストラクタの新しいImmutableMap.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()); 
    } 
} 

別の解決策は、グアバのImmutableMap.Builderを使用しないことですが、通常のJava HashMapあなたはそれで重複キーを入れしようとすると、(それが例外をスローしない、古いエントリは単純に上書きされます)。そして、あなたが書いたあなたのClientKeyコンストラクタで:

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

をが、これは非常に大きなマップのために時間がかかる場合がありマップ、全体のコピーを作成します:

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

また書くことができます。

結論:ClientKeyをコピーするだけの場合は、ビルダーは必要ありません。慣用的なJavaは、コピーコンストラクタまたはclone()メソッドを使用します(ただし後者はいくつかでは推奨されません)。

+0

最初に述べた解決策をどこで使うべきですか? 'ClientKey'コンストラクタか' Builder'コンストラクタの下にもありますか? –

+0

@ user5447339答えを編集しました。スニペットはBuilderクラスのコンストラクタに入ります。 – Hoopje

+0

"Guavaの「ImmutableMap」を使用していません。問題はビルダーであり、マップではありません。結果が不変で、重複したキーを扱う必要がある場合は、 'ImmutableMap.copyOf(aHashMapUsedAsBuilder)'を使ってビルドすることができます。 – maaartinus

0

あなたは、単一のClientKey.Builderクラスで使用したのと同じImmutableMap.Builderにキーis_clientidの値を設定しようとしているので、あなたは例外を取得している:the documentationに見られるように

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

keyと組み込みマップのkeyを関連付けます。重複キーは許可されず、build()が失敗します。

ImmutableMap.Builderの同じインスタンスを再使用しないでください。

あなたはソートの代わりに、次のようにオブジェクトのクローンを作成することができます:あなたが構築した場合

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; 
} 
+0

私はそれが例外をスローする理由を理解することができましたが、私の場合にそれを修正する方法は? –

+0

再度、**は、 'ImmutableMap.Builder' **の同じインスタンスを再利用しません。 – errantlinguist

+0

これを理解できるように、同じインスタンスを再利用しないという例を提供できますか? –

関連する問題