数据结构集合框架这种内功总是会被忽略,这次总算是掉到“坑”里一次,也算是一个警醒。
一个 POJO:
@Getter
@Setter
@ToString
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private Integer seq;
}
先以老生常谈的 HashMap
(1.8)为例,如果我以 User 对象作为 key 进行存储:
public static void main(String[] args) {
Map<User, String> map = new HashMap<>();
map.put(new User(1, "张一", 1), "a");
map.put(new User(3, "张三", 3), "c");
map.put(new User(2, "张二", 2), "b");
map.forEach((k, v) -> System.out.println(k + "->" + v));
}
此时 IDEA 也会有提示,说需要重写 User 类的 hashCode()
和 equals()
方法:
[外链图片转存失败(img-r5J27cTl-1566780350982)(./1.png)]
其实这个提示说的已经很明显了,主要是因为 HashMap
存储的时候会根据 key 的 hashCode()
去判断应该落在哪个桶里,如果这个桶里已经有数据了,会根据 hash 值和 equals()
一起再作进一步判断:
...
...
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
...
...
HashSet
和 HashMap
是一样的,因为 HashSet
其实就是 HashMap
对 key 的实现,value 就是一个 final
参数,PRESENT
,可以看到 HashSet
里面没有几行代码的,都是用的 HashMap
的。
这也是为什么会有这样一段注释的原因:
* Note that it is generally necessary to override the {@code hashCode}
* method whenever this method is overridden, so as to maintain the
* general contract for the {@code hashCode} method, which states
* that equal objects must have equal hash codes.
那么 TreeMap
是怎么样的呢,TreeMap 是基于红黑树实现的:
* A Red-Black tree based {@link NavigableMap} implementation.
* The map is sorted according to the {@linkplain Comparable natural
* ordering} of its keys, or by a {@link Comparator} provided at map
* creation time, depending on which constructor is used.
基于 Comparator
进行比较插入新的元素。
先看下面这个例子:
POJO:
@Getter
@Setter
@ToString
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private Integer seq;
}
public static void main(String[] args) {
Map<User, String> map = new TreeMap<>();
map.put(new User(1, "张一", 1), "a");
map.put(new User(3, "张三", 1), "c");
map.put(new User(2, "张二", 2), "b");
map.forEach((k, v) -> System.out.println(k + "->" + v));
}
此时程序运行出现了异常:
Exception in thread "main" java.lang.ClassCastException: dongguabai.demo.testing.testTreeMap.User cannot be cast to java.lang.Comparable
at java.util.TreeMap.compare(TreeMap.java:1294)
at java.util.TreeMap.put(TreeMap.java:538)
at dongguabai.demo.testing.testTreeMap.TestMain.main(TestMain.java:17)
进入 TreeMap 源码可以看到在 put() 方法中会调用 compare() 方法:
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
...
...
}
而在 compare()
方法中要么使用构造传入的 Comparator
要么就是 key 实现了 Comparable
接口:
final int compare(Object k1, Object k2) {
return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
: comparator.compare((K)k1, (K)k2);
}
我这里又没有传入比较器又没有实现 Comparable
接口,所以会出现类型转换异常。
之前也介绍过了,TreeMap
是基于红黑树实现的,那么在插入元素的时候,首先要将插入的元素融入树中。在 TreeMap
中就是通过 Comparator
或者 Comparable
实现的。再看一个例子:
public static void main(String[] args) {
Map<User, String> map = new TreeMap<>(Comparator.comparing(User::getSeq));
map.put(new User(1, "张一", 1), "a");
map.put(new User(3, "张三", 3), "c");
map.put(new User(2, "张二", 2), "b");
map.forEach((k, v) -> System.out.println(k + "->" + v));
}
我这里传入了一个基于 seq 的升序比较器,所以程序最终输出结果为:
User(id=1, name=张一, seq=1)->a
User(id=2, name=张二, seq=2)->b
User(id=3, name=张三, seq=3)->c
那么再变化一下:
public static void main(String[] args) {
Map<User, String> map = new TreeMap<>(Comparator.comparing(User::getSeq));
map.put(new User(1, "张一", 1), "a");
map.put(new User(3, "张三", 3), "c");
//map.put(new User(2, "张二", 2), "b");
map.put(new User(2, "张二", 1), "b");
map.forEach((k, v) -> System.out.println(k + "->" + v));
}
先传入了一个 seq 为 1 的 User 对象,随后又传入了一个 User 为 1 的对象,再看输出结果:
User(id=1, name=张一, seq=1)->b
User(id=3, name=张三, seq=3)->c
可以发现后传入的 seq 为 1 的User对象的 value 覆盖了前面 seq 为 1 的 User 对象的 value,但是 key 还是没有变,那么这是什么原因呢,可以再看看 put()
方法:
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);
红黑树是以 key 来进行排序的,所以这里以 key 来进行比较检索出合适的叶子结点,而比较多依据就是 Comparator
或者 Comparable
,如果比较出来的结果是 0,那么后面的 value 会覆盖前面的 value。所以在使用 Map
的时候,一定要注意底层的数据结构和对 key 的处理方式。
欢迎关注公众号: