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