一、问题引出
@SuppressWarnings("all")
public class Homework05 {
public static void main(String[] args) {
TreeSet treeSet1 = new TreeSet();
treeSet1.add(new Person1("jack")); //抛出异常
//异常原因:自定义类Person没有实现Comparable接口,同时TreeSet也是采用无参构造器(没有传入比较器)
//(Comparable<? super K>)k1 将传入的key进行向上转型(类型转换)时无法转换,抛出异常
}
}
class Person1{
private String name;
public Person1(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Person1{" +
"name='" + name + '\'' +
'}';
}
}
1. 代码运行结果:
上述代码中,在treeSet1.add(new Person1("jack"));
添加数据到TreeSet对象中时,会抛出ClassCastException
异常,即Person1 cannot be cast to java.lang.Comparable
类型转换失败。
2. 分析结果:
由于初始化TreeSet
对象是使用的是无参构造器,也就没有提供比较器comparator
,所以JDK
底层源码会使用接口Comparable
的compareTo
方法来比较新数据与已经存储在对象中的数据。使用compareTo
需要将数据类型转换为Comparable
类型,然而Person1
类没有实现接口Comparable
,因此在将Person1
对象的数据类型转为Comparable
l类型时抛出异常,无法完成向上转型。具体原理看JDK
源码分析。
3. JDK源码分析:
3.1 TreeMap的put方法源码简化如下:
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) { //插入第一个数据时,root为null
compare(key, key); // type (and possibly null) check,调用TreeMap的compare方法
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
//root!=null,插入的不是第一个数据
int cmp;
Entry<K,V> parent;
// split comparator and comparable paths
Comparator<? super K> cpr = comparator; //比较器赋值给cpr
if (cpr != null) { //初始化TreeSet或TreeMap对象时,传入了自定义的比较器
do {
parent = t;
cmp = cpr.compare(key, t.key); //调用程序员传入的自定义的比较器里重写的compare方法
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else { //初始化TreeSet或TreeMap对象时,没有传入了自定义的比较器,cpr=null
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable<? super K> k = (Comparable<? super K>) key; //向上转型,将自定义数据类型的对象转为Comparable接口类型
do {
parent = t;
cmp = k.compareTo(t.key); //调用实现了Comparable接口的自定义数据类型里面重写的compareTo方法
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
return null;
}
3.2 TreeMap的compare方法如下:
@SuppressWarnings("unchecked")
final int compare(Object k1, Object k2) {
return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
: comparator.compare((K)k1, (K)k2);
compare
分析:
(1) 初始化TreeSet
对象时没有提供比较器comparator
,即comparator=null
,此时会使用接口Comparable
的compareTo
即((Comparable<? super K>)k1).compareTo((K)k2)
将传入的k1和k2做比较。
(2) 初始化TreeSet
对象时提供比较器comparator
,即comparator!=null
,此时会使用比较器comparator
的compare
即comparator.compare((K)k1, (K)k2);
将传入的k1和k2做比较。
二、问题解决
(一)方法一:自定义数据类型实现接口Comparable
,并重写compareTo
方法
代码:
@SuppressWarnings("all")
public class Homework05 {
public static void main(String[] args) {
//解决方法1:自定义数据类型实现Comparable接口,每次插入数据都会使用重写的compareTo方法将要插入的新数据和已经存储在集合中的数据做比较
TreeSet treeSet2 = new TreeSet();
treeSet2.add(new Person2("pony"));
treeSet2.add(new Person2("pony")); //会使用Person2重写的compareTo来比较去重
treeSet2.add(new Person2("tom"));
System.out.println("treeSet2 = " + treeSet2);
}
}
class Person2 implements Comparable{
private String name;
public Person2(String name) {
this.name = name;
}
public String getName() {
return name;
}
//方法1:实现Comparable接口,重写compareTo方法
@Override
public int compareTo(Object o) {
Person2 p2 = (Person2)o;
return this.name.compareTo(p2.name); //实现比较
}
@Override
public String toString() {
return "Person2{" +
"name='" + name + '\'' +
'}';
}
}
(二)方法二:初始化TreeSet
对象时,用基于接口Comparator
的匿名内部类创建比较器对象,重写compare方法,并将比较器对象赋值给TreeMap
里的this.comparator
代码:
@SuppressWarnings("all")
public class Homework05 {
public static void main(String[] args) {
//解决方法2:使用带比较器Comparator对象的构造器,每次插入数据都会调用基于接口Comparator实现的匿名内部类的compare方法,比较新数据与已经存储在集合里的所有数据
TreeSet treeSet3 = new TreeSet(new Comparator() {
@Override
public int compare(Object o1, Object o2) {
return ((Person1) o1).getName().compareTo(((Person1) o2).getName());
}
});
treeSet3.add(new Person1("周树人"));
treeSet3.add(new Person1("鲁迅"));
treeSet3.add(new Person1("鲁迅")); //调用匿名内部类创建的对象的compare方法去重
System.out.println("treeSet3 = " + treeSet3);
}
}
class Person1{
private String name;
public Person1(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Person1{" +
"name='" + name + '\'' +
'}';
}