对于存储进TreeMap及TreeSet中的元素,要求元素自身具有比较性或者在创建集合时传入一个比较器对象。当调用TreeMap.put(key,value)时,如果root为空,也就是集合中没有元素时,不管key值是否为null,TreeMap都创建一个节点并将该节点作为树根节点存入集合中。当第二次存入key为null的键值对时,此时如果集合自身不带比较器,那么调用元素自身的compareTo()方法,即key.compareTo()方法,此时必然产生NullPointerException异常。例如,当创建一个存储键和值都是String类型,但自身不带比较器的TreeMap对象时,即TreeMap<String,String> tm= new TreeMap<String,String>();两次调用tm.put(null,”aaa”)时,第一次能顺利存入,因为TreeMap为空,直接创建一个根节点并将元素存入,但是第二次传入时,由于集合自身不带比较器,因此调用String.compareTo(String str)方法,必然产生空指针异常。
但是当集合自身带有比较器对象时,调用的是比较器Comparator.compare(K key,K k),此时是否能正常存入完全取决于比较器。因此,TreeMap中能否存入多个键值为null的键值对取决于集合自带比较器。下面是TreeMap.put(K key,V value)源码分析。
public V put(K key, V value) {
Entry<K,V> t = root;
//如果根节点为空,也就是集合中没有元素,直接创建一个Entry节点并存入集合中,并将该节点作为root
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
//如果集合自身带有比较器对象,优先使用集合自带的比较器,调用比较器的compare方法,在树中找一个合适位置并记录它的父亲节点
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
//如果使用元素自身的compareTo方法,则key必须非空,只要为空,就产生空指针异常
if (key == null)
throw newNullPointerException();
//在树中找到一个合适位置,并记录它的父亲节点
Comparable<? super K> k = (Comparable<? super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry<K,V> e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}