案例剖析
关于Map集合的特点,我们都知道底层数据结构是控制Map集合的键,而和值即value是无关的。通过查阅JDK的API我们发现TreeMap集合的底层是基于红黑树的即二叉树。该映射根据其键的自然顺序进行排序,或者根据创建映射时提供的Comparator进行排序,具体取决于使用的构造方法。如果使用的构造方法是TreeMap()那么底层的键就是自然排序,如果使用的构造方法是TreeMap(Comparator super K> comparator) ,那么底层的键就是Comparator进行排序的。
接下来我们先看如下案例:
向TreeMap集合中存储数据,要求键为自定义Student类的对象,家庭住址作为TreeMap的值,类型是String类型。
学生Student类代码如下:
packagecn.itcast.sh.set;/** 描述学生*/publicclassStudent {//属性Stringname;intage;publicStudent(Stringname, intage) {this.name=name;this.age=age;}@OverridepublicStringtoString() {return"Student [name="+name+", age="+age+"]";}}
测试类TreeMapDemo01代码如下:
publicclassTreeMapDemo01 {publicstaticvoidmain(String[] args) {// 创建集合对象TreeMap
上述代码运行结果会报如下错误:
分析异常的原因:
通过以上报异常错误原因大概知道我们要将自己定义的类Student的存储到TreeMap集合中的时候,发生自定义类Student不能被转换到Comparable比较器接口的异常,我们在代码中明明没有书写和Comparable接口相关的代码啊,那怎么会报这个异常呢?
这里我们需要查看一下TreeMap底层的源代码进行进一步分析。
关于TreeMap集合的深入分析
问题1:为什么集合中存储的元素可以不重复呢?
TreeMap集合底层使用了二叉树结构,在存储元素时,拿存储的元素会和树结构中已经存在元素进行比较大小(结果有三种:大于、小于、相等)。当比较的结果相等时,表示该元素已经存在了,则不存储。
问题2:为什么TreeMap集合中存储的元素会排序呢?
在存储元素时,先拿要存储的元素和树结构中已经存在的元素进行比较大小,如果要存储的元素大于树结构中已存在的元素,则把要存储的元素存放比较元素的右边;如果要要存储的元素小于树结构中已存在的元素,则把要存储的元素放在左边。
分析TreeMap集合的源码查找为什么会报上述异常:
当上述代码中我们使用集合对象tm调用put方法向集合添加数据时,报了异常。所以我们可以查看put方法的源代码:
TreeMap集合中的put方法源代码如下所示:
publicVput(Kkey, Vvalue) {Entry
对于上述源代码的解释:
Entry
if (t==null) {//判断根元素t是否为空,在插入第一个数据之前根元素肯定是nullcompare(key, key); // type (and possibly null) checkroot=newEntry<>(key, value, null);size=1;modCount++;returnnull; }compare(key, key);比较key,其实这里是判断key位置对象所属类即Student是否实现Comparable接口。代码如下:```javafinal int compare(Object k1, Object k2) { return comparator==null ? ((Comparable super K>)k1).compareTo((K)k2) : comparator.compare((K)k1, (K)k2); }
由于我们在创建TreeMap对象时并没有传递自定义比较器Comparator的对象,所以comparator等于null。这里会执行 ((Comparable super K>)k1).compareTo((K)k2)。这句话的意思是对两个键进行比较,按照自然排序Comparable接口中的compareTo进行排序,但是必须将传递进来的Student对象进行强转。但是Student根本就没有实现Comparable接口,所以这里会报类转换异常。为了解决上述错误,只需Student实现Comparable接口即可。
root = new Entry<>(key, value, null); 表示创建根元素对象,即将第一个元素添加到二叉树的根元素位置。
由于在创建TreeMap集合对象时并没有传递自定义比较器Comparator的对象,所以自定义比较器对象是null,执行如下代码:
else {if (key==null)//在TreeMap集合中key不能是null,只要是null就会报空指针异常thrownewNullPointerException();Comparablek= (Comparable) key;//将添加到集合中的key强转为Comparable类型do {parent=t;cmp=k.compareTo(t.key);if (cmp<0)t=t.left;elseif (cmp>0)t=t.right;elsereturnt.setValue(value); } while (t!=null); }
cmp = k.compareTo(t.key);
t.key表示根元素的键,k表示后添加的键,按照自然排序进行比较。将返回值放到cmp变量中。
如果cmp变量小于0,将执行t = t.left;如果cmp变量大于0,将执行 t = t.right;如果cmp等于0,后添加的值将之前的值覆盖。
这里由于 t.left或者t.right初始化值都是null,所以t等于null。
执行如下代码:
Entry