วิธีเริ่มต้น HashMap โดยตรง (ตามตัวอักษร) ได้อย่างไร


1091

มีวิธีการเริ่มต้น Java HashMap เช่นนี้ไหม:

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

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



เกี่ยวข้องกันอย่างใกล้ชิด: stackoverflow.com/questions/507602/ (คำถามทั้งสองเกี่ยวกับการเริ่มต้นแผนที่คงที่ด้วยค่าคงที่และค่าสุดท้าย)
Jonik



หากคุณใช้ apache.commons.collections คุณสามารถใช้commons.apache.org/proper/commons-collections/javadocs/ …
ขวาน

คำตอบ:


1342

ทุกรุ่น

ในกรณีที่คุณเกิดขึ้นต้องเป็นเพียงแค่รายการเดียว: Collections.singletonMap("key", "value")มี

สำหรับ Java เวอร์ชัน 9 หรือสูงกว่า:

ใช่มันเป็นไปได้แล้วตอนนี้ ใน Java 9 มีการเพิ่มวิธีการในโรงงานสองวิธีเพื่อทำให้การสร้างแผนที่ง่ายขึ้น:

// 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")
);

ในตัวอย่างด้านบนทั้งสองtestและtest2จะเหมือนกันด้วยวิธีที่แตกต่างกันในการแสดงแผนที่ Map.ofวิธีการที่กำหนดไว้สำหรับขึ้นอยู่กับองค์ประกอบหนึ่งในสิบของแผนที่ในขณะที่Map.ofEntriesวิธีการที่จะไม่มีขีด จำกัด ดังกล่าว

โปรดทราบว่าในกรณีนี้แผนที่ที่ได้จะเป็นแผนที่ที่ไม่เปลี่ยนรูป หากคุณต้องการให้แผนที่ไม่แน่นอนคุณสามารถคัดลอกอีกครั้งเช่นใช้mutableMap = new HashMap<>(Map.of("a", "b"));

(ดูJEP 269และJavadoc ด้วย )

สูงถึง Java เวอร์ชัน 8:

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

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

อย่างไรก็ตามคลาสย่อยที่ไม่ระบุชื่ออาจแนะนำพฤติกรรมที่ไม่พึงประสงค์ในบางกรณี รวมถึงตัวอย่างเช่น:

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

การใช้ฟังก์ชั่นสำหรับการเริ่มต้นจะช่วยให้คุณสามารถสร้างแผนที่ใน initializer ได้ แต่จะหลีกเลี่ยงผลข้างเคียงที่น่ารังเกียจ:

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

3
สิ่งนี้จะไม่ทำงานหากคุณต้องการเริ่มต้นองค์ประกอบในฟังก์ชั่น ...
Michael

9
@Michael: ใช่ถ้าคุณต้องการใช้ฟังก์ชั่นมากกว่าที่คุณไม่สามารถใช้ฟังก์ชั่นไม่ได้ แต่ทำไมคุณถึงต้องการ
yankee

6
และสำหรับกรณีที่คุณต้องการแผนที่ที่มีรายการเดียวCollections.singletonMap():)
skwisgaar

3
ตอนนี้ที่มีเสถียรภาพ Java 9 ได้รับการปล่อยตัวออกมาฉันชอบที่ลิงค์นี้สำหรับ Javadoc และ +1 เพราะการพึ่งพาน้อยลงหนึ่งรายการ!
Franklin Yu

3
entryเอกสารJava 9 อยู่ที่ไหน
สูงศักดิ์

1029

นี่เป็นวิธีหนึ่ง

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

อย่างไรก็ตามคุณควรระวังและตรวจสอบให้แน่ใจว่าคุณเข้าใจรหัสข้างต้น (มันสร้างคลาสใหม่ที่สืบทอดมาจาก HashMap) ดังนั้นคุณควรอ่านเพิ่มเติมได้ที่นี่: http://www.c2.com/cgi/wiki?DoubleBraceInitialization หรือใช้ Guava:

Map<String, Integer> left = ImmutableMap.of("a", 1, "b", 2, "c", 3);

72
มันใช้งานได้ แต่มันน่าเกลียดและมีผลข้างเคียงที่มองไม่เห็นที่ผู้ใช้ควรเข้าใจก่อนทำ - ตัวอย่างเช่นการสร้างคลาสที่ไม่ระบุชื่อทั้งหมด
jprete

96
ใช่นั่นคือวิธีที่ฉันเขียนเกี่ยวกับการระมัดระวังและให้ลิงก์ไปยังคำอธิบาย
gregory561

6
ลิงค์ที่ยอดเยี่ยม การอ้างอิงในลิงก์ไปยังGreencoddsTenthRuleOfProgrammingนั้นคุ้มค่ากับการอ่าน
michaelok

19
คุณสามารถเพิ่ม "as ImmutableMap.builder.put (" k1 "," v1 "). put (" k2 "," v2 "). build ()" เป็นวิธี "of" ถูก จำกัด ไว้ที่ 5 คู่ที่มากที่สุด?
kommradHomer

3
+ สำหรับฝรั่งที่google.github.io/guava/releases/18.0/api/docs/com/google/common/...
shan

341

ถ้าคุณอนุญาตให้ libs บุคคลที่ 3 ให้คุณสามารถใช้ฝรั่ง 's ImmutableMapเพื่อให้บรรลุตามตัวอักษรเหมือนกะทัดรัด:

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

ใช้ได้กับคู่คีย์ / ค่าสูงสุด5 คู่มิฉะนั้นคุณสามารถใช้ตัวสร้าง :

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


  • โปรดสังเกตว่าการใช้งานImmutableMapของ Guava นั้นแตกต่างจากการนำHashMapไปใช้ของ Java (โดยเฉพาะอย่างยิ่งมันไม่เปลี่ยนรูปและไม่อนุญาตให้ใช้คีย์ / ค่า Null)
  • สำหรับข้อมูลเพิ่มเติมดูบทความคู่มือผู้ใช้ของ Guava เกี่ยวกับประเภทการเก็บที่ไม่เปลี่ยนแปลง

26
นอกจากนี้ฝรั่งยังมี ImmutableMap.builder.put ("k1", "v1"). put ("k2", "v2"). build ();
Xetius

17
ImmutableMap ไม่เหมือนกับ HashMap เนื่องจากมันจะล้มเหลวในค่า Null ขณะที่ HashMap ของแผนที่จะไม่
Gewthen

2
เพียงเพื่อช่วยเหลือผู้อื่นที่อาจประสบปัญหานี้ คุณต้องพิมพ์ตัวสร้างเพื่อให้เป็นแผนที่ <String, String> เช่นนี้: Map <String, String> test = ImmutableMap. <String, String> builder (). ใส่ ("k1", "v1") ใส่ ("k2", "v2"). build ();
Thiago

นี่คือ Jens ที่ยอดเยี่ยม!
gaurav

105

ไม่มีวิธีการทำเช่นนี้โดยตรง - Java ไม่มีตัวอักษรแผนที่ (ยัง - ฉันคิดว่าพวกเขาถูกเสนอสำหรับ Java 8)

บางคนชอบสิ่งนี้:

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

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

วิธีปกติจะเป็นเช่นนี้ (สำหรับตัวแปรท้องถิ่น):

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

หากtestแผนที่ของคุณเป็นตัวแปรอินสแตนซ์ให้กำหนดค่าเริ่มต้นไว้ในตัวสร้างหรือเครื่องมือเริ่มต้นอินสแตนซ์:

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

หากtestแผนที่ของคุณเป็นตัวแปรคลาสให้กำหนดค่าเริ่มต้นไว้ใน initializer คงที่:

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

Collections.unmodifiableMap(...)หากคุณต้องการแผนที่ของคุณจะไม่เปลี่ยนแปลงหลังจากที่คุณควรเริ่มต้นห่อแผนที่ของคุณโดย คุณสามารถทำได้ใน initializer คงที่เช่นกัน:

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);
}

(ฉันไม่แน่ใจว่าตอนนี้คุณสามารถทำtestขั้นสุดท้ายแล้ว ... ลองใช้งานและรายงานที่นี่)


61
Map<String,String> test = new HashMap<String, String>()
{
    {
        put(key1, value1);
        put(key2, value2);
    }
};

ง่ายและตรงประเด็น ฉันคิดว่าสิ่งนี้ที่มีส่วนคำอธิบายเพิ่มเติมจะเป็นคำตอบที่ดีที่สุด
ooolala

15
มีความหมายของหน่วยความจำที่ควรสังเกตว่ามี blog.jooq.org/2014/12/08/…
Amalgovinus

1
@Amalgovinus โดยทั่วไปโดยการสร้างคลาสย่อยใหม่คุณจะฮาร์ดโค้ดชนิดอาร์กิวเมนต์จากHashMapลงในคลาสย่อยนี้ สิ่งนี้สามารถทำงานได้หากคุณให้บริการจริงเท่านั้น (ด้วยว่างเปล่าใหม่) HashMap อาร์กิวเมนต์ประเภทนั้นไม่เกี่ยวข้องกัน)
Pa --lo Ebermann

1
ฉันชอบความสะอาดของมัน แต่มันสร้างคลาสนิรนามที่ไม่จำเป็นและมีปัญหาอธิบายไว้ที่นี่: c2.com/cgi/wiki?DoubleBraceInitialization
udachny

1
@hello_its_me: เพราะมันเหมือนกับstackoverflow.com/a/6802512/1386911คำตอบเพียงแค่การฟอร์แมตต่างกัน และในกรณีนี้การจัดรูปแบบที่ขยายนี้ไม่มีค่าเพิ่มเติมที่ด้านบนของรูปแบบกะทัดรัดเพื่อให้สามารถอ่านได้
Daniel Hári

44

อีกทางเลือกหนึ่งโดยใช้คลาส Java 7 ธรรมดาและ varargs: สร้างคลาสHashMapBuilderด้วยวิธีนี้:

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

ใช้วิธีการเช่นนี้:

HashMap<String,String> data = HashMapBuilder.build("key1","value1","key2","value2");

ฉันเขียนคำตอบที่ได้รับแรงบันดาลใจจากคุณ: stackoverflow.com/questions/507602/…
GeroldBroser เริ่มต้นใหม่ Monica

1
โซลูชันอื่นที่มี Apache Utils ที่ไม่เคยกล่าวถึง แต่สามารถอ่านได้โดยใช้ Java เวอร์ชันก่อนหน้านี้: MapUtils.putAll (HashMap ใหม่ <String, String> (), Object ใหม่ [] {"My key", "my value", ...
Rolintocour

4

TL; DR

ใช้Map.of…วิธีการใน Java 9 และใหม่กว่า

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

Map.of

Java 9 เพิ่มชุดของMap.ofวิธีการแบบคงที่จะทำเพียงแค่สิ่งที่คุณต้องการ: instantiate ไม่เปลี่ยนรูปMapโดยใช้ไวยากรณ์ที่แท้จริง

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

ลองดูวิธีการเหล่านี้โดยใช้ข้อมูลตัวอย่างสำหรับแผนที่วันต่อสัปดาห์กับบุคคลที่เราคาดว่าจะทำงานในวันนั้น

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

Map.of()

Map.ofMapสร้างที่ว่างเปล่า ไม่สามารถแก้ไขได้ดังนั้นคุณจึงไม่สามารถเพิ่มรายการได้ นี่คือตัวอย่างของแผนที่ดังกล่าวซึ่งไม่มีข้อมูล

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

dailyWorkerEmpty.toString (): {}

Map.of( … )

Map.of( k , v , k , v , …)มีหลายวิธีที่ใช้คู่กับคีย์ - ค่า 1 ถึง 10 นี่คือตัวอย่างของสองรายการ

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

weekendWorker.toString (): {SUNDAY = บุคคล {name = 'Bob'}, SATURDAY = บุคคล {name = 'Alice'}}

Map.ofEntries( … )

Map.ofEntries( Map.Entry , … )ใช้วัตถุจำนวนเท่าใดก็ได้ที่นำMap.Entryอินเตอร์เฟสมาใช้ Java รวมกลุ่มสองชั้นใช้อินเตอร์เฟซที่หนึ่งไม่แน่นอนที่ไม่เปลี่ยนรูปอื่น ๆ ,AbstractMap.SimpleEntry AbstractMap.SimpleImmutableEntryแต่เราไม่จำเป็นต้องระบุคลาสที่เป็นรูปธรรม เราเพียงแค่ต้องเรียกMap.entry( k , v )ใช้เมธอดผ่านคีย์และค่าของเราและเราจะเรียกวัตถุที่มีMap.Entryอินเทอร์เฟซมาใช้

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 = บุคคล {name = 'Bob'}, TUESDAY = บุคคล {name = 'Bob'}, THURSDAY = บุคคล {name = 'Carol'}, FRIDAY = บุคคล {name = 'Carol'} , MONDAY = บุคคล {name = 'Alice'}}

Map.copyOf

Java 10 Map.copyOfเพิ่มวิธีการ ผ่านแผนที่ที่มีอยู่กลับไปคัดลอกแผนที่ที่ไม่เปลี่ยนรูป

หมายเหตุ

ขอให้สังเกตว่าลำดับของตัววนซ้ำของแผนที่ที่ผลิตผ่านMap.ofคือไม่รับประกัน รายการมีคำสั่งโดยพลการ อย่าเขียนรหัสตามลำดับที่เห็นเนื่องจากเอกสารเตือนว่าคำสั่งนั้นอาจมีการเปลี่ยนแปลง

โปรดทราบว่าสิ่งเหล่านี้Map.of…วิธีการกลับมาเป็นMapของชั้นเรียนที่ไม่ได้ระบุ คลาสคอนกรีตพื้นฐานอาจแตกต่างจาก Java เวอร์ชันหนึ่งไปอีกเวอร์ชันหนึ่ง การไม่เปิดเผยตัวตนนี้ช่วยให้ Java สามารถเลือกใช้งานได้หลากหลายไม่ว่าข้อมูลของคุณจะเหมาะสมที่สุด ตัวอย่างเช่นหากคีย์ของคุณมาจากenum , Java อาจใช้EnumMapภายใต้ฝาครอบ


1

คุณสามารถสร้างวิธีของคุณเองMap.of(ซึ่งมีให้เฉพาะใน Java 9 และสูงกว่า) วิธีง่ายๆใน 2 วิธีง่ายๆ

กำหนดให้เป็นพารามิเตอร์จำนวนหนึ่ง

ตัวอย่าง

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

ทำให้มันเป็นรายการ

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

ตัวอย่าง

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)));
    }};
}

หมายเหตุ ไม่แนะนำให้ใช้สิ่งนี้กับทุกสิ่งเนื่องจากจะทำให้คลาสไม่ระบุชื่อทุกครั้งที่คุณใช้สิ่งนี้


1

JAVA 8

ใน Java ธรรมดา 8 คุณมีความเป็นไปได้ที่Streams/Collectorsจะใช้ในการทำงาน

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

นี่เป็นข้อดีของการไม่สร้างคลาสไม่ระบุชื่อ

โปรดทราบว่าการนำเข้าคือ:

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

แน่นอนตามที่ระบุไว้ในคำตอบอื่น ๆ ใน java 9 เป็นต้นไปคุณมีวิธีที่ง่ายกว่าในการทำเช่นเดียวกัน


0

น่าเสียดายที่การใช้ varargs หากประเภทของคีย์และค่าไม่เหมือนกันจะไม่สมเหตุสมผลเท่าที่คุณจะต้องใช้Object...และสูญเสียความปลอดภัยของประเภทโดยสิ้นเชิง หากคุณต้องการสร้างเช่น a Map<String, String>, แน่นอนว่า a toMap(String... args)อาจเป็นไปได้ แต่ไม่สวยมากเพราะมันง่ายที่จะผสมคีย์และค่าต่างๆและการโต้แย้งจำนวนคี่จะไม่ถูกต้อง

คุณสามารถสร้างคลาสย่อยของ HashMap ที่มีวิธีการเชื่อมต่อได้

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

และใช้มันเหมือน new ChainableMap<String, Object>().set("a", 1).set("b", "foo")

อีกวิธีหนึ่งคือการใช้รูปแบบการสร้างทั่วไป:

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

และใช้มันเหมือน new MapBuilder<String, Object>().put("a", 1).put("b", "foo").build();

อย่างไรก็ตามวิธีที่ฉันใช้ตอนนี้แล้วใช้ varargs และPairclass:

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");

ความฟุ่มเฟื่อยของPair.create()รบกวนฉันเล็กน้อย แต่มันใช้งานได้ดี หากคุณไม่สนใจการนำเข้าแบบคงที่คุณสามารถสร้างผู้ช่วยได้แน่นอน:

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");

(แทนที่จะPairเป็นหนึ่งสามารถจินตนาการใช้Map.Entryแต่เนื่องจากมันเป็นอินเทอร์เฟซจึงต้องใช้คลาสและ / หรือวิธีการโรงงานผู้ช่วยมันยังไม่เปลี่ยนรูปและมีตรรกะอื่น ๆ ที่ไม่เป็นประโยชน์สำหรับงานนี้)



0

หากคุณต้องการวางคู่คีย์ - ค่าเพียงคู่เดียวคุณสามารถใช้ Collections.singletonMap (คีย์, ค่า);


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