Bagaimana cara langsung menginisialisasi HashMap (secara literal)?

1095

Apakah ada cara untuk menginisialisasi Java HashMap seperti ini ?:

Map<String,String> test = 
    new HashMap<String, String>{"test":"test","test":"test"};

Apa yang akan menjadi sintaks yang benar? Saya belum menemukan apa pun mengenai hal ini. Apakah ini mungkin? Saya mencari cara terpendek / tercepat untuk meletakkan beberapa nilai "final / statis" di peta yang tidak pernah berubah dan diketahui sebelumnya saat membuat Peta.

jens
sumber
Terkait erat: stackoverflow.com/questions/507602/… (Kedua pertanyaan tersebut adalah tentang menginisialisasi peta konstan dengan nilai akhir statis.)
Jonik
Di Jawa 9: techiedelight.com/initialize-map-java9
Kamil Tomasz Jarmusik
Jika Anda menggunakan apache.commons.collections, Anda dapat menggunakan commons.apache.org/proper/commons-collections/javadocs/…
ax.

Jawaban:

1346

Semua versi

Jika Anda membutuhkan hanya satu entri: Ada Collections.singletonMap("key", "value").

Untuk Java Versi 9 atau lebih tinggi:

Ya, ini mungkin sekarang. Di Jawa 9 beberapa metode pabrik telah ditambahkan yang menyederhanakan pembuatan peta:

// this works for up to 10 elements:
Map<String, String> test1 = Map.of(
    "a", "b",
    "c", "d"
);

// this works for any number of elements:
import static java.util.Map.entry;    
Map<String, String> test2 = Map.ofEntries(
    entry("a", "b"),
    entry("c", "d")
);

Dalam contoh di atas keduanya testdan test2akan sama, hanya dengan berbagai cara mengekspresikan Peta. The Map.ofMetode didefinisikan sampai elemen sepuluh di peta, sedangkan Map.ofEntriesmetode tidak akan memiliki batas tersebut.

Perhatikan bahwa dalam hal ini peta yang dihasilkan akan menjadi peta yang tidak berubah. Jika Anda ingin peta dapat diubah, Anda dapat menyalinnya lagi, misalnya menggunakanmutableMap = new HashMap<>(Map.of("a", "b"));

(Lihat juga JEP 269 dan Javadoc )

Untuk versi Java hingga 8:

Tidak, Anda harus menambahkan semua elemen secara manual. Anda dapat menggunakan penginisialisasi dalam subkelas anonim untuk membuat sintaks sedikit lebih pendek:

Map<String, String> myMap = new HashMap<String, String>() {{
        put("a", "b");
        put("c", "d");
    }};

Namun, subclass anonim mungkin memperkenalkan perilaku yang tidak diinginkan dalam beberapa kasus. Ini termasuk misalnya:

  • Ini menghasilkan kelas tambahan yang meningkatkan konsumsi memori, konsumsi ruang disk, dan waktu mulai
  • Dalam kasus metode non-statis: Ini memegang referensi ke objek metode pembuatan dipanggil. Itu berarti objek dari kelas luar tidak bisa menjadi sampah yang dikumpulkan ketika objek peta yang dibuat masih dirujuk, sehingga memblokir memori tambahan

Menggunakan fungsi untuk inisialisasi juga akan memungkinkan Anda untuk menghasilkan peta dalam penginisialisasi, tetapi menghindari efek samping yang buruk:

Map<String, String> myMap = createMap();

private static Map<String, String> createMap() {
    Map<String,String> myMap = new HashMap<String,String>();
    myMap.put("a", "b");
    myMap.put("c", "d");
    return myMap;
}
yankee
sumber
3
Ini tidak akan berfungsi jika Anda ingin menginisialisasi elemen dalam suatu fungsi ...
Michael
9
@Michael: Ya, jika Anda ingin menggunakan fungsi daripada Anda tidak bisa menggunakan fungsi-tidak. Tapi mengapa kamu mau?
yankee
6
dan untuk kasus-kasus ketika Anda membutuhkan Peta dengan satu entri ada Collections.singletonMap():)
skwisgaar
3
Sekarang stabil Java 9 telah dirilis, saya lebih suka tautan ini untuk Javadoc . Dan +1 karena satu ketergantungan kurang!
Franklin Yu
3
Dimana Java 9 entry didokumentasikan?
Nobar
1030

Ini satu arah.

HashMap<String, String> h = new HashMap<String, String>() {{
    put("a","b");
}};

Namun, Anda harus berhati-hati dan memastikan bahwa Anda memahami kode di atas (ini menciptakan kelas baru yang diwarisi dari HashMap). Karenanya, Anda harus membaca lebih lanjut di sini: http://www.c2.com/cgi/wiki?DoubleBraceInisialisasi , atau cukup gunakan Guava:

Map<String, Integer> left = ImmutableMap.of("a", 1, "b", 2, "c", 3);
gregory561
sumber
72
Ini berfungsi tetapi jelek dan memiliki efek samping yang tidak terlihat yang harus dipahami pengguna sebelum melakukannya - misalnya, membuat seluruh kelas anonim di tempat.
jprete
96
ya, itulah cara saya menulis tentang berhati-hati dan memberikan tautan ke deskripsi.
gregory561
6
Tautan bagus. Referensi dalam tautan itu ke GreencoddsTenthRuleOfProgramming layak dibaca.
michaelok
19
dapatkah Anda menambahkan "sebagai ImmutableMap.builder.put (" k1 "," v1 "). put (" k2 "," v2 "). build ()" karena metode "of" dibatasi maksimal 5 pasang?
kommradHomer
342

Jika Anda membiarkan libs pihak ke-3, Anda dapat menggunakan Jambu 's ImmutableMap untuk mencapai literal seperti singkatnya:

Map<String, String> test = ImmutableMap.of("k1", "v1", "k2", "v2");

Ini berfungsi hingga 5 pasangan kunci / nilai , jika tidak Anda dapat menggunakan pembuatnya :

Map<String, String> test = ImmutableMap.<String, String>builder()
    .put("k1", "v1")
    .put("k2", "v2")
    ...
    .build();


  • perhatikan bahwa implementasi ImmutableMap Guava berbeda dari Java HashMap implementasi (terutama itu tidak berubah dan tidak mengizinkan kunci / nilai null)
  • untuk info lebih lanjut, lihat artikel panduan pengguna Guava tentang jenis koleksi yang tidak berubah
Jens Hoffmann
sumber
26
Juga, jambu biji memiliki ImmutableMap.builder.put ("k1", "v1"). Put ("k2", "v2"). Build ();
Xetius
17
ImmutableMap tidak sama dengan HashMap, karena akan gagal pada nilai nol, sedangkan peta HashMap tidak akan.
Gewthen
2
Hanya untuk membantu orang lain yang mungkin menghadapi masalah ini. Anda harus mengetik builder untuk menjadikannya Map <String, String>, seperti ini: Map <String, String> test = ImmutableMap. <String, String> builder (). Put ("k1", "v1"). put ("k2", "v2"). build ();
Thiago
ini Jens yang luar biasa!
gaurav
105

Tidak ada cara langsung untuk melakukan ini - Java tidak memiliki literal Peta (belum - saya pikir mereka diusulkan untuk Java 8).

Beberapa orang menyukai ini:

Map<String,String> test = new HashMap<String, String>(){{
       put("test","test"); put("test","test");}};

Ini menciptakan subclass anonim dari HashMap, yang penginisiasinya instan menempatkan nilai-nilai ini. (Omong-omong, peta tidak dapat memuat dua kali nilai yang sama, put kedua Anda akan menimpa yang pertama. Saya akan menggunakan nilai yang berbeda untuk contoh berikutnya.)

Cara normal adalah ini (untuk variabel lokal):

Map<String,String> test = new HashMap<String, String>();
test.put("test","test");
test.put("test1","test2");

Jika testpeta Anda adalah variabel instan, masukkan inisialisasi ke konstruktor atau inisialisasi instance:

Map<String,String> test = new HashMap<String, String>();
{
    test.put("test","test");
    test.put("test1","test2");
}

Jika testpeta Anda adalah variabel kelas, letakkan inisialisasi dalam penginisialisasi statis:

static Map<String,String> test = new HashMap<String, String>();
static {
    test.put("test","test");
    test.put("test1","test2");
}

Jika Anda ingin peta Anda tidak pernah berubah, Anda harus setelah inisialisasi membungkus peta Anda dengan Collections.unmodifiableMap(...). Anda dapat melakukan ini di penginisialisasi statis juga:

static Map<String,String> test;
{
    Map<String,String> temp = new HashMap<String, String>();
    temp.put("test","test");
    temp.put("test1","test2");
    test = Collections.unmodifiableMap(temp);
}

(Saya tidak yakin apakah Anda sekarang dapat membuat testfinal ... coba dan laporkan di sini.)

Paŭlo Ebermann
sumber
61
Map<String,String> test = new HashMap<String, String>()
{
    {
        put(key1, value1);
        put(key2, value2);
    }
};
Katak Shaggy
sumber
Sederhana dan to the point. Saya pikir ini dengan bagian komentar panjang akan menjadi jawaban terbaik.
ooolala
15
Ada implikasi memori yang harus dicatat. blog.jooq.org/2014/12/08/...
Amalgovinus
1
@Amalgovinus Pada dasarnya, dengan membuat subkelas baru, Anda mengkodekan argumen tipe dari HashMapke dalam subkelas ini. Ini hanya dapat berfungsi jika Anda benar-benar menyediakannya. (Dengan HashMap baru (kosong), argumen tipe tidak relevan.)
Paŭlo Ebermann
1
Saya suka kebersihannya, tetapi ini menciptakan kelas anonim yang tidak perlu dan memiliki masalah yang dijelaskan di sini: c2.com/cgi/wiki?DoubleBraceInisialisasi
udachny
1
@hello_its_me: Karena jawabannya sama dengan stackoverflow.com/a/6802512/1386911 , formatnya berbeda. Dan dalam hal ini pemformatan yang diperluas ini tidak memiliki nilai tambahan di atas format ringkas untuk keterbacaan.
Daniel Hári
44

Alternatif, menggunakan kelas Java 7 dan varargs polos: buat kelas HashMapBuilderdengan metode ini:

public static HashMap<String, String> build(String... data){
    HashMap<String, String> result = new HashMap<String, String>();

    if(data.length % 2 != 0) 
        throw new IllegalArgumentException("Odd number of arguments");      

    String key = null;
    Integer step = -1;

    for(String value : data){
        step++;
        switch(step % 2){
        case 0: 
            if(value == null)
                throw new IllegalArgumentException("Null key value"); 
            key = value;
            continue;
        case 1:             
            result.put(key, value);
            break;
        }
    }

    return result;
}

Gunakan metode seperti ini:

HashMap<String,String> data = HashMapBuilder.build("key1","value1","key2","value2");
Aerthel
sumber
Saya menulis jawaban yang terinspirasi oleh Anda: stackoverflow.com/questions/507602/…
GeroldBroser mengembalikan Monica
1
Solusi lain dengan Apache Utils yang tidak pernah disebutkan tetapi dapat dibaca, menggunakan versi Java sebelumnya: MapUtils.putAll (HashMap <String, String> (), Object baru [] {"My key", "my value", ...
Rolintocour
4

tl; dr

Gunakan Map.of…metode di Java 9 dan yang lebih baru.

Map< String , String > animalSounds =
    Map.of(
        "dog"  , "bark" ,   // key , value
        "cat"  , "meow" ,   // key , value
        "bird" , "chirp"    // key , value
    )
;

Map.of

Java 9 menambahkan serangkaian Map.ofmetode statis untuk melakukan apa yang Anda inginkan: Buat Instantiate Mapmenggunakan sintaks literal .

Peta (kumpulan entri) tidak dapat diubah, sehingga Anda tidak dapat menambah atau menghapus entri setelah membuat instance. Juga, kunci dan nilai setiap entri tidak berubah, tidak dapat diubah. Lihat Javadoc untuk aturan lain, seperti tidak ada NULL diizinkan, tidak ada kunci duplikat diizinkan, dan urutan iterasi pemetaan adalah arbitrer.

Mari kita lihat metode-metode ini, menggunakan beberapa sampel data untuk peta hari-minggu ke seseorang yang kita harapkan akan bekerja pada hari itu.

Person alice = new Person( "Alice" );
Person bob = new Person( "Bob" );
Person carol = new Person( "Carol" );

Map.of()

Map.ofmenciptakan yang kosong Map. Tidak dapat dimodifikasi, sehingga Anda tidak dapat menambahkan entri. Berikut adalah contoh peta seperti itu, kosong tanpa entri.

Map < DayOfWeek, Person > dailyWorkerEmpty = Map.of();

dailyWorkerEmpty.toString (): {}

Map.of( … )

Map.of( k , v , k , v , …)ada beberapa metode yang mengambil 1 hingga 10 pasangan nilai kunci. Ini adalah contoh dari dua entri.

Map < DayOfWeek, Person > weekendWorker = 
        Map.of( 
            DayOfWeek.SATURDAY , alice ,     // key , value
            DayOfWeek.SUNDAY , bob           // key , value
        )
;

weekendWorker.toString (): {SUNDAY = Orang {name = 'Bob'}, SATURDAY = Orang {name = 'Alice'}}

Map.ofEntries( … )

Map.ofEntries( Map.Entry , … )mengambil sejumlah objek yang mengimplementasikan Map.Entryantarmuka. Java bundel dua kelas mengimplementasikan antarmuka itu, satu bisa berubah, yang lainnya tidak berubah: AbstractMap.SimpleEntry, AbstractMap.SimpleImmutableEntry. Tetapi kita tidak perlu menentukan kelas konkret. Kita hanya perlu memanggil Map.entry( k , v )metode, meneruskan kunci dan nilai kita, dan kita mendapatkan kembali objek dari beberapa Map.Entryantarmuka implementasi kelas .

Map < DayOfWeek, Person > weekdayWorker = Map.ofEntries(
        Map.entry( DayOfWeek.MONDAY , alice ) ,            // Call to `Map.entry` method returns an object implementing `Map.Entry`. 
        Map.entry( DayOfWeek.TUESDAY , bob ) ,
        Map.entry( DayOfWeek.WEDNESDAY , bob ) ,
        Map.entry( DayOfWeek.THURSDAY , carol ) ,
        Map.entry( DayOfWeek.FRIDAY , carol )
);

weekdayWorker.toString (): {WEDNESDAY = Orang {name = 'Bob'}, SELASA = Orang {name = 'Bob'}, KAMIS = Orang {name = 'Carol'}, FRIDAY = Orang {nama = 'Carol'} , SENIN = Orang {name = 'Alice'}}

Map.copyOf

Java 10 menambahkan metode ini Map.copyOf. Lewati peta yang ada, dapatkan kembali salinan peta itu yang tidak berubah.

Catatan

Perhatikan bahwa urutan iterator peta yang dihasilkan melalui Map.ofyang tidak dijamin. Entri memiliki perintah sewenang-wenang. Jangan menulis kode berdasarkan urutan yang terlihat, karena dokumentasi memperingatkan pesanan dapat berubah.

Perhatikan bahwa semua Map.of…metode ini mengembalikan a Mapdari kelas yang tidak ditentukan . Kelas beton yang mendasari bahkan dapat bervariasi dari satu versi Java ke yang lain. Anonimitas ini memungkinkan Java untuk memilih dari berbagai implementasi, apa pun yang secara optimal sesuai dengan data khusus Anda. Misalnya, jika kunci Anda berasal dari enum , Java mungkin menggunakan EnumMapbawah.

Basil Bourque
sumber
1

Anda dapat membuat Map.ofmetode Anda sendiri (yang hanya tersedia di Java 9 dan lebih tinggi) dengan mudah dalam 2 cara mudah

Buat dengan sejumlah parameter yang ditetapkan

Contoh

public <K,V> Map<K,V> mapOf(K k1, V v1, K k2, V v2 /* perhaps more parameters */) {
    return new HashMap<K, V>() {{
      put(k1, v1);
      put(k2,  v2);
      // etc...
    }};
}

Jadikan menggunakan Daftar

Anda juga dapat membuatnya menggunakan daftar, alih-alih membuat banyak metode untuk serangkaian parameter tertentu.

Contoh

public <K, V> Map<K, V> mapOf(List<K> keys, List<V> values) {
   if(keys.size() != values.size()) {
        throw new IndexOutOfBoundsException("amount of keys and values is not equal");
    }

    return new HashMap<K, V>() {{
        IntStream.range(0, keys.size()).forEach(index -> put(keys.get(index), values.get(index)));
    }};
}

Catatan Tidak disarankan untuk menggunakan ini untuk semuanya karena ini membuat kelas anonim setiap kali Anda menggunakan ini.

NotNV6
sumber
1

JAWA 8

Di java 8 Anda juga memiliki kemungkinan menggunakan Streams/Collectorsuntuk melakukan pekerjaan.

Map<String, String> myMap = Stream.of(
         new SimpleEntry<>("key1", "value1"),
         new SimpleEntry<>("key2", "value2"),
         new SimpleEntry<>("key3", "value3"))
        .collect(toMap(SimpleEntry::getKey, SimpleEntry::getValue));

Ini memiliki keuntungan karena tidak membuat kelas Anonim.

Perhatikan bahwa impor adalah:

import static java.util.stream.Collectors.toMap;
import java.util.AbstractMap.SimpleEntry;

Tentu saja, seperti disebutkan dalam jawaban lain, di java 9 dan seterusnya Anda memiliki cara yang lebih sederhana untuk melakukan hal yang sama.

Johnny Willer
sumber
0

Sayangnya, menggunakan varargs jika jenis kunci dan nilai tidak sama tidak masuk akal karena Anda harus menggunakan Object...dan kehilangan keamanan jenis sepenuhnya. Jika Anda selalu ingin membuat misalnya a Map<String, String>, tentu saja atoMap(String... args) mungkin saja, tetapi tidak terlalu cantik karena akan mudah untuk mencampur kunci dan nilai, dan sejumlah ganjil argumen akan tidak valid.

Anda bisa membuat sub-kelas dari HashMap yang memiliki metode seperti rantai

public class ChainableMap<K, V> extends HashMap<K, V> {
  public ChainableMap<K, V> set(K k, V v) {
    put(k, v);
    return this;
  }
}

dan menggunakannya seperti new ChainableMap<String, Object>().set("a", 1).set("b", "foo")

Pendekatan lain adalah dengan menggunakan pola pembangun umum:

public class MapBuilder<K, V> {
  private Map<K, V> mMap = new HashMap<>();

  public MapBuilder<K, V> put(K k, V v) {
    mMap.put(k, v);
    return this;
  }

  public Map<K, V> build() {
    return mMap;
  }
}

dan menggunakannya seperti new MapBuilder<String, Object>().put("a", 1).put("b", "foo").build();

Namun, solusi yang saya gunakan sekarang dan kemudian menggunakan varargs dan Pairkelas:

public class Maps {
  public static <K, V> Map<K, V> of(Pair<K, V>... pairs) {
    Map<K, V> = new HashMap<>();

    for (Pair<K, V> pair : pairs) {
      map.put(pair.first, pair.second);
    }

    return map;
  }
}

Map<String, Object> map = Maps.of(Pair.create("a", 1), Pair.create("b", "foo");

Kata-kata Pair.create()yang agak mengganggu saya sedikit, tetapi ini bekerja cukup baik. Jika Anda tidak keberatan impor statis, tentu saja Anda dapat membuat pembantu:

public <K, V> Pair<K, V> p(K k, V v) {
  return Pair.create(k, v);
}

Map<String, Object> map = Maps.of(p("a", 1), p("b", "foo");

(Alih-alih Pairbisa dibayangkan menggunakan Map.Entry, tapi karena itu antarmuka, ini membutuhkan kelas implementasi dan / atau metode pabrik pembantu. Ini juga tidak dapat diubah, dan mengandung logika lain yang tidak berguna untuk tugas ini.)

JHH
sumber
0

Anda dapat menggunakan Streams In Java 8 (ini adalah exmaple dari Set):

@Test
public void whenInitializeUnmodifiableSetWithDoubleBrace_containsElements() {
    Set<String> countries = Stream.of("India", "USSR", "USA")
      .collect(collectingAndThen(toSet(), Collections::unmodifiableSet));

    assertTrue(countries.contains("India"));
}

Ref: https://www.baeldung.com/java-double-brace-initialisation

Robocide
sumber
0

Jika Anda hanya perlu menempatkan satu pasangan nilai kunci, Anda dapat menggunakan Collections.singletonMap (kunci, nilai);

Balakrishna Kudikala
sumber
1
memformat kode membuat posting jauh lebih mudah dibaca
Renato