HashMap เพื่อส่งคืนค่าเริ่มต้นสำหรับคีย์ที่ไม่พบหรือไม่


151

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


คุณสามารถตรวจสอบการมีอยู่ของคีย์และคืนค่าเริ่มต้น หรือขยายคลาสและปรับเปลี่ยนพฤติกรรม หรือแม้กระทั่งคุณสามารถใช้เป็นโมฆะ - และใส่ตรวจสอบทุกที่ที่คุณต้องการใช้
SudhirJ

2
นี่คือความเกี่ยวข้อง / ซ้ำของstackoverflow.com/questions/4833336/…ตัวเลือกอื่น ๆ ที่กล่าวถึงมี
Mark Butler

3
ลองดูโซลูชัน Java 8 สำหรับgetOrDefault() ลิงก์
Trey Jonn

คำตอบ:


136

[Update]

เท่าที่สังเกตจากคำตอบอื่น ๆ และการแสดงความคิดเห็นเป็นของ Java 8 Map#getOrDefault(...)คุณก็สามารถโทร

[ต้นฉบับ]

ไม่มีการใช้งานแผนที่ที่ทำสิ่งนี้ได้อย่างแน่นอน แต่การใช้งานของคุณเองด้วยการขยาย HashMap:

public class DefaultHashMap<K,V> extends HashMap<K,V> {
  protected V defaultValue;
  public DefaultHashMap(V defaultValue) {
    this.defaultValue = defaultValue;
  }
  @Override
  public V get(Object k) {
    return containsKey(k) ? super.get(k) : defaultValue;
  }
}

20
เพื่อความแม่นยำคุณอาจต้องการปรับเงื่อนไขจาก(v == null)เป็นเป็น(v == null && !this.containsKey(k))ในกรณีที่ต้องการเพิ่มnullมูลค่า ฉันรู้ว่านี่เป็นเพียงแค่มุม แต่ผู้เขียนอาจพบเจอ
Adam Paynter

@maerics: !this.containsValue(null)ผมสังเกตเห็นว่าที่คุณใช้ !this.containsKey(k)นี้เป็นอย่างละเอียดที่แตกต่างจาก containsValueวิธีการแก้ปัญหาจะล้มเหลวถ้าบางคนอื่น ๆnullที่สำคัญได้รับมอบหมายค่าอย่างชัดเจน ตัวอย่างเช่น: map = new HashMap(); map.put(k1, null); V v = map.get(k2);ในกรณีนี้vจะยังคงnullถูกต้องหรือไม่
Adam Paynter

21
โดยทั่วไปแล้วฉันคิดว่านี่เป็นความคิดที่ไม่ดี - ฉันจะผลักดันพฤติกรรมการเริ่มต้นไปยังลูกค้าหรือตัวแทนที่ไม่ได้อ้างว่าเป็นแผนที่ โดยเฉพาะอย่างยิ่งการขาด keySet () ที่ถูกต้องหรือ entrySet () จะทำให้เกิดปัญหากับสิ่งใดก็ตามที่คาดว่าสัญญาแผนที่จะได้รับการเคารพ และชุดคีย์ที่ใช้ได้ไม่ จำกัด ซึ่งมีKey () หมายถึงมีแนวโน้มที่จะทำให้ประสิทธิภาพการทำงานแย่ซึ่งยากต่อการวินิจฉัย แม้ว่าจะไม่พูดว่ามันอาจไม่ตอบสนองวัตถุประสงค์เฉพาะบางอย่าง
Ed Staub

ปัญหาอย่างหนึ่งของวิธีนี้คือถ้าค่าเป็นวัตถุที่ซับซ้อน แผนที่ <String, List> #put จะไม่ทำงานตามที่คาดไว้
Eyal

ไม่ทำงานบน ConcurrentHashMap ที่นั่นคุณควรตรวจสอบผลลัพธ์ของการหาค่าว่าง
dveim

162

ใน Java 8 ใช้Map.getOrDefault ใช้คีย์และค่าที่จะส่งคืนหากไม่พบคีย์ที่ตรงกัน


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

3
นี่เป็นเรื่องเล็กน้อยที่จะใช้ตัวคุณเอง private static String get(Map map, String s) { return map.getOrDefault(s, "Error"); }
Jack Satriano

@JackSatriano ใช่ แต่คุณต้องฮาร์ดโค้ดค่าเริ่มต้นหรือมีตัวแปรแบบคงที่
Blrp

1
ดูด้านล่างคำตอบโดยใช้ compute ถ้าดีกว่าเมื่อค่าเริ่มต้นแพงหรือควรแตกต่างกันในแต่ละครั้ง
hectorpal

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

73

ใช้DefaultedMapของคอมมอนส์หากคุณไม่รู้สึกเหมือนการพลิกโฉมพวงมาลัยเช่น

Map<String, String> map = new DefaultedMap<>("[NO ENTRY FOUND]");
String surname = map.get("Surname"); 
// surname == "[NO ENTRY FOUND]"

คุณสามารถส่งผ่านแผนที่ที่มีอยู่ได้หากคุณไม่รับผิดชอบในการสร้างแผนที่ตั้งแต่แรก


26
+1 แม้ว่าบางครั้งมันง่ายที่จะบูรณาการล้อกว่าที่จะแนะนำการพึ่งพาขนาดใหญ่สำหรับชิ้นเล็ก ๆ ของฟังก์ชั่นที่เรียบง่าย
maerics

3
และสิ่งที่ตลกคือโครงการจำนวนมากที่ฉันทำงานด้วยมีบางอย่างเช่นนี้ใน classpath (เช่น Apache Commons หรือ Google Guava)
bartosz.r

@ bartosz.r ไม่ใช่มือถือ
Pacerier

44

Java 8 แนะนำวิธีการเริ่มต้นที่ดีcomputeIfAbsent ในการMapเชื่อมต่อซึ่งเก็บค่าที่ขี้เกียจและดังนั้นจึงไม่ทำลายสัญญาแผนที่:

Map<Key, Graph> map = new HashMap<>();
map.computeIfAbsent(aKey, key -> createExpensiveGraph(key));

แหล่งกำเนิด: http://blog.javabien.net/2014/02/20/loadingcache-in-java-8-without-guava/

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


ไม่ใช่สิ่งที่ OP ถาม: เขาไม่ต้องการผลข้างเคียงบนแผนที่ นอกจากนี้การจัดเก็บค่าเริ่มต้นสำหรับแต่ละคีย์ที่ขาดหายไปคือการสูญเสียพื้นที่หน่วยความจำที่ไร้ประโยชน์
numéro6

@ numéro6ใช่มันไม่ตรงกับที่ OP ถาม แต่คน googling บางคนยังพบว่าคำตอบด้านนี้มีประโยชน์ ตามคำตอบอื่น ๆ ที่กล่าวถึงมันเป็นไปไม่ได้ที่จะบรรลุสิ่งที่ OP ถามโดยไม่ทำลายสัญญาแผนที่ วิธีแก้ปัญหาอื่นไม่ได้กล่าวถึงที่นี่คือการใช้สิ่งที่เป็นนามธรรมแทนแผนที่อื่น
Vadzim

เป็นไปได้ที่จะบรรลุสิ่งที่ OP ถามโดยไม่ทำลายสัญญาแผนที่ ไม่จำเป็นต้องใช้วิธีการแก้ปัญหาเพียงแค่ใช้ getOrDefault เป็นวิธีที่ถูกต้อง (อัปเดตล่าสุด) compute หากAbsentเป็นวิธีที่ผิด: คุณจะเสียเวลาในการเรียกการแมปฟังก์ชันและหน่วยความจำโดยการจัดเก็บผลลัพธ์ ฉันไม่เห็นเหตุผลที่ดีที่จะทำเช่นนั้นแทน getOrDefault สิ่งที่ฉันอธิบายคือเหตุผลที่แน่นอนว่าทำไมมีสองวิธีที่แตกต่างกันในสัญญาแผนที่: มีสองกรณีการใช้ที่แตกต่างกันที่ไม่ควรสับสน (ฉันต้องแก้ไขบางอย่างในที่ทำงาน) คำตอบนี้กระจายความสับสน
numéro6

14

คุณไม่สามารถสร้างวิธีแบบคงที่ที่ทำสิ่งนี้ได้ใช่หรือไม่

private static <K, V> V getOrDefault(Map<K,V> map, K key, V defaultValue) {
    return map.containsKey(key) ? map.get(key) : defaultValue;
}

สถานที่จัดเก็บแบบคงที่?
Pacerier

10

คุณสามารถสร้างคลาสใหม่ที่สืบทอด HashMap และเพิ่มเมธอด getDefault นี่คือตัวอย่างรหัส:

public class DefaultHashMap<K,V> extends HashMap<K,V> {
    public V getDefault(K key, V defaultValue) {
        if (containsKey(key)) {
            return get(key);
        }

        return defaultValue;
    }
}

ฉันคิดว่าคุณไม่ควรลบล้างวิธี get (K key) ในการติดตั้งของคุณเนื่องจากเหตุผลที่ระบุโดย Ed Staub ในความคิดเห็นของเขาและเพราะคุณจะทำสัญญากับส่วนต่อประสานของ Map (สิ่งนี้อาจทำให้ยากต่อการค้นหา ข้อบกพร่อง)


4
คุณมีจุดที่ไม่ได้เอาชนะgetวิธีนี้ ในทางกลับกัน - โซลูชันของคุณไม่อนุญาตให้ใช้คลาสผ่านอินเทอร์เฟซซึ่งอาจเป็นกรณี
Krzysztof Jabłoński


3

มันทำตามค่าเริ่มต้น มันกลับnullมา


@ Larry ดูเหมือนว่าโง่เล็กน้อยสำหรับ subclass HashMapสำหรับฟังก์ชั่นนี้เมื่อใช้งานnullได้ดีอย่างสมบูรณ์แบบ
mrkhrts

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


1

ฉันพบว่าLazyMapมีประโยชน์มาก

เมื่อมีการเรียกใช้เมธอด get (Object) ด้วยรหัสที่ไม่มีอยู่ในแผนที่โรงงานจะใช้ในการสร้างวัตถุ วัตถุที่สร้างจะถูกเพิ่มลงในแผนที่โดยใช้คีย์ที่ร้องขอ

สิ่งนี้อนุญาตให้คุณทำสิ่งนี้:

    Map<String, AtomicInteger> map = LazyMap.lazyMap(new HashMap<>(), ()->new AtomicInteger(0));
    map.get(notExistingKey).incrementAndGet();

การเรียกเพื่อgetสร้างค่าเริ่มต้นสำหรับคีย์ที่กำหนด LazyMap.lazyMap(map, factory)คุณสามารถระบุวิธีการสร้างค่าเริ่มต้นที่มีการโต้แย้งโรงงาน ในตัวอย่างด้านบนแผนที่จะเริ่มต้นใหม่AtomicIntegerด้วยค่า 0


นี่เป็นข้อได้เปรียบเหนือคำตอบที่ยอมรับซึ่งค่าเริ่มต้นถูกสร้างโดยโรงงาน จะเกิดอะไรขึ้นถ้าค่าเริ่มต้นของฉันคือList<String>- การใช้คำตอบที่ยอมรับฉันจะเสี่ยงต่อการใช้รายการเดียวกันสำหรับคีย์ใหม่แต่ละคีย์แทนที่จะเป็น ( new ArrayList<String>()จาก) a จากโรงงาน


0
/**
 * Extension of TreeMap to provide default value getter/creator.
 * 
 * NOTE: This class performs no null key or value checking.
 * 
 * @author N David Brown
 *
 * @param <K>   Key type
 * @param <V>   Value type
 */
public abstract class Hash<K, V> extends TreeMap<K, V> {

    private static final long serialVersionUID = 1905150272531272505L;

    /**
     * Same as {@link #get(Object)} but first stores result of
     * {@link #create(Object)} under given key if key doesn't exist.
     * 
     * @param k
     * @return
     */
    public V getOrCreate(final K k) {
        V v = get(k);
        if (v == null) {
            v = create(k);
            put(k, v);
        }
        return v;
    }

    /**
     * Same as {@link #get(Object)} but returns specified default value
     * if key doesn't exist. Note that default value isn't automatically
     * stored under the given key.
     * 
     * @param k
     * @param _default
     * @return
     */
    public V getDefault(final K k, final V _default) {
        V v = get(k);
        return v == null ? _default : v;
    }

    /**
     * Creates a default value for the specified key.
     * 
     * @param k
     * @return
     */
    abstract protected V create(final K k);
}

ตัวอย่างการใช้งาน:

protected class HashList extends Hash<String, ArrayList<String>> {
    private static final long serialVersionUID = 6658900478219817746L;

    @Override
        public ArrayList<Short> create(Short key) {
            return new ArrayList<Short>();
        }
}

final HashList haystack = new HashList();
final String needle = "hide and";
haystack.getOrCreate(needle).add("seek")
System.out.println(haystack.get(needle).get(0));

0

ฉันจำเป็นต้องอ่านผลลัพธ์ที่ส่งคืนจากเซิร์ฟเวอร์ใน JSON ซึ่งฉันไม่สามารถรับประกันได้ว่าจะมีฟิลด์ต่างๆ ฉันใช้ class org.json.simple.JSONObject ซึ่งมาจาก HashMap นี่คือฟังก์ชั่นตัวช่วยที่ฉันใช้:

public static String getString( final JSONObject response, 
                                final String key ) 
{ return getString( response, key, "" ); }  
public static String getString( final JSONObject response, 
                                final String key, final String defVal ) 
{ return response.containsKey( key ) ? (String)response.get( key ) : defVal; }

public static long getLong( final JSONObject response, 
                            final String key ) 
{ return getLong( response, key, 0 ); } 
public static long getLong( final JSONObject response, 
                            final String key, final long defVal ) 
{ return response.containsKey( key ) ? (long)response.get( key ) : defVal; }

public static float getFloat( final JSONObject response, 
                              final String key ) 
{ return getFloat( response, key, 0.0f ); } 
public static float getFloat( final JSONObject response, 
                              final String key, final float defVal ) 
{ return response.containsKey( key ) ? (float)response.get( key ) : defVal; }

public static List<JSONObject> getList( final JSONObject response, 
                                        final String key ) 
{ return getList( response, key, new ArrayList<JSONObject>() ); }   
public static List<JSONObject> getList( final JSONObject response, 
                                        final String key, final List<JSONObject> defVal ) { 
    try { return response.containsKey( key ) ? (List<JSONObject>) response.get( key ) : defVal; }
    catch( ClassCastException e ) { return defVal; }
}   

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