web-dev-qa-db-ja.com

JavaのHashMapオブジェクトとMapオブジェクトの違いは何ですか?

私が作成した次のマップの違いは何ですか(別の質問では、人々は一見同じようにそれらを使用して答えました、そして、それらがどう違うか/どう違うか疑問に思います):

HashMap<String, Object> map = new HashMap<String, Object>();
Map<String, Object> map = new HashMap<String, Object>();
322
Tony Stark

オブジェクト間に違いはありません。どちらの場合もHashMap<String, Object>があります。 interfaceにはオブジェクトに違いがあります。前者の場合、インターフェースはHashMap<String, Object>ですが、後者の場合はMap<String, Object>です。しかし、基礎となるオブジェクトは同じです。

Map<String, Object>を使用する利点は、使用しているコードとの契約を破ることなく、基になるオブジェクトを別の種類のマップに変更できることです。 HashMap<String, Object>として宣言した場合、基盤となる実装を変更する場合は契約を変更する必要があります。


例:このクラスを書いたとしましょう:

class Foo {
    private HashMap<String, Object> things;
    private HashMap<String, Object> moreThings;

    protected HashMap<String, Object> getThings() {
        return this.things;
    }

    protected HashMap<String, Object> getMoreThings() {
        return this.moreThings;
    }

    public Foo() {
        this.things = new HashMap<String, Object>();
        this.moreThings = new HashMap<String, Object>();
    }

    // ...more...
}

クラスには、サブクラスと(アクセサメソッドを介して)共有するstring-> objectの内部マップがいくつかあります。そもそもHashMapsで書いたとしましょう。これは、クラスを記述するときに使用する適切な構造だと思うからです。

後で、Maryはそれをサブクラス化するコードを作成します。彼女はthingsmoreThingsの両方で何かする必要があるので、当然、彼女はそれを共通のメソッドに入れ、メソッドを定義するときにgetThings/getMoreThingsで使用したのと同じ型を使用します。

class SpecialFoo extends Foo {
    private void doSomething(HashMap<String, Object> t) {
        // ...
    }

    public void whatever() {
        this.doSomething(this.getThings());
        this.doSomething(this.getMoreThings());
    }

    // ...more...
}

後で、実際には、TreeMapHashMapの代わりにFooを使用する方が良いと判断しました。 FooHashMapに変更して、TreeMapを更新します。これで、SpecialFooはもうコンパイルされません。これは、契約を破ったためです。FooHashMapsを提供すると言っていましたが、代わりにTreeMapsを提供しています。したがって、今すぐSpecialFooを修正する必要があります(この種のことはコードベース全体に波及する可能性があります)。

私の実装がHashMapを使用していることを共有するための本当に正当な理由がなければ(そしてそれは起こります)、私がすべきだったのは、getThingsgetMoreThingsMap<String, Object>を返すこととして宣言することでした。実際、Foo内であっても、他の何かを行う正当な理由がない限り、おそらくthingsmoreThingsMap/HashMapではなくTreeMapとして宣言する必要があります。

class Foo {
    private Map<String, Object> things;             // <== Changed
    private Map<String, Object> moreThings;         // <== Changed

    protected Map<String, Object> getThings() {     // <== Changed
        return this.things;
    }

    protected Map<String, Object> getMoreThings() { // <== Changed
        return this.moreThings;
    }

    public Foo() {
        this.things = new HashMap<String, Object>();
        this.moreThings = new HashMap<String, Object>();
    }

    // ...more...
}

Map<String, Object>をどこでも使用できるようになったことに注意してください。実際のオブジェクトを作成するときにのみ具体的になります。

もし私がそれをしたなら、メアリーはこれをしたでしょう。

class SpecialFoo extends Foo {
    private void doSomething(Map<String, Object> t) { // <== Changed
        // ...
    }

    public void whatever() {
        this.doSomething(this.getThings());
        this.doSomething(this.getMoreThings());
    }
}

...そしてFooを変更してもSpecialFooのコンパイルは停止しません。

インターフェース(および基本クラス)を使用して必要な場合のみを明らかにし、必要に応じて変更を加えるための柔軟性を内部に保持します。一般的に、参照はできるだけ基本的なものにする必要があります。 HashMapであることを知る必要がない場合は、単にMapと呼びます。

これは盲目的なルールではありませんが、一般的に、最も一般的なインターフェースへのコーディングは、より具体的なものへのコーディングよりも脆弱ではありません。覚えていたら、Fooで失敗するようにMaryを設定するSpecialFooを作成しなかったでしょう。 Maryを覚えていれば、Fooを台無しにしたとしても、彼女はMapの代わりにHashMapを使用してプライベートメソッドを宣言し、Fooの契約を変更してもコードには影響しませんでした。

それができない場合もあれば、具体的に説明する必要がある場合もあります。しかし、あなたがそうする理由がない限り、最も特定性の低いインターフェースに向かって誤ります。

399
T.J. Crowder

MapHashMap が実装するインターフェースです。違いは、2つ目の実装ではHashMapへの参照はMapインターフェースで定義された関数の使用のみを許可するのに対し、1つ目はHashMap(Mapインターフェースを含む)での任意のパブリック関数の使用を許可するということです。

Sunのインタフェースチュートリアル を読むと、おそらくもっと意味があるでしょう。

54
Graphics Noob

enter image description here

Mapには以下の実装があります。

  1. HashMap Map m = new HashMap();

  2. LinkedHashMap Map m = new LinkedHashMap();

  3. ツリーマップMap m = new TreeMap();

  4. WeakHashMap Map m = new WeakHashMap();

1つのメソッドを作成したとします(これは単なる擬似コードです)。

public void HashMap getMap(){
   return map;
}

プロジェクト要件が変更されたとします。

  1. メソッドはマップの内容を返す必要があります - HashMapを返す必要があります。
  2. メソッドはマップキーを挿入順に返す必要があります - 戻り値の型HashMapLinkedHashMapに変更する必要があります。
  3. メソッドはマップキーをソートされた順序で返すべきです - 戻り値の型LinkedHashMapTreeMapに変更する必要があります。

メソッドがMapインターフェースを実装するものではなく特定のクラスを返す場合は、毎回getMap()メソッドの戻り型を変更する必要があります。

しかし、Javaの多態性機能を使用し、特定のクラスを返す代わりにインターフェースMapを使用すると、コードの再利用性が向上し、要件変更の影響が少なくなります。

20
atish shimpi

私はただ受け入れられた答えのコメントとしてこれをやろうとしていました、しかしそれはあまりにもファンキーになりました(改行を持っていないのは嫌いです)

ああ、違いは、一般的には、Mapはそれに関連付けられている特定のメソッドを持っているということです。しかし、HashMapなど、さまざまな方法やマップの作成方法があり、これらのさまざまな方法で、すべてのマップが持っているわけではない独自の方法が提供されます。

正確に - そして、あなたはいつも可能な限り最も一般的なインターフェースを使いたいのです。 ArrayListとLinkedListを比較してください。使い方は大きく異なりますが、「リスト」を使えば、すぐに切り替えることができます。

実際、イニシャライザの右側をより動的な文に置き換えることができます。このようなものはどうですか。

List collection;
if(keepSorted)
    collection=new LinkedList();
else
    collection=new ArrayList();

このように、コレクションを挿入ソートで埋める場合は、リンクリストを使用します(配列リストへの挿入ソートは犯罪です)。ただし、ソートする必要がなく、単に追加するだけの場合は、あなたはArrayListを使う(他の操作のためにより効率的)。

コレクションは最良の例ではないので、これはここではかなり大きなストレッチですが、OO designで最も重要な概念の1つは、まったく同じコードでさまざまなオブジェクトにアクセスするためのインタフェースファサードの使用です。

コメントに返信して編集:

下記のあなたの地図のコメントに関しては、あなたがMapからHashMap(目的を完全に無効にする)にコレクションをキャストしない限り、 "Map"インターフェースを使うYesはそれらのメソッドだけにあなたを制限します。

多くの場合、オブジェクトを作成し、特定のタイプ(HashMap)を使用して入力することです。ある種の "create"または "initialize"メソッドでは、そのメソッドは "Map"を返します。これ以上HashMapとして扱われます。

ところで、キャストする必要がある場合は、おそらく間違ったインターフェースを使用しているか、コードが十分に構造化されていません。コードの1つのセクションでそれを "HashMap"として扱い、もう1つのセクションでそれを "Map"として扱うことは許容されますが、これは "下へ"流れるはずです。あなたがキャストしないように。

また、インタフェースによって示される役割の半きれいな側面にも注意してください。 LinkedListは優れたスタックまたはキューを作成し、ArrayListは優れたスタックを作成しますが、恐ろしいキュー(削除するとリスト全体が移動します)なので、LinkedListはQueueインターフェイスを実装しますが、ArrayListは実装しません。

17
Bill K

TJ CrowderとAdamskiによって言及されているように、1つの参照はインターフェースへの参照であり、もう1つはインターフェースの特定の実装への参照です。 Joshua Blockによると、基礎となる実装への変更をより適切に処理できるように、常にインターフェイスへのコーディングを試みる必要があります。インスタンス化タイプを変更します。

12
aperkins

2番目の例では、 "map"参照はMap型であり、これはHashMap(およびその他のタイプのMap)によって実装されるインタフェースです。このインタフェースは契約で、オブジェクトはキーを値にマッピングし、さまざまな操作(例:putget)をサポートします。 Mapの実装については何もしません(この場合はHashMap)。

通常、Mapを使用したりAPI定義を介して特定のマップ実装をメソッドに公開したくない場合、2番目の方法が一般的に推奨されます。

8
Adamski

Mapはmapの静的型ですが、HashMapは動的型です。地図の。これは、実行時には、そのサブオブジェクトを指す可能性がある場合でも、コンパイラはマップオブジェクトをMap型のオブジェクトとして扱うことを意味します。

実装ではなくインタフェースに対してプログラミングを行うことで、柔軟性が維持されるという利点があります。たとえば、マップのサブタイプ(LinkedHashMapなど)であれば、動的タイプのマップを実行時に置き換えて、マップの動作を変更できます。その場.

たとえば、プログラミング中のメソッドがマップで機能する必要がある場合は、パラメータを厳密ではなくMapとして宣言するだけで十分です(抽象度が低いため)。HashMap型。そのようにして、あなたのAPIの消費者は、彼らがあなたのメソッドにどのようなMap実装を渡したいかについて柔軟になることができます。

8
Matthias

同じマップを作成します。

しかし、あなたはそれを使うつもりでその違いを埋めることができます。最初のケースでは、特別なHashMapメソッドを使うことができるでしょう(しかし、誰かが本当に役に立つことを私は覚えていません)、そしてそれをHashMapパラメータとして渡すことができるでしょう:

public void foo (HashMap<String, Object) { ... }

...

HashMap<String, Object> m1 = ...;
Map<String, Object> m2 = ...;

foo (m1);
foo ((HashMap<String, Object>)m2); 
3
Roman

「より一般的な、より良い」を強調した上で投票された回答と上記の多くの回答に加えて、私はもう少し掘り下げたいと思います。

Mapは構造体の規約で、HashMapは実際のさまざまな問題に対処するための独自の方法を提供する実装です。

ソースコードを見てみましょう。

MapにはcontainsKey(Object key)のメソッドがあります。

boolean containsKey(Object key);

JavaDoc:

boolean Java.util.Map.containsValue(Object value)

このマップが1つ以上のキーを指定された値にマッピングしている場合にtrueを返します。より正式には、このマップが(value==null ? v==null : value.equals(v))のような値vへのマッピングを少なくとも1つ含む場合に限り、trueを返します。この操作では、Mapインタフェースのほとんどの実装で、おそらくマップサイズに線形の時間が必要になります。

パラメータ:値

このマップ内の存在を確認する値

戻り値:true

このマップが1つ以上のキーを指定されたキーにマッピングするかどうか

valueThrows:

ClassCastException - 値がこのマップには不適切なタイプの場合(オプション)

NullPointerException - 指定された値がnullで、このマップがnull値を許可しない場合(オプション)

それはそれを実装するためにその実装を必要とします、しかしそれを正しく返すことを確実にするためだけに「方法」は自由にあります。

HashMap内:

public boolean containsKey(Object key) {
    return getNode(hash(key), key) != null;
}

このマップにキーが含まれているかどうかをテストするためにHashMapがハッシュコードを使用していることがわかります。そのため、ハッシュアルゴリズムの利点があります。

2
WesternGun

Mapはインターフェースであり、HashmapはMapインターフェースを実装するクラスです。

2
user6623857

Mapはインターフェースで、Hashmapはそれを実装するクラスです。

したがって、この実装では同じオブジェクトを作成します。

1
Diego Dias

HashMapはMapの実装なので、まったく同じですが、 "clone()"メソッドを持っています。参照ガイドを参照してください)

0
kolyaseg
HashMap<String, Object> map1 = new HashMap<String, Object>();
Map<String, Object> map2 = new HashMap<String, Object>();  

まず第一にMapは - HashMapTreeHashMapLinkedHashMapなどのように異なる実装を持つインターフェースです。インターフェースは実装クラスのスーパークラスのように機能します。そのため、OOPの規則によれば、Mapを実装する任意の具象クラスもMapです。つまり、キャストを行うことなく、任意のHashMap型の変数をMap型の変数に割り当てることができます。

この場合、キャストやデータの損失なしにmap1map2に割り当てることができます -

map2 = map1
0
Razib