我的GitHub | 我的博客 | 我的微信 | 我的郵箱 |
---|---|---|---|
baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
集合判斷是否是同一元素的策略
List:equals
對於List集合(ArrayList、LinkedList等):僅僅是通過判斷兩個對象的【equals】方法是否為true。
以下為 ArrayList 的部分源碼:
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i] == null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i])) //核心代碼:contains 時完全是根據 equals 方法來判斷是否是同一元素
return i;
}
return -1;
}
Tree:compare 或 compareTo
對於Tree集合(TreeSet、TreeMap):
- 如果構造時指定了
Comparator
,則根據 Comparator 接口中的compare
方法返回值是否為 0 來判斷。 - 如果構造時沒有指定 Comparator,則其中的元素必須實現
Comparable
接口,此時根據compareTo
方法返回值是否為 0 來判斷。
注意上面的 compare
方法的目的並不是判斷對象相同的,而是給對象排序的。
TreeSet、TreeMap部分源碼:
public TreeSet() {
this(new TreeMap<E,Object>());
}
public TreeSet(Comparator<? super E> comparator) {
this(new TreeMap<>(comparator));
}
public boolean contains(Object o) {
return m.containsKey(o);
}
public boolean containsKey(Object key) {
return getEntry(key) != null;
}
final Entry<K,V> getEntry(Object key) {
if (comparator != null) return getEntryUsingComparator(key); //核心代碼:如果指定了比較器......
if (key == null) throw new NullPointerException();
@SuppressWarnings("unchecked") //核心代碼:如果沒有指定比較器,則將元素強轉為 Comparable
Comparable<? super K> k = (Comparable<? super K>) key;
Entry<K,V> p = root;
while (p != null) {
int cmp = k.compareTo(p.key);
if (cmp < 0) p = p.left;
else if (cmp > 0) p = p.right;
else return p; //核心代碼:如果 compareTo 的值為0 ,則返回此值,否則繼續遍歷
}
return null;
}
final Entry<K,V> getEntryUsingComparator(Object key) {
@SuppressWarnings("unchecked")
K k = (K) key;
Comparator<? super K> cpr = comparator;
if (cpr != null) {
Entry<K,V> p = root;
while (p != null) {
int cmp = cpr.compare(k, p.key);
if (cmp < 0) p = p.left;
else if (cmp > 0) p = p.right;
else return p; //核心代碼:如果 compare 的值為0 ,則返回此值,否則繼續遍歷
}
}
return null;
}
Hash:hashCode + equals
對於Hash系列的集合(HashSet、HashMap):
- 先判斷元素的
hashCode
的值是否相同,如果不同則認為是不同的元素。 - 如果兩個元素的 hashCode 的值相同,則再判斷元素的
equals
返回值是否為 true,如果為 true 則是不同的元素,否則為相同的元素。
HashSet、HashMap部分源碼:
public HashSet() {
map = new HashMap<>();
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
public boolean contains(Object o) {
return map.containsKey(o);
}
public boolean containsKey(Object key) {
return getNode(hash(key), key) != null;
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab;
Node<K,V> first, e;
int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k)))) // always check first node
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode) return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e;
} while ((e = e.next) != null);
}
}
return null;
}
特殊對象 String:字符序列
String 類重寫了 hashCode
和 equals
方法,如果兩個 String 的內容相同則 hashCode 和 equals 方法也相同或為 true。
private final char value[]; // The value is used for character storage.
private int hash; // Default to 0
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) { //如果長度為 0 ,則其 hash 也為 0
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i]; //哈希值完全就是對字符串中所有字符通過某種算法計算后的結果,而且算法是穩定的
}
hash = h;
}
return h;
}
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) { //必須是 String
String anotherString = (String) anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i]) return false; //逐個比較字符,一旦發現對應位置有不相同的,則為不同的字符串
i++;
}
return true;
}
}
return false;
}
測試代碼
List
測試代碼
class Person {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
Person p = (Person) o;
System.out.println("調用了equals方法,當前對象【" + toString() + "】被比較對象【" + p.toString() + "】");
return this.age == p.age || this.name.equals(p.name);
}
@Override
public String toString() {
return "{" + name + "," + age + "}";
}
}
List<Person> list = Arrays.asList(new Person("a", 20), new Person("a", 21), new Person("b", 20), new Person("b", 21));
System.out.println(list);
System.out.println(list.contains(new Person("c", 20)) + " " + list.contains(new Person("b", 10086)));
日志
[{a,20}, {a,21}, {b,20}, {b,21}]
調用了equals方法,當前對象【{c,20}】被比較對象【{a,20}】
調用了equals方法,當前對象【{b,10086}】被比較對象【{a,20}】
調用了equals方法,當前對象【{b,10086}】被比較對象【{a,21}】
調用了equals方法,當前對象【{b,10086}】被比較對象【{b,20}】
true true
Tree
Person
class Person implements Comparable<Person> {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object p) {
System.out.println("調用了equals方法,當前對象【" + toString() + "】被比較對象【" + p.toString() + "】");
return this.name.equals(((Person) p).name);
}
@Override
public String toString() {
return "{" + name + "," + age + "}";
}
@Override
public int compareTo(Person o) {
System.out.println("調用了compareTo方法,當前對象【" + toString() + "】被比較對象【" + o.toString() + "】");
return this.age - o.age;
}
}
不指定 Comparator
TreeSet<Person> set = new TreeSet<>();
System.out.println("添加第1個元素");
set.add(new Person("a", 20));
System.out.println("添加第2個元素"); // compareTo 方法返回值不為 0,判斷為不同元素,所以添加進去
set.add(new Person("a", 21));
System.out.println("添加第3個元素"); // compareTo 方法返回值為 0,判斷為相同元素,所以不添加進去
set.add(new Person("b", 20));
System.out.println("添加第4個元素"); // 相同元素,所以不添加進去
set.add(new Person("b", 21));
System.out.println(set);
日志:
添加第1個元素
調用了compareTo方法,當前對象【{a,20}】被比較對象【{a,20}】
添加第2個元素
調用了compareTo方法,當前對象【{a,21}】被比較對象【{a,20}】
添加第3個元素
調用了compareTo方法,當前對象【{b,20}】被比較對象【{a,20}】//先和第一個元素比較,因為相同,所以直接結束
添加第4個元素
調用了compareTo方法,當前對象【{b,21}】被比較對象【{a,20}】//先和第一個元素比較,不相同,繼續和下一個元素比較
調用了compareTo方法,當前對象【{b,21}】被比較對象【{a,21}】//再和第二個元素比較,因為相同,所以結束
[{a,20}, {a,21}]
指定 Comparator
TreeSet<Person> set = new TreeSet<>((p1, p2) -> {
System.out.println("調用了compare方法,對象1【" + p1.toString() + "】對象2【" + p2.toString() + "】");
return p1.name.compareTo(p2.name);
});
日志:
添加第1個元素
調用了compare方法,對象1【{a,20}】對象2【{a,20}】
添加第2個元素
調用了compare方法,對象1【{a,21}】對象2【{a,20}】
添加第3個元素
調用了compare方法,對象1【{b,20}】對象2【{a,20}】
添加第4個元素
調用了compare方法,對象1【{b,21}】對象2【{a,20}】
調用了compare方法,對象1【{b,21}】對象2【{b,20}】
[{a,20}, {b,20}]
Hash 集合
Person
class Person {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public int hashCode() {
System.out.println("調用了hashCode方法,當前對象【" + toString() + "】");
return age;
}
@Override
public boolean equals(Object p) {
System.out.println("調用了equals方法,當前對象【" + toString() + "】被比較對象【" + p.toString() + "】");
return this.name.equals(((Person) p).name);
}
@Override
public String toString() {
return "{" + name + "," + age + "}";
}
}
HashSet
HashSet<Person> set = new HashSet<>();
System.out.println("添加第1個元素");
set.add(new Person("a", 20));
System.out.println("添加第2個元素");
set.add(new Person("a", 21));// hashCode 不相同,直接判斷為不同元素,所以直接添加進去
System.out.println("添加第3個元素");
set.add(new Person("b", 21)); // hashCode 相同,進一步判斷 equals 方法是否相同;因為不相同,所以添加進去
System.out.println("添加第4個元素");
set.add(new Person("c", 21)); // hashCode 相同,進一步判斷 equals 方法是否相同;因為不相同,所以添加進去
System.out.println("添加第5個元素");
set.add(new Person("b", 21)); // hashCode 相同,進一步判斷 equals 方法是否相同;因為相同,所以不添加進去
System.out.println(set); //[{a,20}, {a,21}, {b,21}, {c,21}]
HashMap
HashMap<Person, Integer> map = new HashMap<>();
System.out.println("添加第1個元素");
map.put(new Person("a", 20), 1);
System.out.println("添加第2個元素");
map.put(new Person("a", 21), 1);// hashCode 不相同,直接判斷為不同元素,所以直接添加進去
System.out.println("添加第3個元素");
map.put(new Person("b", 21), 1); // hashCode 相同,進一步判斷 equals 方法是否相同;因為不相同,所以添加進去
System.out.println("添加第4個元素");
map.put(new Person("c", 21), 1); // hashCode 相同,進一步判斷 equals 方法是否相同;因為不相同,所以添加進去
System.out.println("添加第5個元素");
map.put(new Person("b", 21), 1); // hashCode 相同,進一步判斷 equals 方法是否相同;因為相同,所以不添加進去
System.out.println(map.keySet()); //[{a,20}, {a,21}, {b,21}, {c,21}]
日志
以上兩種方式具有完全相同的日志:
添加第1個元素
調用了hashCode方法,當前對象【{a,20}】
添加第2個元素
調用了hashCode方法,當前對象【{a,21}】
添加第3個元素
調用了hashCode方法,當前對象【{b,21}】
調用了equals方法,當前對象【{b,21}】被比較對象【{a,21}】
添加第4個元素
調用了hashCode方法,當前對象【{c,21}】
調用了equals方法,當前對象【{c,21}】被比較對象【{a,21}】//首先是和第一個具有相同 hashCode 的元素比較
調用了equals方法,當前對象【{c,21}】被比較對象【{b,21}】//不一致時再和下一個具有相同 hashCode 的元素比較
添加第5個元素
調用了hashCode方法,當前對象【{b,21}】
調用了equals方法,當前對象【{b,21}】被比較對象【{a,21}】//首先是和第一個具有相同 hashCode 的元素比較
調用了equals方法,當前對象【{b,21}】被比較對象【{b,21}】//一旦發現具有相同 hashCode 的元素也相互 equals,就會立即結束
[{a,20}, {a,21}, {b,21}, {c,21}]
2016-04-26