ฉันจะเริ่มต้นแผนที่แบบคงที่ได้อย่างไร


1131

คุณจะเริ่มต้นคงที่Mapใน Java อย่างไร

วิธีที่หนึ่ง: แบบเริ่มต้นคงที่
วิธีที่สอง: ตัวเริ่มต้นอินสแตนซ์ (คลาสย่อยแบบไม่ระบุชื่อ) หรือวิธีอื่น ๆ ?

ข้อดีและข้อเสียของแต่ละข้อคืออะไร

นี่คือตัวอย่างที่แสดงวิธีการสองวิธี:

import java.util.HashMap;
import java.util.Map;

public class Test {
    private static final Map<Integer, String> myMap = new HashMap<>();
    static {
        myMap.put(1, "one");
        myMap.put(2, "two");
    }

    private static final Map<Integer, String> myMap2 = new HashMap<>(){
        {
            put(1, "one");
            put(2, "two");
        }
    };
}

2
สำหรับการเริ่มต้นแผนที่ใน Java 8: stackoverflow.com/a/37384773/1216775
akhil_mittal

2
โปรดอย่าใช้การเริ่มต้นรั้งสองครั้ง - มันเป็นการแฮกและเป็นวิธีที่ง่ายในการรั่วไหลของหน่วยความจำและทำให้เกิดปัญหาอื่น ๆ
dimo414

Java 9 หากรายการนับ <= 10 การใช้งานMap.ofอื่นMap.ofEntriesให้ตรวจสอบstackoverflow.com/a/37384773/1216775
akhil_mittal

คำตอบ:


1106

ผู้เริ่มต้นอินสแตนซ์เป็นเพียงน้ำตาลประโยคในกรณีนี้ใช่ไหม ฉันไม่เห็นว่าทำไมคุณต้องมีคลาสแบบไม่ระบุชื่อเพื่อเริ่มต้น และมันจะไม่ทำงานหากคลาสที่ถูกสร้างขึ้นเป็นที่สิ้นสุด

คุณสามารถสร้างแผนที่ที่ไม่เปลี่ยนรูปได้โดยใช้ตัวย่อคงที่:

public class Test {
    private static final Map<Integer, String> myMap;
    static {
        Map<Integer, String> aMap = ....;
        aMap.put(1, "one");
        aMap.put(2, "two");
        myMap = Collections.unmodifiableMap(aMap);
    }
}

10
นี่เป็นสำนวนที่ฉันใช้มานานหลายปีและฉันไม่เคยมีใครสนใจ ฉันทำเช่นเดียวกันสำหรับชุดและรายการคงที่ที่ไม่สามารถเปลี่ยนแปลงได้เช่นกัน
jasonmp85

3
ฉันจะจัดการ HashMap <String, String> ด้วยคีย์ String ได้อย่างไร วัตถุแผนที่ไม่อนุญาตให้ฉันมีคีย์สตริงดังนั้นฉันจึงไม่สามารถใช้ unmodifiableMap () ฉันเดาว่าการคัดเลือก HashMap จะเอาชนะวัตถุประสงค์ได้เช่นกัน ความคิดใด ๆ
ลุ

30
@ ลุคฉันสงสัยอย่างจริงจังว่า Android มีข้อ จำกัด เช่นนี้ มันไม่สมเหตุสมผลเลย การค้นหาอย่างรวดเร็วพบคำถามนี้ที่นี่ (และอื่น ๆ อีกมากมาย) ซึ่งดูเหมือนว่าแปลว่าคุณสามารถใช้คีย์สตริงสำหรับวัตถุแผนที่ใน Android
mluisbrown

11
ดังนั้นไม่มีใครรบกวนการตรวจสอบฉันสามารถยืนยันว่าไม่มีปัญหากับการใช้คีย์สตริงสำหรับวัตถุแผนที่บน Android
Jordan

11
Jordan: มันเป็นหัวข้อเก่าแล้ว แต่ฉันคิดว่า @Luke กำลังพยายามใช้สตริงเป็นคีย์ในแผนที่ที่มีประเภทของคีย์ที่แตกต่างกันเช่น Map <Integer, String>
แปรปรวนแปรปรวน

445

ฉันชอบวิธีGuavaในการเริ่มต้นแผนที่คงที่และไม่เปลี่ยนรูปแบบ:

static final Map<Integer, String> MY_MAP = ImmutableMap.of(
    1, "one",
    2, "two"
);

อย่างที่คุณเห็นมันกระชับมาก (เพราะวิธีการที่สะดวกในโรงงานImmutableMap)

หากคุณต้องการแผนที่จะมีมากขึ้นกว่า 5 ImmutableMap.of()รายการคุณสามารถไม่ได้ใช้ ให้ลองทำImmutableMap.builder()ตามบรรทัดเหล่านี้แทน:

static final Map<Integer, String> MY_MAP = ImmutableMap.<Integer, String>builder()
    .put(1, "one")
    .put(2, "two")
    // ... 
    .put(15, "fifteen")
    .build();

ต้องการเรียนรู้เพิ่มเติมเกี่ยวกับประโยชน์ของฝรั่งของสาธารณูปโภคคอลเลกชันที่ไม่เปลี่ยนรูปให้ดูคอลเลกชันไม่เปลี่ยนรูปอธิบายในฝรั่งคู่มือการใช้งาน

(ส่วนหนึ่งของ) ฝรั่งเคยถูกเรียกว่าGoogle คอลเลกชัน หากคุณยังไม่ได้ใช้ห้องสมุดนี้ในโครงการ Java ของคุณฉันขอแนะนำอย่างยิ่งให้ลอง! ฝรั่งได้อย่างรวดเร็วกลายเป็นหนึ่งในความนิยมมากที่สุดและมีประโยชน์ libs บุคคลที่ 3 ฟรีสำหรับ Java เป็นผู้ใช้ดังนั้นเพื่อนเห็นด้วย (ถ้าคุณยังใหม่กับมันมีแหล่งการเรียนรู้ที่ยอดเยี่ยมอยู่ด้านหลังลิงค์)


Update (2015) : สำหรับJava 8ยังไงฉันก็ยังคงใช้แนวทางของ Guava เพราะมันสะอาดกว่าวิธีอื่น หากคุณไม่ต้องการพึ่งพาฝรั่งพิจารณาวิธี init ธรรมดาเก่า แฮ็คที่มีอาเรย์สองมิติและ Stream APIนั้นค่อนข้างน่าเกลียดถ้าคุณถามฉันและได้รับอัปลักษณ์ถ้าคุณต้องการสร้างแผนที่ซึ่งมีรหัสและค่าที่ไม่เหมือนกัน (เหมือนMap<Integer, String>ในคำถาม)

สำหรับอนาคตของ Guava โดยทั่วไปสำหรับ Java 8 นั้น Louis Wasserman กล่าวว่าย้อนกลับไปในปี 2014 และ [ อัพเดต ] ในปี 2559 ได้มีการประกาศว่าGuava 21 จะต้องการและสนับสนุน Java 8อย่างเหมาะสม


Update (2016) : เป็นTagir Valeev ชี้ให้เห็น , Java 9ในที่สุดก็จะทำให้สะอาดนี้จะทำอย่างไรใช้อะไร แต่บริสุทธิ์ JDK โดยการเพิ่มวิธีการโรงงานสะดวกสบายสำหรับคอลเลกชัน:

static final Map<Integer, String> MY_MAP = Map.of(
    1, "one", 
    2, "two"
);

21
ดูเหมือนว่าผู้ดูแลระบบ SO เพื่อนของเราได้ลบคำถาม "ห้องสมุด Java ของบุคคลที่สามที่มีประโยชน์ฟรีที่มีประโยชน์มากที่สุด" ที่ฉันเชื่อมโยง :( ประณามพวกเขา
Jonik

2
ฉันเห็นด้วยนี่เป็นวิธีที่ดีที่สุดในการเริ่มต้นแผนที่คงที่ ไม่เพียงอ่านได้มากขึ้น แต่ยังรวมถึง Collections.unmodifiableMapส่งคืนมุมมองแบบอ่านอย่างเดียวของแผนที่ต้นแบบ (ที่ยังสามารถแก้ไขได้)
crunchdog

11
ตอนนี้ผมสามารถดูคำถามที่ถูกลบ (กับ 10k + ตัวแทน) ดังนั้นนี่เป็นสำเนาของ 'ประโยชน์ฟรีของบุคคลที่สาม Java ห้องสมุดส่วนใหญ่' มันเป็นเพียงหน้าแรก แต่อย่างน้อยคุณก็สามารถค้นหาทรัพยากร Guava ที่กล่าวถึงข้างต้นได้
Jonik

2
ฉันชอบวิธีนี้มากแม้ว่าจะเป็นประโยชน์ที่จะทราบวิธีการทำโดยไม่ต้องพึ่งพาเพิ่มเติม
ประแจ

2
JEP 186ยังไม่ปิดดังนั้นจึงอาจแนะนำคุณสมบัติใหม่ที่เกี่ยวข้องกับตัวอักษรของการสะสม
cybersoft

182

ฉันจะใช้:

public class Test {
    private static final Map<Integer, String> MY_MAP = createMap();

    private static Map<Integer, String> createMap() {
        Map<Integer, String> result = new HashMap<>();
        result.put(1, "one");
        result.put(2, "two");
        return Collections.unmodifiableMap(result);
    }
}
  1. มันหลีกเลี่ยงคลาสที่ไม่ระบุชื่อซึ่งโดยส่วนตัวแล้วฉันคิดว่าเป็นสไตล์ที่ไม่ดีและหลีกเลี่ยง
  2. มันทำให้การสร้างแผนที่ชัดเจนยิ่งขึ้น
  3. มันทำให้แผนที่ไม่สามารถแก้ไขได้
  4. เนื่องจาก MY_MAP คงที่ฉันจะตั้งชื่อให้เหมือนค่าคงที่

3
จากตัวเลือก JDK ล้วนๆ (ไม่มี libs) ฉันชอบสิ่งนี้มากที่สุดเนื่องจากคำจำกัดความของแผนที่เชื่อมโยงกับการเริ่มต้นอย่างชัดเจน เห็นด้วยกับการตั้งชื่อคงที่
Jonik

ไม่เคยเกิดขึ้นกับฉันที่คุณสามารถทำได้
romulusnr

181

Java 5 มีไวยากรณ์ที่กระชับมากขึ้นนี้:

static final Map<String , String> FLAVORS = new HashMap<String , String>() {{
    put("Up",    "Down");
    put("Charm", "Strange");
    put("Top",   "Bottom");
}};

46
เทคนิคนั้นเรียกว่าการเริ่มต้นใช้งานวงเล็บปีกกาคู่: stackoverflow.com/questions/1372113/ …ไม่ใช่ไวยากรณ์ Java 5 พิเศษมันเป็นเพียงกลลวงกับคลาสที่ไม่ระบุชื่อพร้อมด้วย initializer อินสแตนซ์
Jesper

13
คำถามด่วนเกี่ยวกับการเริ่มต้นวงเล็บปีกกาคู่: เมื่อทำเช่นนี้ Eclipse จะออกคำเตือนเกี่ยวกับ Serial ID ที่ขาดหายไป ในอีกด้านหนึ่งฉันไม่เห็นว่าทำไมต้องมี Serial ID ในกรณีนี้โดยเฉพาะ แต่ในทางกลับกันฉันมักจะไม่ชอบคำเตือนที่ทำให้หงุดหงิด คุณคิดอย่างไรกับเรื่องนี้?
nbarraille

8
@nbarraille HashMap implements Serializableนั่นเป็นเพราะ เนื่องจากคุณสร้างคลาสย่อยของ HashMap โดยใช้ "เคล็ดลับ" นี้คุณจึงสร้างคลาสที่สามารถทำให้เป็นอนุกรมได้ และสำหรับสิ่งนี้คุณควรจัดหา serialUID
noone

5
Double brace initialization can cause memory leaks when used from a non-static context, because the anonymous class created will maintain a reference to the surrounding object. It has worse performance than regular initialization because of the additional class loading required. It can cause equals() comparisons to fail, if the equals() method does not accept subclasses as parameter. And finally, pre Java 9 it cannot be combined with the diamond operator, because that cannot be used with anonymous classes.- IntelliJ
Mark Jeronimus

3
@MarkJeronimus - การใช้ที่แนะนำคือบริบทแบบคงที่ ประสิทธิภาพอาจแย่ลง แต่ไม่เด่นชัดดังนั้นเมื่อจัดการกับแผนที่ที่มีการกำหนดจำนวนคงที่ HashMap.equalsถูกกำหนดไว้ในAbstractMapและทำงานบนใด ๆ subclass ของแผนที่เพื่อที่ว่าไม่กังวลที่นี่ สิ่งที่ผู้ประกอบการเพชรเป็นที่น่ารำคาญ แต่ดังกล่าวได้รับการแก้ไขแล้ว
จูลส์

95

ข้อดีอย่างหนึ่งของวิธีที่สองคือคุณสามารถห่อด้วยCollections.unmodifiableMap()เพื่อรับประกันว่าจะไม่มีการอัพเดทคอลเลกชันในภายหลัง:

private static final Map<Integer, String> CONSTANT_MAP = 
    Collections.unmodifiableMap(new HashMap<Integer, String>() {{ 
        put(1, "one");
        put(2, "two");
    }});

 // later on...

 CONSTANT_MAP.put(3, "three"); // going to throw an exception!

3
คุณไม่สามารถทำสิ่งนี้ในวิธีแรกได้อย่างง่ายดายโดยการย้ายตัวดำเนินการใหม่ไปยังบล็อก {} แบบคงที่และล้อมรอบหรือไม่
Patrick

2
ฉันจะย้ายการโทรคอนสตรัคเป็นแบบคงที่เริ่มต้นได้แล้ว อะไรก็ได้ที่ดูแปลก ๆ
Tom Hawtin - tackline

2
ความคิดใด ๆ ที่อาจกระทบต่อประสิทธิภาพจากการใช้คลาสที่ไม่ระบุชื่อซึ่งตรงข้ามกับคลาสที่เป็นรูปธรรม?
23904 Kip

62

นี่คือตัวกำหนดค่าเริ่มต้นแผนที่แบบคงที่ Java 8 บรรทัดเดียว:

private static final Map<String, String> EXTENSION_TO_MIMETYPE =
    Arrays.stream(new String[][] {
        { "txt", "text/plain" }, 
        { "html", "text/html" }, 
        { "js", "application/javascript" },
        { "css", "text/css" },
        { "xml", "application/xml" },
        { "png", "image/png" }, 
        { "gif", "image/gif" }, 
        { "jpg", "image/jpeg" },
        { "jpeg", "image/jpeg" }, 
        { "svg", "image/svg+xml" },
    }).collect(Collectors.toMap(kv -> kv[0], kv -> kv[1]));

แก้ไข: เพื่อเริ่มต้น a Map<Integer, String>ในคำถามคุณต้องมีสิ่งนี้:

static final Map<Integer, String> MY_MAP = Arrays.stream(new Object[][]{
        {1, "one"},
        {2, "two"},
}).collect(Collectors.toMap(kv -> (Integer) kv[0], kv -> (String) kv[1]));

แก้ไข (2): มีรุ่นที่ดีกว่าและหลากหลายประเภทโดย i_am_zero ที่ใช้สตรีมการnew SimpleEntry<>(k, v)โทร ลองดูคำตอบนั้น: https://stackoverflow.com/a/37384773/3950982


7
ฉันใช้เสรีภาพในการเพิ่มเวอร์ชันที่เทียบเท่ากับคำถามและคำตอบอื่น ๆ : เริ่มต้นแผนที่ที่มีคีย์และค่าต่างประเภทกัน ( String[][]ไม่Object[][]จำเป็นต้องทำ) IMHO แนวทางนี้น่าเกลียดมากยิ่งขึ้นกับนักแสดง) และยากที่จะจำได้ จะไม่ใช้มันด้วยตัวเอง
Jonik

57

Map.of ใน Java 9+

private static final Map<Integer, String> MY_MAP = Map.of(1, "one", 2, "two");

ดูJEP 269สำหรับรายละเอียด JDK 9 มาถึงความพร้อมใช้งานทั่วไปในเดือนกันยายน 2017


7
หรือหากคุณต้องการคู่ค่าคีย์มากกว่า 10 คู่คุณสามารถใช้Map.ofEntries
ZhekaKozlov

8
นี่คือสิ่งที่สะอาดและทุกอย่างจนกว่าคุณจะรู้ว่ามันถูกนำไปใช้อย่างไร
กลาง

เอ่อเศร้ามาก - ดูเหมือนว่ารองรับเพียง 10 รายการหลังจากนั้นคุณต้องใช้งานรายการ กะพร่องกะแพร่ง.
Somaiah Kumbera

2
ความสะอาดของการใช้งานใน JDK ไม่ควรสำคัญตราบใดที่มันใช้งานได้และเป็นไปตามสัญญา เช่นเดียวกับกล่องดำรายละเอียดการติดตั้งสามารถแก้ไขได้ในอนาคตหากจำเป็นจริงๆ ...
4324

@ กลางนั่นเป็นวิธีเดียวที่ปลอดภัยสำหรับการทำเช่นนี้ใน Java
ลุคฮัทชิสัน

44

Java 9

เราสามารถใช้Map.ofEntriesโทรMap.entry( k , v )เพื่อสร้างแต่ละรายการ

import static java.util.Map.entry;
private static final Map<Integer,String> map = Map.ofEntries(
        entry(1, "one"),
        entry(2, "two"),
        entry(3, "three"),
        entry(4, "four"),
        entry(5, "five"),
        entry(6, "six"),
        entry(7, "seven"),
        entry(8, "eight"),
        entry(9, "nine"),
        entry(10, "ten"));

นอกจากนี้เรายังสามารถใช้Map.ofการแนะนำโดย Tagir ในคำตอบของเขาที่นี่แต่เราไม่สามารถมีมากกว่า 10 Map.ofรายการโดยใช้

Java 8 (โซลูชันที่ประณีต)

เราสามารถสร้างรายการสตรีมของแผนที่ เรามีสองการใช้งานของEntryในjava.util.AbstractMapซึ่งเป็นSimpleEntryและSimpleImmutableEntry สำหรับตัวอย่างนี้เราสามารถใช้อดีตเป็น:

import java.util.AbstractMap.*;
private static final Map<Integer, String> myMap = Stream.of(
            new SimpleEntry<>(1, "one"),
            new SimpleEntry<>(2, "two"),
            new SimpleEntry<>(3, "three"),
            new SimpleEntry<>(4, "four"),
            new SimpleEntry<>(5, "five"),
            new SimpleEntry<>(6, "six"),
            new SimpleEntry<>(7, "seven"),
            new SimpleEntry<>(8, "eight"),
            new SimpleEntry<>(9, "nine"),
            new SimpleEntry<>(10, "ten"))
            .collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue));

2
new SimpleEntry<>()วิธีที่อยู่ไกลสามารถอ่านได้น้อยกว่าแบบคงที่put(): /
Danon

32

ด้วยEclipse Collectionsสิ่งต่อไปนี้จะได้ผล:

import java.util.Map;

import org.eclipse.collections.api.map.ImmutableMap;
import org.eclipse.collections.api.map.MutableMap;
import org.eclipse.collections.impl.factory.Maps;

public class StaticMapsTest
{
    private static final Map<Integer, String> MAP =
        Maps.mutable.with(1, "one", 2, "two");

    private static final MutableMap<Integer, String> MUTABLE_MAP =
       Maps.mutable.with(1, "one", 2, "two");


    private static final MutableMap<Integer, String> UNMODIFIABLE_MAP =
        Maps.mutable.with(1, "one", 2, "two").asUnmodifiable();


    private static final MutableMap<Integer, String> SYNCHRONIZED_MAP =
        Maps.mutable.with(1, "one", 2, "two").asSynchronized();


    private static final ImmutableMap<Integer, String> IMMUTABLE_MAP =
        Maps.mutable.with(1, "one", 2, "two").toImmutable();


    private static final ImmutableMap<Integer, String> IMMUTABLE_MAP2 =
        Maps.immutable.with(1, "one", 2, "two");
}

นอกจากนี้คุณยังสามารถเริ่มต้นแผนที่แบบดั้งเดิมด้วยการรวบรวม Eclipse

import org.eclipse.collections.api.map.primitive.ImmutableIntObjectMap;
import org.eclipse.collections.api.map.primitive.MutableIntObjectMap;
import org.eclipse.collections.impl.factory.primitive.IntObjectMaps;

public class StaticPrimitiveMapsTest
{
    private static final MutableIntObjectMap<String> MUTABLE_INT_OBJ_MAP =
            IntObjectMaps.mutable.<String>empty()
                    .withKeyValue(1, "one")
                    .withKeyValue(2, "two");

    private static final MutableIntObjectMap<String> UNMODIFIABLE_INT_OBJ_MAP =
            IntObjectMaps.mutable.<String>empty()
                    .withKeyValue(1, "one")
                    .withKeyValue(2, "two")
                    .asUnmodifiable();

    private static final MutableIntObjectMap<String> SYNCHRONIZED_INT_OBJ_MAP =
            IntObjectMaps.mutable.<String>empty()
                    .withKeyValue(1, "one")
                    .withKeyValue(2, "two")
                    .asSynchronized();

    private static final ImmutableIntObjectMap<String> IMMUTABLE_INT_OBJ_MAP =
            IntObjectMaps.mutable.<String>empty()
                    .withKeyValue(1, "one")
                    .withKeyValue(2, "two")
                    .toImmutable();

    private static final ImmutableIntObjectMap<String> IMMUTABLE_INT_OBJ_MAP2 =
            IntObjectMaps.immutable.<String>empty()
                    .newWithKeyValue(1, "one")
                    .newWithKeyValue(2, "two");
} 

หมายเหตุ:ฉันเป็นคอมมิชชันสำหรับ Eclipse Collections


1
ฉันหวังว่า Eclipse Collections คือไลบรารีชุดเริ่มต้นสำหรับ Java ฉันสนุกกับมันมากกว่า Guava + JCL
Kenny Cason

29

ฉันจะไม่สร้างคลาสย่อยที่ไม่ระบุชื่อในสถานการณ์นี้ static initializers ทำงานได้ดีเช่นกันหากคุณต้องการทำให้แผนที่ไม่สามารถแก้ไขได้เช่น:

private static final Map<Integer, String> MY_MAP;
static
{
    Map<Integer, String>tempMap = new HashMap<Integer, String>();
    tempMap.put(1, "one");
    tempMap.put(2, "two");
    MY_MAP = Collections.unmodifiableMap(tempMap);
}

1
ในสถานการณ์ใดที่คุณจะใช้คลาสย่อยที่ไม่ระบุชื่อเพื่อกำหนดค่าเริ่มต้น hashmap แล้ว?
dogbane

6
อย่าเริ่มต้นชุดสะสม
eljenso

คุณสามารถอธิบายได้หรือไม่ว่าทำไมการใช้ initializer แบบคงที่จึงเป็นทางเลือกที่ดีกว่าการสร้างคลาสย่อยแบบไม่ระบุชื่อ
leba-lev

3
@rookie มีหลายเหตุผลที่ให้ไว้ในคำตอบอื่น ๆ ที่ชอบแบบคงที่ เป้าหมายที่นี่คือการเริ่มต้นเหตุใดจึงนำมาซึ่งคลาสย่อยยกเว้นอาจบันทึกการกดแป้นบางครั้ง (ถ้าคุณต้องการบันทึกในการกดแป้นพิมพ์ Java ไม่ได้เป็นตัวเลือกที่ดีในการเขียนโปรแกรมภาษา) กฎข้อหนึ่งที่ฉันใช้ในการเขียนโปรแกรมใน Java คือ subclass ให้น้อยที่สุดเท่าที่จะทำได้
eljenso

@eljenso - เหตุผลที่ฉันมักชอบไวยากรณ์ subclass สำหรับเรื่องนี้ก็คือว่ามันทำให้อินไลน์ initialisation ที่มันเป็น ตัวเลือกที่ดีที่สุดที่สองคือการเรียกวิธีการคงที่ที่ส่งกลับแผนที่เริ่มต้น แต่ฉันเกรงว่าฉันจะดูรหัสของคุณและต้องใช้เวลาสองสามวินาทีเพื่อหาว่า MY_MAP มาจากไหนและถึงเวลาที่ฉันไม่ต้องการเสียเปล่า การปรับปรุงใด ๆ ในการอ่านเป็นโบนัสและผลการปฏิบัติงานมีเพียงเล็กน้อยดังนั้นจึงเป็นตัวเลือกที่ดีที่สุดสำหรับฉัน
จูลส์

18

อาจเป็นเรื่องที่น่าสนใจในการดูGoogle Collectionsเช่นวิดีโอที่พวกเขามีในหน้าเว็บของพวกเขา พวกเขาให้วิธีการต่างๆในการเริ่มต้นแผนที่และชุดและให้คอลเลกชันที่ไม่เปลี่ยนรูปเช่นกัน

อัปเดต: ห้องสมุดนี้ชื่อGuavaแล้ว


17

ฉันชอบคลาสนิรนามเพราะง่ายต่อการจัดการ:

public static final Map<?, ?> numbers = Collections.unmodifiableMap(new HashMap<Integer, String>() {
    {
        put(1, "some value");
                    //rest of code here
    }
});

12
public class Test {
    private static final Map<Integer, String> myMap;
    static {
        Map<Integer, String> aMap = ....;
        aMap.put(1, "one");
        aMap.put(2, "two");
        myMap = Collections.unmodifiableMap(aMap);
    }
}

หากเราประกาศค่าคงที่มากกว่าหนึ่งรหัสนั้นจะถูกเขียนในบล็อกคงที่และยากที่จะรักษาในอนาคต ดังนั้นจะเป็นการดีกว่าถ้าใช้คลาสที่ไม่ระบุชื่อ

public class Test {

    public static final Map numbers = Collections.unmodifiableMap(new HashMap(2, 1.0f){
        {
            put(1, "one");
            put(2, "two");
        }
    });
}

และแนะนำให้ใช้ unmodifiableMap สำหรับค่าคงที่อื่น ๆ อย่างชาญฉลาดไม่สามารถถือว่าเป็นค่าคงที่


10

ฉันขอแนะนำสไตล์ "double brace initialization" มากกว่าสไตล์บล็อกแบบคงที่

บางคนอาจแสดงความคิดเห็นว่าพวกเขาไม่ชอบคลาสนิรนามค่าใช้จ่ายประสิทธิภาพ ฯลฯ

แต่ที่ฉันพิจารณาเพิ่มเติมคือการอ่านและบำรุงรักษาโค้ด ในมุมมองนี้ฉันยืนวงเล็บปีกกาสองครั้งเป็นรูปแบบรหัสที่ดีกว่าวิธีการคงที่

  1. องค์ประกอบต่างๆซ้อนกันและอินไลน์
  2. มันเป็น OO มากขึ้นไม่ใช่ขั้นตอน
  3. ผลกระทบต่อประสิทธิภาพมีขนาดเล็กมากและอาจถูกเพิกเฉย
  4. การสนับสนุนโครงร่าง IDE ที่ดีขึ้น (แทนที่จะเป็นบล็อก {} แบบไม่ระบุชื่อจำนวนมาก)
  5. คุณบันทึกความคิดเห็นไม่กี่บรรทัดเพื่อให้พวกเขามีความสัมพันธ์
  6. ป้องกันการรั่วไหลขององค์ประกอบที่เป็นไปได้ / อินสแตนซ์ของวัตถุที่ไม่ได้กำหนดค่าเริ่มต้นจากข้อยกเว้นและเครื่องมือเพิ่มประสิทธิภาพ bytecode
  7. ไม่ต้องกังวลกับการสั่งการบล็อกคงที่

นอกจากนี้ยังทราบถึง GC ของชนชั้นที่ไม่ระบุชื่อคุณสามารถแปลงเป็น HashMap new HashMap(Map map)ปกติโดยใช้

คุณสามารถทำได้จนกว่าคุณจะประสบปัญหาอื่น หากคุณทำคุณควรใช้รูปแบบการเข้ารหัสอื่น (เช่นไม่มีสแตติกคลาสจากโรงงาน)


8

ตามปกติ apache-Commons มีวิธีการที่เหมาะสมMapUtils.putAll (แผนที่, วัตถุ []) :

ตัวอย่างเช่นในการสร้างแผนที่สี:

Map<String, String> colorMap = MapUtils.putAll(new HashMap<String, String>(), new String[][] {
     {"RED", "#FF0000"},
     {"GREEN", "#00FF00"},
     {"BLUE", "#0000FF"}
 });

ฉันรวม Apache Commons ในการสร้างทั้งหมดดังนั้นในกรณีที่ไม่มีวิธีการที่น่าเสียดายArrays.asMap( ... )ใน Java ธรรมดาฉันคิดว่านี่เป็นทางออกที่ดีที่สุด การประกอบล้อใหม่เป็นเรื่องโง่ ข้อเสียเล็กน้อยมากคือกับ generics จะต้องมีการแปลงไม่ จำกัด
ไมค์หนู

@mikerodent 4.1 เวอร์ชั่นเป็นแบบทั่วไป: public static <K, V> แผนที่ <K, V> putAll (แผนที่สุดท้าย <K, V> แผนที่, Object สุดท้าย [] array)
agad

Tx ... ใช่ฉันกำลังใช้ 4.1 แต่ฉันยังต้องSuppressWarnings( unchecked )อยู่ใน Eclipse ด้วยบรรทัดเช่นMap<String, String> dummy = MapUtils.putAll(new HashMap<String, String>(), new Object[][]... )
mike rodent

@mikerodent ไม่ใช่เพราะObject [] [] ? ดูอัปเดตที่ไม่ได้ตอบ - ฉันไม่มีคำเตือนใน Eclipse
agad

ช่างแปลกเหลือเกิน ... แม้เมื่อฉันไปString[][]ฉันจะได้รับ "คำเตือน"! และแน่นอนว่าใช้ได้เฉพาะกับคุณKและVเป็นคลาสเดียวกันเท่านั้น ฉันคิดว่าคุณยังไม่ได้ตั้งค่า "ไม่แปลงการแปลง" เป็น "ไม่สนใจ" ในการตั้งค่า Eclipse หรือไม่
ไมค์หนู

7

นี่คือสิ่งที่ฉันโปรดปรานเมื่อฉันไม่ต้องการ (หรือไม่สามารถ) ใช้ Guava ImmutableMap.of()หรือถ้าฉันต้องการการเปลี่ยนแปลงMap:

public static <A> Map<String, A> asMap(Object... keysAndValues) {
    return new LinkedHashMap<String, A>() {{
        for (int i = 0; i < keysAndValues.length - 1; i++) {
            put(keysAndValues[i].toString(), (A) keysAndValues[++i]);
        }
    }};
}

มันกะทัดรัดมากและไม่สนใจค่าหลงทาง (เช่นคีย์สุดท้ายโดยไม่มีค่า)

การใช้งาน:

Map<String, String> one = asMap("1stKey", "1stVal", "2ndKey", "2ndVal");
Map<String, Object> two = asMap("1stKey", Boolean.TRUE, "2ndKey", new Integer(2));

7

หากคุณต้องการแผนที่ที่แก้ไขไม่ได้ในที่สุด java 9 ก็เพิ่มวิธีofการMapเชื่อมต่อที่ยอดเยี่ยมให้กับโรงงาน มีการเพิ่มวิธีที่คล้ายกันในการตั้งค่ารายการเช่นกัน

Map<String, String> unmodifiableMap = Map.of("key1", "value1", "key2", "value2");


6

ฉันชอบการใช้ initializer แบบสแตติกเพื่อหลีกเลี่ยงการสร้างคลาสนิรนาม (ซึ่งจะไม่มีวัตถุประสงค์เพิ่มเติม) ดังนั้นฉันจะแสดงรายการเคล็ดลับการเริ่มต้นด้วย initializer แบบคงที่ โซลูชัน / เคล็ดลับที่ปรากฏในรายการทั้งหมดเป็นประเภทที่ปลอดภัย

หมายเหตุ:คำถามไม่ได้พูดอะไรเกี่ยวกับการทำให้แผนที่ไม่สามารถแก้ไขได้ดังนั้นฉันจะออกไป แต่รู้ว่ามันสามารถทำได้Collections.unmodifiableMap(map)ง่าย

เคล็ดลับแรก

เคล็ดลับแรกคือคุณสามารถอ้างอิงท้องถิ่นไปยังแผนที่และให้ชื่อสั้น ๆ :

private static final Map<Integer, String> myMap = new HashMap<>();
static {
    final Map<Integer, String> m = myMap; // Use short name!
    m.put(1, "one"); // Here referencing the local variable which is also faster!
    m.put(2, "two");
    m.put(3, "three");
}

เคล็ดลับที่สอง

เคล็ดลับที่ 2 คือคุณสามารถสร้างวิธีการช่วยเหลือเพื่อเพิ่มรายการ; คุณสามารถทำให้วิธีการช่วยเหลือนี้เป็นแบบสาธารณะได้หากคุณต้องการ:

private static final Map<Integer, String> myMap2 = new HashMap<>();
static {
    p(1, "one"); // Calling the helper method.
    p(2, "two");
    p(3, "three");
}

private static void p(Integer k, String v) {
    myMap2.put(k, v);
}

เมธอดตัวช่วยที่นี่ไม่สามารถใช้งานได้อีกครั้งเพราะมันสามารถเพิ่มองค์ประกอบmyMap2ได้เท่านั้น เพื่อให้สามารถใช้งานได้อีกครั้งเราสามารถทำให้ตัวแผนที่เป็นพารามิเตอร์ของวิธีการช่วยเหลือได้ แต่จากนั้นรหัสการเริ่มต้นจะไม่สั้นลง

เคล็ดลับที่สาม

เคล็ดลับที่ 3 คือคุณสามารถสร้างคลาสตัวช่วยตัวช่วยสร้างที่กลับมาใช้งานได้อีกครั้งด้วยฟังก์ชั่นการเติมข้อมูล นี่คือคลาสผู้ช่วย 10 บรรทัดที่ง่าย ๆ ซึ่งปลอดภัยต่อประเภท:

public class Test {
    private static final Map<Integer, String> myMap3 = new HashMap<>();
    static {
        new B<>(myMap3)   // Instantiating the helper class with our map
            .p(1, "one")
            .p(2, "two")
            .p(3, "three");
    }
}

class B<K, V> {
    private final Map<K, V> m;

    public B(Map<K, V> m) {
        this.m = m;
    }

    public B<K, V> p(K k, V v) {
        m.put(k, v);
        return this; // Return this for chaining
    }
}

5

คลาสที่ไม่ระบุชื่อที่คุณสร้างขึ้นใช้งานได้ดี อย่างไรก็ตามคุณควรทราบว่านี่เป็นคลาสภายในและจะมีการอ้างอิงไปยังอินสแตนซ์ของคลาสที่อยู่โดยรอบ ดังนั้นคุณจะพบว่าคุณไม่สามารถทำสิ่งบางอย่างกับมัน (ใช้XStreamสำหรับหนึ่ง) คุณจะได้รับข้อผิดพลาดแปลก ๆ

ต้องบอกว่าตราบใดที่คุณทราบวิธีการนี้ก็ใช้ได้ ฉันใช้เวลาส่วนใหญ่ในการเริ่มต้นคอลเลกชันทุกประเภทในรูปแบบที่กระชับ

แก้ไข: ชี้ให้เห็นอย่างถูกต้องในความคิดเห็นว่านี่เป็นคลาสที่คงที่ เห็นได้ชัดว่าฉันอ่านมันไม่ใกล้พอ อย่างไรก็ตามความคิดเห็นของฉันจะยังคงนำไปใช้กับการเรียนด้านที่ไม่ระบุชื่อ


3
ในกรณีนี้มันคงที่ดังนั้นไม่มีอินสแตนซ์ด้านนอก
Tom Hawtin - tackline

เนื้อหา XStream ไม่ควรพยายามทำให้เป็นอนุกรมสิ่งนี้ (มันคงที่ทำไมคุณจะต้องทำให้เป็นอันดับตัวแปรคงที่)
jasonmp85

5

หากคุณต้องการสิ่งที่กระชับและค่อนข้างปลอดภัยคุณสามารถเปลี่ยนการตรวจสอบประเภทเวลาคอมไพล์เป็นเวลาทำงาน:

static final Map<String, Integer> map = MapUtils.unmodifiableMap(
    String.class, Integer.class,
    "cat",  4,
    "dog",  2,
    "frog", 17
);

การใช้งานนี้ควรตรวจพบข้อผิดพลาด:

import java.util.HashMap;

public abstract class MapUtils
{
    private MapUtils() { }

    public static <K, V> HashMap<K, V> unmodifiableMap(
            Class<? extends K> keyClazz,
            Class<? extends V> valClazz,
            Object...keyValues)
    {
        return Collections.<K, V>unmodifiableMap(makeMap(
            keyClazz,
            valClazz,
            keyValues));
    }

    public static <K, V> HashMap<K, V> makeMap(
            Class<? extends K> keyClazz,
            Class<? extends V> valClazz,
            Object...keyValues)
    {
        if (keyValues.length % 2 != 0)
        {
            throw new IllegalArgumentException(
                    "'keyValues' was formatted incorrectly!  "
                  + "(Expected an even length, but found '" + keyValues.length + "')");
        }

        HashMap<K, V> result = new HashMap<K, V>(keyValues.length / 2);

        for (int i = 0; i < keyValues.length;)
        {
            K key = cast(keyClazz, keyValues[i], i);
            ++i;
            V val = cast(valClazz, keyValues[i], i);
            ++i;
            result.put(key, val);
        }

        return result;
    }

    private static <T> T cast(Class<? extends T> clazz, Object object, int i)
    {
        try
        {
            return clazz.cast(object);
        }
        catch (ClassCastException e)
        {
            String objectName = (i % 2 == 0) ? "Key" : "Value";
            String format = "%s at index %d ('%s') wasn't assignable to type '%s'";
            throw new IllegalArgumentException(String.format(format, objectName, i, object.toString(), clazz.getSimpleName()), e);
        }
    }
}

4

ด้วย Java 8 ฉันมาใช้รูปแบบต่อไปนี้:

private static final Map<String, Integer> MAP = Stream.of(
    new AbstractMap.SimpleImmutableEntry<>("key1", 1),
    new AbstractMap.SimpleImmutableEntry<>("key2", 2)
).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

มันไม่ได้เป็นบทสรุปที่สั้นที่สุดและวงเวียนเล็กน้อย แต่

  • มันไม่ต้องการอะไรนอกเหนือจาก java.util
  • มันเป็นประเภทที่ปลอดภัยและรองรับประเภทที่แตกต่างกันสำหรับคีย์และค่า

หากจำเป็นเราสามารถใช้toMapลายเซ็นรวมถึงผู้จัดหาแผนที่เพื่อระบุประเภทของแผนที่
zrvan



4

วิธีการที่สองของคุณ(การกำหนดค่าเริ่มต้นของ Double Brace)นั้นเป็นรูปแบบการต่อต้านดังนั้นฉันจะไปหาวิธีแรก

อีกวิธีที่ง่ายในการเริ่มต้นแผนที่แบบคงที่คือการใช้ฟังก์ชั่นยูทิลิตี้นี้:

public static <K, V> Map<K, V> mapOf(Object... keyValues) {
    Map<K, V> map = new HashMap<>(keyValues.length / 2);

    for (int index = 0; index < keyValues.length / 2; index++) {
        map.put((K)keyValues[index * 2], (V)keyValues[index * 2 + 1]);
    }

    return map;
}

Map<Integer, String> map1 = mapOf(1, "value1", 2, "value2");
Map<String, String> map2 = mapOf("key1", "value1", "key2", "value2");

หมายเหตุ: ในJava 9คุณสามารถใช้Map.of


3

ฉันไม่ชอบไวยากรณ์เริ่มต้นแบบคงที่และฉันไม่เชื่อว่าจะเป็นคลาสย่อยที่ไม่ระบุชื่อ โดยทั่วไปฉันเห็นด้วยกับข้อเสียทั้งหมดของการใช้ initializers คงที่และข้อเสียทั้งหมดในการใช้คลาสย่อยที่ไม่ระบุชื่อที่กล่าวถึงในคำตอบ previus ในทางกลับกันข้อดีที่นำเสนอในโพสต์เหล่านี้ไม่เพียงพอสำหรับฉัน ฉันชอบที่จะใช้วิธีการเริ่มต้นคงที่:

public class MyClass {
    private static final Map<Integer, String> myMap = prepareMap();

    private static Map<Integer, String> prepareMap() {
        Map<Integer, String> hashMap = new HashMap<>();
        hashMap.put(1, "one");
        hashMap.put(2, "two");

        return hashMap;
    }
}

3

ฉันไม่เห็นวิธีการที่ฉันใช้ (และเพิ่มขึ้นเช่น) โพสต์ในคำตอบใด ๆ ดังนั้นนี่คือ:

ฉันไม่ชอบการใช้ initializers คงที่เพราะพวกมัน clunky และฉันไม่ชอบคลาสที่ไม่ระบุชื่อเพราะมันกำลังสร้างคลาสใหม่สำหรับแต่ละอินสแตนซ์

ฉันต้องการเริ่มต้นแทนที่มีลักษณะดังนี้:

map(
    entry("keyA", "val1"),
    entry("keyB", "val2"),
    entry("keyC", "val3")
);

น่าเสียดายที่วิธีการเหล่านี้ไม่ได้เป็นส่วนหนึ่งของไลบรารีมาตรฐานของ Java ดังนั้นคุณจะต้องสร้าง (หรือใช้) ไลบรารียูทิลิตี้ที่กำหนดวิธีการดังต่อไปนี้:

 public static <K,V> Map<K,V> map(Map.Entry<K, ? extends V>... entries)
 public static <K,V> Map.Entry<K,V> entry(K key, V val)

(คุณสามารถใช้ 'นำเข้าคงที่' เพื่อหลีกเลี่ยงการต้องนำหน้าชื่อของวิธีการ)

ฉันพบว่ามีประโยชน์ที่จะให้วิธีการคงที่คล้ายกันสำหรับคอลเลกชันอื่น ๆ (รายการ, ชุด, SortSet, SortMap, ฯลฯ )

มันไม่ดีเท่าการเริ่มต้นวัตถุ json แต่มันเป็นขั้นตอนในทิศทางนั้นเท่าที่เกี่ยวข้องกับการอ่าน


3

เนื่องจาก Java ไม่รองรับตัวอักษรแผนที่แมปอินสแตนซ์แผนที่จะต้องมีการสร้างอินสแตนซ์และบรรจุอย่างชัดเจนเสมอ

โชคดีที่มันเป็นไปได้ที่จะใกล้เคียงกับพฤติกรรมของตัวอักษรของแผนที่ในจาวาโดยใช้วิธีการโรงงาน

ตัวอย่างเช่น:

public class LiteralMapFactory {

    // Creates a map from a list of entries
    @SafeVarargs
    public static <K, V> Map<K, V> mapOf(Map.Entry<K, V>... entries) {
        LinkedHashMap<K, V> map = new LinkedHashMap<>();
        for (Map.Entry<K, V> entry : entries) {
            map.put(entry.getKey(), entry.getValue());
        }
        return map;
    }
    // Creates a map entry
    public static <K, V> Map.Entry<K, V> entry(K key, V value) {
        return new AbstractMap.SimpleEntry<>(key, value);
    }

    public static void main(String[] args) {
        System.out.println(mapOf(entry("a", 1), entry("b", 2), entry("c", 3)));
    }
}

เอาท์พุท:

{a = 1, b = 2, c = 3}

สะดวกกว่าการสร้างและเติมองค์ประกอบของแผนที่ในแต่ละครั้ง


2

JEP 269มีวิธีการอำนวยความสะดวกบางอย่างสำหรับโรงงาน Collections API เมธอดจากโรงงานนี้ไม่ได้อยู่ในเวอร์ชัน Java ปัจจุบันซึ่งคือ 8 แต่ถูกวางแผนสำหรับการปล่อย Java 9

สำหรับการMapมีสองวิธีโรงงาน: และof ofEntriesการใช้ofคุณสามารถส่งผ่านคู่ของคีย์ / ค่าที่สลับกันได้ ตัวอย่างเช่นเพื่อสร้างสิ่งที่Mapชอบ{age: 27, major: cs}:

Map<String, Object> info = Map.of("age", 27, "major", "cs");

ขณะนี้มีเวอร์ชั่นที่โอเวอร์โหลดสิบรุ่นofดังนั้นคุณสามารถสร้างแผนที่ที่มีคู่คีย์ / ค่าสิบคู่ หากคุณไม่ชอบข้อ จำกัด นี้หรือการสลับคีย์ / ค่าคุณสามารถใช้ofEntries:

Map<String, Object> info = Map.ofEntries(
                Map.entry("age", 27),
                Map.entry("major", "cs")
);

ทั้งสองofและofEntriesจะคืนค่าไม่เปลี่ยนรูปMapดังนั้นคุณจะไม่สามารถเปลี่ยนแปลงองค์ประกอบของพวกเขาหลังจากการก่อสร้าง คุณสามารถลองใช้คุณลักษณะเหล่านี้โดยใช้JDK 9 ในช่วงต้นของการเข้าถึง


2

ดี ... ฉันชอบ enums;)

enum MyEnum {
    ONE   (1, "one"),
    TWO   (2, "two"),
    THREE (3, "three");

    int value;
    String name;

    MyEnum(int value, String name) {
        this.value = value;
        this.name = name;
    }

    static final Map<Integer, String> MAP = Stream.of( values() )
            .collect( Collectors.toMap( e -> e.value, e -> e.name ) );
}

2

ฉันอ่านคำตอบแล้วฉันก็ตัดสินใจที่จะเขียนตัวสร้างแผนที่ของฉันเอง รู้สึกฟรีเพื่อคัดลอกและสนุก

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * A tool for easy creation of a map. Code example:<br/>
 * {@code MapBuilder.of("name", "Forrest").and("surname", "Gump").build()}
 * @param <K> key type (inferred by constructor)
 * @param <V> value type (inferred by constructor)
 * @author Vlasec (for http://stackoverflow.com/a/30345279/1977151)
 */
public class MapBuilder <K, V> {
    private Map<K, V> map = new HashMap<>();

    /** Constructor that also enters the first entry. */
    private MapBuilder(K key, V value) {
        and(key, value);
    }

    /** Factory method that creates the builder and enters the first entry. */
    public static <A, B> MapBuilder<A, B> mapOf(A key, B value) {
        return new MapBuilder<>(key, value);
    }

    /** Puts the key-value pair to the map and returns itself for method chaining */
    public MapBuilder<K, V> and(K key, V value) {
        map.put(key, value);
        return this;
    }

    /**
     * If no reference to builder is kept and both the key and value types are immutable,
     * the resulting map is immutable.
     * @return contents of MapBuilder as an unmodifiable map.
     */
    public Map<K, V> build() {
        return Collections.unmodifiableMap(map);
    }
}

แก้ไข: เมื่อเร็ว ๆ นี้ฉันค้นหาวิธีคงที่สาธารณะ ofค่อนข้างบ่อยและฉันชอบมัน ฉันเพิ่มมันลงในรหัสและทำให้คอนสตรัคเตอร์ส่วนตัวดังนั้นจึงเปลี่ยนเป็นรูปแบบวิธีการคงที่จากโรงงาน

EDIT2: ยิ่งเมื่อเร็ว ๆ นี้ฉันไม่ชอบวิธีแบบคงที่อีกต่อไปที่เรียกว่าofเนื่องจากมันดูไม่ดีเมื่อใช้การนำเข้าแบบคงที่ ฉันเปลี่ยนชื่อเป็นmapOfแทนทำให้เหมาะสำหรับการนำเข้าแบบคงที่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.