ImmutableMapまたはMapを返す方が良いですか?


90

Mapを返すメソッドを書いているとしましょう。例えば:

public Map<String, Integer> foo() {
  return new HashMap<String, Integer>();
}

しばらく考えた結果、このマップが作成された後で変更する理由はないと判断しました。したがって、私はImmutableMapを返したいと思います。

public Map<String, Integer> foo() {
  return ImmutableMap.of();
}

戻り値の型を汎用のMapのままにするか、ImmutableMapを返すように指定する必要がありますか?

一方で、これがまさにインターフェースが作成された理由です。実装の詳細を非表示にします。
一方、このままにしておくと、他の開発者はこのオブジェクトが不変であるという事実を見逃す可能性があります。したがって、私は不変オブジェクトの主要な目標を達成しません。変更可能なオブジェクトの数を最小限に抑えてコードをより明確にするため。最悪の場合でも、しばらくすると、誰かがこのオブジェクトを変更しようとする可能性があり、これによりランタイムエラーが発生します(コンパイラーは警告しません)。


2
私は誰かが最終的にこの質問に議論に対してオープンすぎるとタグ付けするのではないかと思います。「WDYT」の代わりに、より具体的な質問を入力できますか?「大丈夫ですか…?」たとえば、「通常のマップを返す際に問題や考慮事項はありますか?」そして「どのような代替案が存在しますか?」
アッシュ

実際に変更可能なマップを返す必要があると後で決定した場合はどうなりますか?
user253751

3
@immibis lol、またはその問題のリスト?
djechlin

3
本当にGuavaの依存関係をAPIに焼きたいですか?下位互換性を壊さずに変更することはできません。
user2357112はMonica

1
質問はおおざっぱで、多くの重要な詳細が欠けていると思います。たとえば、とにかく(目に見える)依存関係としてGuavaがあるかどうか。すでに持っている場合でも、コードスニペットはpesudocodeであり、実際に達成したいことを伝えていません。あなたは確かに書いていないでしょうreturn ImmutableMap.of(somethingElse)。代わりに、ImmutableMap.of(somethingElse)フィールドとして格納し、このフィールドのみを返します。このすべてがここのデザインに影響を与えます。
Marco13

回答:


70
  • 公開されているAPIを作成していて、その不変性が設計の重要な側面である場合、返されるマップが不変であることをメソッドの名前が明確に示すことによって、または具体的なタイプを返すことによって、それを明確にします地図。私の意見では、javadocで言及するだけでは十分ではありません。

    あなたは明らかにGuava実装を使用しているので、私はドキュメントを調べましたが、それは抽象クラスなので、実際の具象型に少し柔軟性を与えます。

  • 内部ツール/ライブラリを作成している場合は、単純なを返すだけで十分Mapです。人々は彼らが呼んでいるコードの内部について知っているか、少なくともそれに簡単にアクセスできるでしょう。

私の結論は、露骨な表現は良いことであり、物事を偶然に任せないことです。


1
1つの追加インターフェース、1つの追加抽象クラス、およびこの抽象クラスを拡張する1つのクラス。これは、OPが要求するような単純なこと、つまり、メソッドがMapインターフェース型またはImmutableMap実装型を返すかどうかという面倒です。
fps

20
Guavaのを参照している場合ImmutableMap、GuavaチームのアドバイスはImmutableMap、セマンティック保証を備えたインターフェースを検討することであり、その型を直接返す必要があるということです。
Louis Wasserman

1
@LouisWasserman mm、質問がGuava実装へのリンクを提供しているのを見ませんでした。次にスニペットを削除します。ありがとう
Dici

3
私がルイに同意します。不変オブジェクトであるマップを返すことを意図している場合は、返されるオブジェクトがそのタイプであることを確認してください。何らかの理由でそれが不変の型であるという事実を覆い隠したい場合は、独自のインターフェースを定義してください。マップとして残すことは誤解を招きます。
WSimpson

1
@WSimpson私はそれが私の答えが言っていることだと信じています。ルイスは私が追加したスニペットについて話していましたが、削除しました。
Dici、2016年

38

ImmutableMap戻り値の型として持っている必要があります。Map実装によってサポートされていないメソッドが含まれImmutableMap(例えばput)とマークされている@deprecated中をImmutableMap

非推奨のメソッドを使用するとコンパイラの警告が表示され、ほとんどのIDEは非推奨のメソッドを使用しようとすると警告します。

この高度な警告は、何かが間違っているという最初のヒントとして、実行時の例外よりも望ましいです。


1
これは私の意見では最も賢明な答えです。Mapインターフェースはコントラクトであり、ImmutableMapは明らかにその重要な部分に違反しています。この場合、戻り値の型としてMapを使用しても意味がありません。
TonioElGringo

@TonioElGringoそれではどうCollections.immutableMapですか?
OrangeDog 2016年

@TonioElGringo ImmutableMapが実装しないメソッドは、インターフェースのドキュメントdocs.oracle.com/javase/7/docs/api/java/util/…で明示的にオプションとしてマークされていることに注意してください。したがって、(適切なjavadocsを使用して)Mapを返すことは依然として意味があると思います。それでも、上記の理由により、ImmutableMapを明示的に返すことにしました。
AMT 2016年

3
@TonioElGringoそれは何も違反していません、それらのメソッドはオプションです。のようにListList::add有効である保証はありません。お試しくださいArrays.asList("a", "b").add("c");。実行時に失敗するという兆候はありませんが、失敗します。少なくともグアバはあなたに頭を上げます。
njzk2

14

一方、このままにしておくと、他の開発者はこのオブジェクトが不変であるという事実を見落とす可能性があります。

javadocsでそれを言及する必要があります。開発者はそれらを読んでいますね。

したがって、私は不変オブジェクトの主要な目標を達成しません。変更可能なオブジェクトの数を最小限に抑えてコードをより明確にするため。最悪の場合でも、しばらくすると、誰かがこのオブジェクトを変更しようとする可能性があり、これによりランタイムエラーが発生します(コンパイラーは警告しません)。

テストされていないコードを公開している開発者はいません。そしてそれをテストすると、理由だけでなく、不変のマップに書き込もうとしたファイルと行も確認できる例外がスローされます。

ただし、Map不変になるのはそれ自体だけであり、それが含むオブジェクトではありません。


10
「開発者は自分のコードをテストせずに公開していません。」これに賭けますか?この文が真実であれば、公開ソフトウェアには(私の推測では)バグがはるかに少ないでしょう。「ほとんどの開発者...」が本当であるかどうかさえ私は確信しません。そして、はい、それは残念です。
トム

1
わかりました、トムは正しいです、あなたが何を言おうとしているのかはわかりませんが、あなたが実際に書いたことは明らかに誤りです。(また:ジェンダーインクルーシブ性のために
微調整する

@トム私はあなたがこの答えで皮肉を逃したと思います
キャプテンフォジェッティ2016

10

このままにしておくと、他の開発者がこのオブジェクトが不変であるという事実を見逃す可能性があります

それは事実ですが、他の開発者はコードをテストして、それがカバーされていることを確認する必要があります。

それでも、これを解決するための2つのオプションがあります。

  • Javadocを使用する

    @return a immutable map
  • 説明的なメソッド名を選択した

    public Map<String, Integer> getImmutableMap()
    public Map<String, Integer> getUnmodifiableEntries()

    具体的な使用例については、メソッドに名前を付けることもできます。例えば

    public Map<String, Integer> getUnmodifiableCountByWords()

他に何ができますか?!

あなたは返すことができます

  • 写す

    private Map<String, Integer> myMap;
    
    public Map<String, Integer> foo() {
      return new HashMap<String, Integer>(myMap);
    }

    このアプローチは、多くのクライアントがマップを変更することが予想され、マップに含まれるエントリが少ない場合にのみ使用してください。

  • CopyOnWriteMap

    copy on writeコレクションは通常、
    同時実行性を処理する必要がある場合に使用されます。ただし、CopyOnWriteMapは変更操作(追加、削除など)で内部データ構造のコピーを作成するため、この概念は状況にも役立ちます。

    この場合、変更操作を除いて、すべてのメソッド呼び出しを基になるマップに委譲する、マップの薄いラッパーが必要です。変更操作が呼び出されると、基になるマップのコピーが作成され、それ以降の呼び出しはすべてこのコピーに委任されます。

    このアプローチは、一部のクライアントがマップを変更することが予想される場合に使用する必要があります。

    悲しいことに、javaにはそのようなはありませんCopyOnWriteMap。しかし、サードパーティを見つけたり、自分で実装したりすることもできます。

最後に、マップ内の要素がまだ変更可能である可能性があることを覚えておく必要があります。


8

間違いなくImmutableMapを返します。

  • メソッドシグネチャ(戻り値の型を含む)は自己文書化する必要があります。コメントはカスタマーサービスのようなものです。クライアントがそれらに依存する必要がある場合、主要製品に欠陥があります。
  • 何かがインターフェースであるかクラスであるかは、それを拡張または実装するときにのみ関係します。インスタンス(オブジェクト)が与えられると、99%の時間、クライアントコードは何かがインターフェイスであるかクラスであるかを知らないか気にしません。私は最初、ImmutableMapがインターフェースであると想定しました。リンクをクリックして初めて、それがクラスであることがわかりました。

5

クラスによって異なります。Guava ImmutableMapは、変更可能なクラスへの不変のビューになることを意図していません。クラスが不変でありImmutableMap、基本的にである構造を持っている場合は、戻り値の型を作成しImmutableMapます。ただし、クラスが変更可能な場合は変更しないでください。これがあれば:

public ImmutableMap<String, Integer> foo() {
    return ImmutableMap.copyOf(internalMap);
}

グアバは毎回マップをコピーします。それは遅いです。しかし、internalMapすでにだったImmutableMap場合、それはまったく問題ありません。

クラスをImmutableMapreturnに制限しない場合は、代わりに次のCollections.unmodifiableMapように返すことができます。

public Map<String, Integer> foo() {
    return Collections.unmodifiableMap(internalMap);
}

これは地図への不変のビューであることに注意してください。internalMap変更された場合、キャッシュされたのコピーも変更されますCollections.unmodifiableMap(internalMap)。ただし、ゲッターにはまだ好んで使用しています。


1
これらは良い点ですが、完全を期すために、私はこの回答を気に入っています。これはunmodifiableMap、参照の所有者だけが変更できないことを説明しています。これはビューであり、バッキングマップの所有者はバッキングマップを変更してからビューを変更できます。これは重要な考慮事項です。
davidbak 2016年

@ davidbackがコメントしたように、の使用には十分注意してくださいCollections.unmodifiableMap。このメソッドは、別個の新しいマップオブジェクトを作成するのではなく、元のマップのビューを返します。そのため、元のマップを参照する他のコードはそれを変更でき、変更できないと思われるマップの所有者は、マップを実際にその下から変更できることを学習します。私はその事実に自分で噛まれました。私はマップのコピーを返すか、グアバを使用することを学びましたImmutableMap
バジルブルク2016年

5

これは正確な質問には答えませんが、マップを返す必要があるかどうかを検討する価値はあります。マップが不変の場合、提供される主要なメソッドはget(key)に基づいています。

public Integer fooOf(String key) {
    return map.get(key);
}

これにより、APIが大幅に強化されます。マップが実際に必要な場合は、エントリのストリームを提供することにより、APIのクライアントに任せることができます。

public Stream<Map.Entry<String, Integer>> foos() {
    map.entrySet().stream()
}

次に、クライアントは、必要に応じて独自の不変マップまたは可変マップを作成するか、独自のマップにエントリを追加できます。クライアントが値が存在するかどうかを知る必要がある場合は、代わりにオプションを返すことができます:

public Optional<Integer> fooOf(String key) {
    return Optional.ofNullable(map.get(key));
}

-1

不変マップはマップの一種です。したがって、戻り値の型のMapを残しても問題ありません。

ユーザーが返されたオブジェクトを変更しないようにするために、メソッドのドキュメントでは、返されたオブジェクトの特性を説明できます。


-1

これは間違いなく意見の問題ですが、ここでのより良いアイデアは、マップクラスのインターフェイスを使用することです。このインターフェイスは不変であると明示的に言う必要はありませんが、インターフェイスで親クラスのセッターメソッドを公開しない場合、メッセージは同じになります。

次の記事をご覧ください。

アンディ・ギブソン


1
不必要なラッパーは有害と見なされます。
djechlin

インターフェースは十分なので、ここでもラッパーを使用しません。
WSimpson 2016年

これが正しいことだと私は感じていますが、ImmutableMapはすでにImmutableMapInterfaceを実装していますが、技術的にはわかりません。
djechlin

要点はGuava開発者が意図したものではなく、この開発者がアーキテクチャをカプセル化し、ImmutableMapの使用を公開したくないという事実です。これを行う1つの方法は、インターフェイスを使用することです。ご指摘のとおりラッパーを使用する必要はないため、お勧めしません。Mapを返すことは誤解を招くので望ましくありません。したがって、目標がカプセル化である場合、インターフェイスが最良のオプションであると思われます。
WSimpson

Mapインターフェースを拡張(または実装)しないものを返すことの欠点は、Mapキャストしないとそれを予期するAPI関数にそれを渡すことができないことです。これには、他のタイプのマップのあらゆる種類のコピーコンストラクターが含まれます。それに加えて、変更可能性がインターフェースによって「隠されている」だけの場合、ユーザーはコードをそのようにキャストして壊すことで、常にこれを回避できます。
ハルク
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.