map 容易被忽略的bug小結

Map是我們在工作中經常使用數據結構,但也因爲自己的忽略造成一些bug,這裏做一個小結。

一、lamda表達式中,list組成map但key重複造成IllegalStateException。

  public static void main(String[] args) {

        ArrayList<InnerVO> innerList = new ArrayList<>();
        InnerVO innerVO1 = new InnerVO();
        innerVO1.setId("testId1");
        innerVO1.setName("testName1");

        InnerVO innerVO2 = new InnerVO();
        innerVO2.setId("testId1");
       innerVO2.setName("testName2");

        innerList.add(innerVO1);
        innerList.add(innerVO2);

        Map<String, String> map = innerList.stream().collect(Collectors.toMap(InnerVO::getId, InnerVO::getName));
        System.out.println(map);

    }

由於list中不同對象的key不同,形成重複key引起的異常。

Exception in thread "main" java.lang.IllegalStateException: Duplicate key testName1
	at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133)
	at java.util.HashMap.merge(HashMap.java:1254)
	at java.util.stream.Collectors.lambda$toMap$58(Collectors.java:1320)
	at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
	at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1382)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
	at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
	at com.yin.databaseproject.util.BugShow.main(BugShow.java:31)

我們常用解決方法是,當key重複的時候用後面的值代替前面的值,或者前面值代替後面的值。
Map<String, String> map = innerList.stream().collect(Collectors.toMap(InnerVO::getId, InnerVO::getName)); 改爲下面這一行:

Map<String, String> map = innerList.stream().collect(Collectors.toMap(InnerVO::getId, InnerVO::getName, (o, n) 

當出現key重複的時候,後面值覆蓋原來的值。
在這裏插入圖片描述
改爲下面一行時候:

 Map<String, String> map = innerList.stream().collect(Collectors.toMap(InnerVO::getId, InnerVO::getName, (o, n) -> o));

當出現key重複的時候,使用原本的值。
在這裏插入圖片描述

二、lamda表達式中,list組成map種value爲null造成空指針異常。

 public static void main(String[] args) {

        ArrayList<InnerVO> innerList = new ArrayList<>();
        InnerVO innerVO1 = new InnerVO();
        innerVO1.setId("testId1");
        innerVO1.setName("testName1");

        InnerVO innerVO2 = new InnerVO();
        innerVO2.setId("testId2");
       //innerVO2.setName("testName2");

        innerList.add(innerVO1);
        innerList.add(innerVO2);

        Map<String, String> map = innerList.stream().collect(Collectors.toMap(InnerVO::getId, InnerVO::getName, (o, n) -> n));
        System.out.println(map);

    }
Exception in thread "main" java.lang.NullPointerException
	at java.util.HashMap.merge(HashMap.java:1225)
	at java.util.stream.Collectors.lambda$toMap$58(Collectors.java:1320)
	at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
	at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1382)
	at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
	at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
	at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
	at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
	at com.yin.databaseproject.util.BugShow.main(BugShow.java:31)

其實這個如果不是遇到,不太容易發現,因爲hashMap中value是可以爲null的。
1、可以直接改爲,當爲null就好。

HashMap<String, String> map = new HashMap<>();
        for (InnerVO innerVO : innerList) {
            map.put(innerVO.getId(), innerVO.getName());
        }

2、lamda 表達式中,如果爲null直接給一個默認值

 Map<String, String> map = innerList.stream().collect(Collectors.toMap(InnerVO::getId, vo -> vo.getName() == null ? "" : vo.getName()));

3、通過Optional.ofNullable給默認值(其實2和3處理方案一樣,只是通過哪種方式給默認值而已)

Map<String, String> map = innerList.stream().collect(Collectors.toMap(InnerVO::getId, vo -> Optional.ofNullable(vo.getName()).orElse("")));

4、通過lamda中構造方法來構建(1,4的處理方案一樣,都是爲value直接給null)

 Map<String, String> map = innerList.stream().collect(HashMap::new, (n, v) -> n.put(v.getId(), v.getName()), HashMap::putAll);

三、value爲null但getOrDefault返回不爲默認值

        HashMap<String, String> map = new HashMap<>();
        map.put("testKey1", null);
        System.out.println(map);

        String defaultValue = map.getOrDefault("testKey1", "defaultValue1");
        System.out.println(defaultValue);

    }
}

在這裏插入圖片描述

2、我們查看getOrDefault的處理

 @Override
    public V getOrDefault(Object key, V defaultValue) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? defaultValue : e.value;
    }

可以看到getOrDefault這個方法並不是,獲取value值來判斷是否爲null。而是看對應key是否創建對應的節點。

2、查看put方法

 public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
        //可以看到當key不存在時候,不管value是否爲null,都會創建對應的節點
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

可以看到當key不存在時候,不管value是否爲null,都會創建對應的節點。getOrDefault判斷的是是否有對應Node。

附上使用到vo類:

@Data
class  InnerVO {
    private String id;

    private String name;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章