Apa perbedaan antara objek HashMap dan Peta di Jawa?

349

Apa perbedaan antara peta-peta berikut yang saya buat (dalam pertanyaan lain, orang-orang menjawab menggunakannya secara bergantian dan saya bertanya-tanya apakah / bagaimana mereka berbeda):

HashMap<String, Object> map = new HashMap<String, Object>();
Map<String, Object> map = new HashMap<String, Object>();
Tony Stark
sumber
Misalkan Anda menerapkan menggunakan HashMap dan Mary menggunakan Peta. Apakah akan dikompilasi?
GilbertS

Jawaban:

446

Tidak ada perbedaan antara objek; Anda memiliki HashMap<String, Object>dalam kedua kasus. Ada perbedaan dalam antarmuka yang Anda miliki ke objek. Dalam kasus pertama, antarmuka adalah HashMap<String, Object>, sedangkan yang kedua adalah Map<String, Object>. Tetapi objek yang mendasarinya adalah sama.

Keuntungan menggunakan Map<String, Object>adalah Anda dapat mengubah objek yang mendasarinya menjadi jenis peta yang berbeda tanpa memutus kontrak Anda dengan kode apa pun yang menggunakannya. Jika Anda menyatakannya sebagai HashMap<String, Object>, Anda harus mengubah kontrak Anda jika Anda ingin mengubah implementasi yang mendasarinya.


Contoh: Katakanlah saya menulis kelas ini:

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...
}

Kelas memiliki beberapa peta internal objek string-> yang dibagikan (melalui metode accessor) dengan subclass. Katakanlah saya menulisnya dengan HashMaps karena saya pikir itu adalah struktur yang tepat untuk digunakan ketika menulis kelas.

Kemudian, Mary menulis kode subkelasnya. Dia memiliki sesuatu yang perlu dia lakukan dengan keduanya thingsdan moreThings, jadi secara alami dia menempatkan itu dalam metode umum, dan dia menggunakan tipe yang sama yang saya gunakan pada getThings/ getMoreThingsketika mendefinisikan metodenya:

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

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

    // ...more...
}

Kemudian, saya memutuskan bahwa sebenarnya, lebih baik jika saya menggunakan TreeMapdaripada HashMapdi Foo. Saya memperbarui Foo, berubah HashMapmenjadi TreeMap. Sekarang, SpecialFootidak mengkompilasi lagi, karena saya telah melanggar kontrak: Foodulu mengatakan itu diberikan HashMap, tetapi sekarang menyediakan itu TreeMapssebagai gantinya. Jadi kita harus memperbaikinya SpecialFoosekarang (dan hal semacam ini dapat mengacak basis kode).

Kecuali saya memiliki alasan yang sangat bagus untuk berbagi bahwa implementasi saya menggunakan HashMap(dan itu memang terjadi), apa yang seharusnya saya lakukan adalah menyatakan getThingsdan getMoreThingshanya kembali Map<String, Object>tanpa menjadi lebih spesifik dari itu. Bahkan, kecuali alasan yang baik untuk melakukan sesuatu yang lain, bahkan di dalam Foosaya mungkin harus menyatakan thingsdan moreThingssebagai Map, bukan 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...
}

Perhatikan bagaimana saya sekarang menggunakan di Map<String, Object>mana saja saya bisa, hanya spesifik ketika saya membuat objek yang sebenarnya.

Jika saya melakukan itu, maka Mary akan melakukan ini:

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

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

... dan mengubah Footidak akan membuat SpecialFookompilasi berhenti.

Antarmuka (dan kelas dasar) memungkinkan kami mengungkapkan sebanyak yang diperlukan , menjaga fleksibilitas kami di bawah penutup untuk membuat perubahan yang sesuai. Secara umum, kami ingin agar referensi kami sedasar mungkin. Jika kita tidak perlu tahu itu adalah HashMap, sebut saja a Map.

Ini bukan aturan buta, tetapi secara umum, pengkodean ke antarmuka paling umum akan menjadi kurang rapuh daripada pengkodean untuk sesuatu yang lebih spesifik. Jika saya ingat itu, saya tidak akan membuat Fooyang membuat Mary gagal SpecialFoo. Jika Mary ingat itu, maka meskipun aku mengacau Foo, dia akan mendeklarasikan metode pribadinya Mapsebagai ganti HashMapdan dan Fookontrakku yang berubah tidak akan memengaruhi kode etiknya.

Terkadang Anda tidak bisa melakukan itu, terkadang Anda harus spesifik. Tetapi kecuali Anda memiliki alasan untuk itu, berbuat salah menuju antarmuka yang paling tidak spesifik.

TJ Crowder
sumber
56

Peta adalah antarmuka yang mengimplementasikan HashMap . Perbedaannya adalah bahwa dalam implementasi kedua referensi Anda ke HashMap hanya akan memungkinkan penggunaan fungsi yang didefinisikan dalam antarmuka Peta, sedangkan yang pertama akan memungkinkan penggunaan fungsi publik apa pun di HashMap (yang mencakup antarmuka Peta).

Mungkin akan lebih masuk akal jika Anda membaca tutorial antarmuka Sun.

Grafik Noob
sumber
Saya berasumsi: first = HashMap <String, Object> map = new HashMap <String, Object> ();
OneWorld
Ini mirip dengan seberapa sering Daftar diimplementasikan sebagai ArrayList
Gerard
26

masukkan deskripsi gambar di sini

Peta memiliki implementasi sebagai berikut:

  1. HashMap Map m = new HashMap();

  2. LinkedHashMap Map m = new LinkedHashMap();

  3. Peta Pohon Map m = new TreeMap();

  4. WeakHashMap Map m = new WeakHashMap();

Misalkan Anda telah membuat satu metode (ini hanya kodesemu).

public void HashMap getMap(){
   return map;
}

Misalkan perubahan persyaratan proyek Anda:

  1. Metode harus mengembalikan konten peta - Harus kembali HashMap.
  2. Metode harus mengembalikan kunci peta dalam urutan penyisipan - Harus mengubah jenis kembali HashMapke LinkedHashMap.
  3. Metode ini harus mengembalikan kunci peta dalam urutan diurutkan - Perlu mengubah jenis kembali LinkedHashMapke TreeMap.

Jika metode Anda mengembalikan kelas tertentu dan bukan sesuatu yang mengimplementasikan Mapantarmuka, Anda harus mengubah jenis getMap()metode kembali setiap kali.

Tetapi jika Anda menggunakan fitur polimorfisme Java, dan bukannya mengembalikan kelas-kelas tertentu, gunakan antarmuka Map, itu meningkatkan penggunaan kembali kode dan mengurangi dampak perubahan persyaratan.

atish shimpi
sumber
17

Saya hanya akan melakukan ini sebagai komentar pada jawaban yang diterima tetapi terlalu funky (saya benci tidak memiliki jeda baris)

ah, jadi perbedaannya adalah bahwa secara umum, Peta memiliki metode tertentu yang terkait dengannya. tetapi ada berbagai cara atau membuat peta, seperti HashMap, dan cara-cara yang berbeda ini memberikan metode unik yang tidak dimiliki semua peta.

Tepat - dan Anda selalu ingin menggunakan antarmuka paling umum yang Anda bisa. Pertimbangkan ArrayList vs LinkedList. Perbedaan besar dalam cara Anda menggunakannya, tetapi jika Anda menggunakan "Daftar" Anda dapat beralih di antara mereka dengan mudah.

Bahkan, Anda dapat mengganti sisi kanan penginisialisasi dengan pernyataan yang lebih dinamis. Bagaimana dengan sesuatu yang seperti ini:

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

Dengan cara ini jika Anda akan mengisi koleksi dengan semacam penyisipan, Anda akan menggunakan daftar tertaut (semacam penyisipan ke dalam daftar array adalah kriminal.) Tetapi jika Anda tidak perlu menyimpannya diurutkan dan hanya menambahkan, Anda menggunakan ArrayList (Lebih efisien untuk operasi lain).

Ini adalah peregangan yang cukup besar di sini karena koleksi bukan contoh terbaik, tetapi dalam desain OO salah satu konsep paling penting adalah menggunakan fasad antarmuka untuk mengakses objek yang berbeda dengan kode yang sama persis.

Edit menanggapi komentar:

Adapun komentar peta Anda di bawah ini, Ya menggunakan antarmuka "Peta" membatasi Anda hanya pada metode-metode itu kecuali Anda mengembalikan koleksi dari Peta ke HashMap (yang SEPENUHNYA mengalahkan tujuannya).

Seringkali yang akan Anda lakukan adalah membuat objek dan mengisinya dengan menggunakan tipe spesifiknya (HashMap), dalam beberapa jenis metode "buat" atau "inisialisasi", tetapi metode itu akan mengembalikan "Peta" yang tidak perlu dimanipulasi sebagai HashMap lagi.

Jika Anda harus melakukannya, Anda mungkin menggunakan antarmuka yang salah atau kode Anda tidak terstruktur dengan baik. Perhatikan bahwa dapat diterima jika satu bagian dari kode Anda memperlakukannya sebagai "HashMap" sementara yang lain memperlakukannya sebagai "Peta", tetapi ini akan mengalir "turun". agar kamu tidak pernah casting.

Perhatikan juga aspek peran yang semi-rapi yang ditunjukkan oleh antarmuka. LinkedList membuat stack atau antrian yang baik, ArrayList membuat stack yang bagus tetapi antrian yang mengerikan (sekali lagi, sebuah penghapusan akan menyebabkan pergeseran seluruh daftar) sehingga LinkedList mengimplementasikan antarmuka antrian, ArrayList tidak.

Bill K.
sumber
tetapi dalam contoh ini, saya hanya mendapatkan metode dari kelas Daftar umum, kan? terlepas dari apakah saya membuatnya menjadi LinkedList () atau ArrayList ()? hanya saja jika saya menggunakan jenis penyisipan (yang saya bayangkan harus menjadi metode untuk Daftar yang mendapatkan LinkedList dan ArrayList secara turun-temurun) itu bekerja jauh lebih cepat di LinkedList?
Tony Stark
Saya kira apa yang saya cari adalah apakah atau tidak ketika saya mengatakan Peta <string, string> m = new HashMap <string, string> () Peta saya m dapat menggunakan metode khusus untuk HashMap, atau tidak. Saya pikir tidak bisa?
Tony Stark
ah, tunggu, tidak, Peta saya m dari atas harus memiliki metode dari HashMap.
Tony Stark
jadi pada dasarnya satu-satunya manfaat menggunakan Peta dalam 'antarmuka akal' adalah bahwa jika saya memiliki metode yang memerlukan peta, saya jamin semua jenis peta akan berfungsi dalam metode ini. tetapi jika saya menggunakan hashmap, saya katakan metode ini hanya bekerja dengan hashmaps. atau, dengan kata lain, metode saya hanya menggunakan metode yang didefinisikan dalam kelas Peta tetapi diwarisi oleh Kelas lain yang memperluas Peta.
Tony Stark
selain kegembiraan yang Anda sebutkan di atas, di mana menggunakan Daftar berarti saya tidak perlu memutuskan jenis Daftar yang saya inginkan sampai runtime, sedangkan jika antarmuka tidak ada, saya harus memilih satu sebelum mengkompilasi dan menjalankan
Tony Stark
12

Seperti dicatat oleh TJ Crowder dan Adamski, satu referensi adalah antarmuka, yang lain untuk implementasi spesifik antarmuka. Menurut Joshua Block, Anda harus selalu berusaha kode ke antarmuka, untuk memungkinkan Anda menangani perubahan implementasi yang lebih baik - yaitu jika HashMap tiba-tiba tidak ideal untuk solusi Anda dan Anda perlu mengubah implementasi peta, Anda masih bisa menggunakan Peta antarmuka, dan ubah jenis instantiation.

aperkins
sumber
8

Dalam contoh kedua Anda, referensi "peta" adalah tipe Map, yang merupakan antarmuka yang diimplementasikan oleh HashMap(dan jenis lain dari Map). Antarmuka ini adalah kontrak yang mengatakan bahwa objek memetakan nilai-nilai dan mendukung berbagai operasi (misalnya put, get). Ia mengatakan apa-apa tentang pelaksanaan dari Map(dalam hal ini HashMap).

Pendekatan kedua umumnya lebih disukai karena Anda biasanya tidak ingin mengekspos implementasi peta spesifik untuk metode yang menggunakan Mapatau melalui definisi API.

Adamski
sumber
8

Peta adalah tipe peta statis , sedangkan HashMap adalah tipe peta dinamis . Ini berarti bahwa kompiler akan memperlakukan objek peta Anda sebagai salah satu dari tipe Peta, meskipun saat runtime, ia mungkin menunjuk ke subtipe apa pun darinya.

Praktik pemrograman terhadap antarmuka dan bukan implementasi ini memiliki manfaat tambahan dengan tetap fleksibel: Misalnya, Anda dapat mengganti tipe dinamis peta saat runtime, asalkan itu adalah subtipe Peta (misalnya LinkedHashMap), dan mengubah perilaku peta pada lalat.

Aturan praktis yang baik adalah untuk tetap seabstrak mungkin pada tingkat API: Jika misalnya metode yang Anda pemrograman harus bekerja pada peta, maka itu cukup untuk mendeklarasikan parameter sebagai Peta alih-alih yang lebih ketat (karena kurang abstrak) tipe HashMap . Dengan begitu, konsumen API Anda bisa fleksibel tentang jenis implementasi Peta yang ingin mereka sampaikan ke metode Anda.

Matthias
sumber
4

Menambah jawaban pilihan teratas dan banyak yang di atas menekankan "lebih umum, lebih baik", saya ingin menggali sedikit lebih banyak.

Map adalah struktur kontrak sementara HashMap merupakan implementasi yang menyediakan metode sendiri untuk menghadapi berbagai masalah nyata: bagaimana menghitung indeks, apa kapasitasnya dan bagaimana meningkatkannya, bagaimana memasukkannya, bagaimana membuat indeksnya unik, dll.

Mari kita lihat kode sumbernya:

Di Mapkami memiliki metode containsKey(Object key):

boolean containsKey(Object key);

JavaDoc:

boolean java.util.Map.containsValue (Nilai objek)

Mengembalikan nilai true jika peta ini memetakan satu atau beberapa kunci ke nilai yang ditentukan. Lebih formal, mengembalikan true jika dan hanya jika peta ini mengandung setidaknya satu pemetaan ke nilai vsedemikian rupa (value==null ? v==null : value.equals(v)). Operasi ini mungkin akan memerlukan waktu linier dalam ukuran peta untuk sebagian besar implementasi antarmuka Peta.

Parameter: nilai

nilai yang kehadirannya di peta ini akan dipertaruhkan

Pengembalian: benar

jika peta ini memetakan satu atau lebih kunci ke yang ditentukan

nilaiMengapa:

ClassCastException - jika nilainya dari jenis yang tidak sesuai untuk peta ini (opsional)

NullPointerException - jika nilai yang ditentukan adalah nol dan peta ini tidak mengizinkan nilai nol (opsional)

Ia membutuhkan implementasinya untuk mengimplementasikannya, tetapi "bagaimana caranya" adalah kebebasannya, hanya untuk memastikan ia kembali dengan benar.

Dalam HashMap:

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

Ternyata yang HashMapmenggunakan kode hash untuk menguji apakah peta ini berisi kunci. Jadi itu memiliki manfaat dari algoritma hash.

WesternGun
sumber
3

Anda membuat peta yang sama.

Tetapi Anda dapat mengisi perbedaannya saat menggunakannya. Dengan case pertama Anda akan dapat menggunakan metode HashMap khusus (tapi saya tidak ingat ada orang yang benar-benar berguna), dan Anda akan dapat meneruskannya sebagai parameter HashMap:

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

...

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

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

Peta adalah antarmuka dan Hashmap adalah kelas yang mengimplementasikan Antarmuka Peta


sumber
1

Peta adalah Antarmuka dan Hashmap adalah kelas yang mengimplementasikannya.

Jadi dalam implementasi ini Anda membuat objek yang sama

Diego Dias
sumber
0

HashMap adalah implementasi dari Map sehingga cukup sama tetapi memiliki metode "clone ()" seperti yang saya lihat di panduan referensi))

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

Pertama-tama Mapadalah sebuah antarmuka memiliki implementasi yang berbeda seperti - HashMap, TreeHashMap, LinkedHashMapdll Antarmuka bekerja seperti kelas super untuk kelas pelaksana. Jadi menurut aturan OOP setiap kelas konkret yang mengimplementasikan Mapadalah Mapjuga. Itu berarti kita dapat menetapkan / menempatkan HashMapvariabel tipe apa pun ke Mapvariabel tipe tanpa tipe casting apa pun.

Dalam hal ini kita dapat menetapkan map1untuk map2tanpa pengecoran atau kalah data -

map2 = map1
Razib
sumber