HashMap để trả về giá trị mặc định cho các khóa không tìm thấy?


151

Có thể có HashMapgiá trị trả về mặc định cho tất cả các khóa không tìm thấy trong tập hợp không?


Bạn có thể kiểm tra sự tồn tại của khóa và trả về mặc định. Hoặc mở rộng lớp và sửa đổi hành vi. hoặc thậm chí bạn có thể sử dụng null - và đặt một số kiểm tra bất cứ nơi nào bạn muốn sử dụng nó.
SudhirJ

2
Điều này có liên quan / trùng lặp với stackoverflow.com/questions/4833336/ Khăn một số tùy chọn khác được thảo luận ở đó.
Mark Butler

3
Kiểm tra giải pháp Java 8 cho getOrDefault() liên kết
Trey Jonn

Câu trả lời:


136

[Cập nhật]

Như đã lưu ý bởi các câu trả lời và nhận xét khác, kể từ Java 8, bạn có thể chỉ cần gọi Map#getOrDefault(...).

[Nguyên]

Không có triển khai Bản đồ nào thực hiện điều này một cách chính xác nhưng sẽ rất đơn giản khi thực hiện bằng cách mở rộng 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
Chỉ cần để được chính xác, bạn có thể muốn điều chỉnh tình trạng này từ (v == null)để (v == null && !this.containsKey(k))trong trường hợp họ cố tình thêm một nullgiá trị. Tôi biết, đây chỉ là một trường hợp góc, nhưng tác giả có thể gặp phải nó.
Adam Paynter

@maerics: Tôi nhận thấy rằng bạn đã sử dụng !this.containsValue(null). Điều này là khác biệt tinh tế từ !this.containsKey(k). Các containsValuegiải pháp sẽ thất bại nếu một số khác quan trọng đã được chỉ định một cách rõ ràng giá trị của null. Ví dụ: map = new HashMap(); map.put(k1, null); V v = map.get(k2);Trong trường hợp này, vvẫn sẽ null, đúng không?
Adam Paynter

21
Nói chung , tôi nghĩ rằng đây là một ý tưởng tồi - tôi sẽ đẩy hành vi mặc định vào máy khách hoặc một đại biểu không tuyên bố là Bản đồ. Cụ thể, việc thiếu keyset () hoặc entryset () hợp lệ sẽ gây ra vấn đề với bất kỳ điều gì khiến hợp đồng Map được tôn trọng. Và tập hợp vô hạn các khóa hợp lệ chứa hàm Key () có khả năng gây ra hiệu năng xấu khó chẩn đoán. Tuy nhiên, không thể nói rằng nó có thể không phục vụ một số mục đích cụ thể.
Ed Staub

Một vấn đề với cách tiếp cận này là nếu giá trị là một đối tượng phức tạp. Bản đồ <Chuỗi, Danh sách> #put sẽ không hoạt động như mong đợi.
Mắt

Không hoạt động trên ConcảnHashMap. Ở đó, bạn nên kiểm tra kết quả của get cho null.
dveim

162

Trong Java 8, sử dụng Map.getOrDefault . Nó nhận khóa và giá trị trả về nếu không tìm thấy khóa khớp nào.


14
getOrDefaultlà rất đẹp, nhưng yêu cầu định nghĩa mặc định mỗi khi bản đồ được truy cập. Xác định giá trị mặc định một lần cũng sẽ có lợi ích dễ đọc khi tạo bản đồ tĩnh của các giá trị.
ach

3
Điều này là tầm thường để thực hiện chính mình. private static String get(Map map, String s) { return map.getOrDefault(s, "Error"); }
Jack Satriano

@JackSatriano Vâng, nhưng bạn phải mã hóa giá trị mặc định hoặc có một biến tĩnh cho nó.
Blrp

1
Xem bên dưới câu trả lời bằng compute IfAbsent, tốt hơn khi giá trị mặc định đắt hoặc mỗi lần nên khác nhau.
hectorpal

Mặc dù nó tệ hơn cho bộ nhớ và sẽ chỉ tiết kiệm thời gian tính toán nếu giá trị mặc định đắt khi xây dựng / tính toán. Nếu nó rẻ, có lẽ bạn sẽ thấy nó hoạt động kém hơn, vì nó phải chèn vào bản đồ, thay vì chỉ trả về một giá trị mặc định. Chắc chắn một lựa chọn khác mặc dù.
Spycho

73

Sử dụng Commons' DefaultedMap nếu bạn không cảm thấy như reinventing the wheel, ví dụ:

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

Bạn cũng có thể vượt qua trong một bản đồ hiện có nếu bạn không phụ trách việc tạo bản đồ ở nơi đầu tiên.


26
+1 mặc dù đôi khi dễ dàng phát minh lại bánh xe hơn là giới thiệu các phụ thuộc lớn cho các lát nhỏ có chức năng đơn giản.
maerics

3
và điều buồn cười là, nhiều dự án mà tôi làm việc đã có một cái gì đó giống như vậy trong classpath (cả Apache Commons hoặc Google Guava)
bartosz.r

@ bartosz.r, chắc chắn không có trên điện thoại di động
Pacerier

44

Java 8 đã giới thiệu một phương thức mặc định compute IfAbsent đẹp mắt để Mapgiao diện lưu trữ giá trị được tính toán lười biếng và do đó không phá vỡ hợp đồng bản đồ:

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

Xuất xứ: http://blog.javabien.net/2014/02/20/loadingcache-in-java-8-without-guava/

Tuyên bố từ chối trách nhiệm: Câu trả lời này không khớp chính xác với những gì OP yêu cầu nhưng có thể hữu ích trong một số trường hợp khớp với tiêu đề của câu hỏi khi số khóa bị giới hạn và bộ nhớ đệm của các giá trị khác nhau sẽ có lợi. Nó không nên được sử dụng trong trường hợp ngược lại với nhiều khóa và cùng giá trị mặc định vì điều này sẽ không lãng phí bộ nhớ.


Không phải những gì OP yêu cầu: anh ta muốn không có tác dụng phụ trên bản đồ. Ngoài ra, lưu trữ giá trị mặc định cho mỗi khóa vắng mặt là mất không gian bộ nhớ vô ích.
numéro6

@ numéro6, vâng, điều này không khớp chính xác với những gì OP yêu cầu nhưng một số người làm việc vẫn thấy câu trả lời bên này hữu ích. Như các câu trả lời khác đã đề cập, không thể đạt được chính xác những gì OP yêu cầu mà không phá vỡ hợp đồng bản đồ. Một cách giải quyết khác không được đề cập ở đây là sử dụng một bản tóm tắt khác thay vì Bản đồ .
Vadzim

Có thể đạt được chính xác những gì OP yêu cầu mà không phá vỡ hợp đồng bản đồ. Không có cách giải quyết nào là cần thiết, chỉ cần sử dụng getOrDefault là cách đúng (cập nhật nhất), compute IfAbsent là cách sai: bạn sẽ mất thời gian gọi ánh xạ Chức năng và bộ nhớ bằng cách lưu trữ kết quả (cả hai cho mỗi khóa bị thiếu). Tôi không thể thấy bất kỳ lý do chính đáng nào để làm điều đó thay vì getOrDefault. Những gì tôi mô tả là lý do chính xác tại sao có hai phương pháp riêng biệt trong hợp đồng Bản đồ: có hai trường hợp sử dụng riêng biệt không nên nhầm lẫn (tôi phải sửa một số trong công việc). Câu trả lời này lan truyền sự nhầm lẫn.
numéro6

14

Bạn có thể tạo một phương thức tĩnh thực hiện chính xác điều này không?

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

Lưu trữ tĩnh ở đâu?
Pacerier

10

Bạn có thể chỉ cần tạo một lớp mới kế thừa HashMap và thêm phương thức getDefault. Đây là một mã mẫu:

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

        return defaultValue;
    }
}

Tôi nghĩ rằng bạn không nên ghi đè phương thức get (khóa K) trong quá trình triển khai của mình, vì những lý do được Ed Staub chỉ định trong nhận xét của anh ấy và vì bạn sẽ phá vỡ hợp đồng của giao diện Bản đồ (điều này có thể dẫn đến một số khó tìm lỗi).


4
Bạn đã có một điểm trong việc không ghi đè getphương thức. Mặt khác - giải pháp của bạn không cho phép sử dụng lớp thông qua giao diện, thường có thể là trường hợp.
Krzysztof Jabłoński


3

Nó làm điều này theo mặc định. Nó trở lại null.


@Larry, Có vẻ hơi ngớ ngẩn với tôi để phân lớp HashMapchỉ cho chức năng này khi nullhoàn toàn ổn.
mrkhrts

15
Tuy nhiên, sẽ không ổn nếu bạn đang sử dụng một NullObjectmẫu hoặc không muốn phân tán kiểm tra null trong toàn bộ mã của bạn - một mong muốn tôi hoàn toàn hiểu được.
Dave Newton


1

Tôi thấy LazyMap khá hữu ích.

Khi phương thức get (Object) được gọi với một khóa không tồn tại trong bản đồ, nhà máy được sử dụng để tạo đối tượng. Đối tượng đã tạo sẽ được thêm vào bản đồ bằng phím được yêu cầu.

Điều này cho phép bạn làm một cái gì đó như thế này:

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

Cuộc gọi để gettạo một giá trị mặc định cho khóa đã cho. Bạn chỉ định cách tạo giá trị mặc định với đối số gốc LazyMap.lazyMap(map, factory). Trong ví dụ trên, bản đồ được khởi tạo thành một bản mớiAtomicInteger với giá trị 0.


Điều này có một lợi thế so với câu trả lời được chấp nhận ở chỗ giá trị mặc định được tạo bởi một nhà máy. Điều gì xảy ra nếu giá trị mặc định của tôi là List<String>- sử dụng câu trả lời được chấp nhận Tôi sẽ mạo hiểm khi sử dụng cùng một danh sách cho mỗi khóa mới, thay vì (nói) a new ArrayList<String>()từ một nhà máy.


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

Cách sử dụng ví dụ:

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

Tôi cần đọc kết quả trả về từ máy chủ trong JSON nơi tôi không thể đảm bảo các trường sẽ có mặt. Tôi đang sử dụng lớp org.json.simple.JSONObject có nguồn gốc từ HashMap. Dưới đây là một số chức năng trợ giúp tôi sử dụng:

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.