(1)Collection接口和Collections類的區別
Collection是個java.util下的接口,它是各種集合結構的父接口。
Collections是個java.util下的類,它包含有各種有關集合操作的靜態方法。
Collection 層次結構中的根接口。Collection 表示一組對象,這些對象也稱爲 collection的元素。一些 collection 允許有重複的元素,而另一些則不允許。一些 collection 是有序的,而另一些則是無序的。JDK 不提供此接口的任何直接 實現:它提供更具體的子接口(如 Set 和 List)實現。此接口通常用來傳遞 collection,並在需要最大普遍性的地方操作這些 collection。
Collection 和 Collections 的區別。
Collection是集合類的上級接口,繼承與他的接口主要有Set 和List.
Collections是針對集合類的一個幫助類,他提供一系列靜態方法實現對各種集合的搜索、排序、線程安全化等操作。
(2)實現比較的Comparable接口和Comparator類
ü Comparable接口(裏面只定義了一個compareTo方法)
public interface Comparable<T> {
public int compareTo(T o);
}
ü Comparator類
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
}
(3)List、Map、Set三個接口,存取元素時,各有什麼特點
ü List與Set具有相似性,都是單列元素的集合,因此他們都有一個共同的父接口Collection;
ü List表示先後順序的集合,因此可以插入相等的元素,而Set裏面不允許有重複的元素,即不能有兩個相等的對象,這個”相等”取決於對象的equals方法;
ü 下面我們來深入討論底層中HashSet的add方法的實現:
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
private transient HashMap<E,Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
public HashSet() {
map = new HashMap<E,Object>();
}
public boolean contains(Object o) {
return map.containsKey(o);
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
}
驚訝地發現,HashSet元素的唯一性是藉助HashMap中Key的唯一性來實現的。因此現在再深入看看HashMap中是如何保證Key的唯一性的:
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next)
{
Object k;
if (e.hash == hash && ((k = e.key) == key
|| key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
}
從這段代碼中可以看出,HashMap中的Key是根據對象的hashCode() 和 euqals()來判斷是否唯一的。
結論:爲了保證HashSet中的對象不會出現重複值,在被存放元素的類中必須要重寫hashCode()和equals()這兩個方法。
心得:以前寫”貪食蛇”程序的時候就曾經遇到過用重寫HashMap的equals方法來判斷添加的元素是否一樣,後來也知道了還需要重寫hashCode()方法,但是卻沒想到去看源代碼,可見做研究的必須得做深入了,這樣才能真正鞏固和學習到新知識。
擴展:綜上所述,不難推斷出:HashMap中的Key完全可以使用自定義的類,而關鍵之處就在於要同時重寫equals方法和hashCode方法,比如說有個User類,其中有個id屬性,要求其id屬性相等就認爲Key相等,故不能重新添加,要實現這樣的功能,就必須去重寫User類的equals和hashCode方法了。下面是網上找的一個實例:
Ø Dummy.java
public class Dummy {
private int id;
public Dummy(final int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setId(final int id) {
this.id = id;
}
@Override
public int hashCode() {
return id;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o instanceof Dummy) {
Dummy anotherDummy = (Dummy) o;
return this.getId() == anotherDummy.getId();
}
return false;
}
}
Ø HashCode.java
public class HashCode {
public static void main(String[] args) {
Map<Dummy, String> map = new HashMap<Dummy, String>();
map.put(new Dummy(1), "old value whose key's value is 1");
Dummy dummy = new Dummy(1);
map.put(dummy, "new value whose key's value is 1");
map.put(new Dummy(2), "old value whose key's value is 2");
for (Map.Entry entry : map.entrySet()) {
System.out.println("key: " + entry.getKey() + " value: " + entry.getValue());
}
}
}
輸出:
key: com.pdm.Dummy@1 value: new value whose key's value is 1
key: com.pdm.Dummy@2 value: old value whose key's value is 2
下面再深入JDK的Object類中的equals和hashCode方法:
equals 和 hashcode 方法來自於Object類,實現分別如下:
public boolean equals(Object obj) {
return (this == obj);
}
public native int hashCode();
通過以上兩個方法的實現,對object的這兩個方法(如果我們的自定義類不overwiter這兩個方法,就是如上默認實現),我們可以得出如下結論:
(1) equals方法默認是對象存放地址的比較
(2) hashcode是於存放內存地址相關的一個整形值,因爲和內存地址相關,所以用到了native關鍵字,該關鍵字表明該方法需要調用非java的接口來實現,如c,c#
繼續深入:
equals():
用於兩個對象的比較,在Object類中已經實現了這個方法,是對對象內部地址的比較,即如果兩個對象的內部地址是一樣的則是相等的。如果要按照對象內容的進行比較,就需要重載這兩個方法。Java語言對equals()的要求如下,這些要求是必須遵循的:
對稱性:如果x.equals(y)返回是“true”,那麼y.equals(x)也應該返回是“true”。
反射性:x.equals(x)必須返回是“true”。
類推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那麼z.equals(x)也應該返回是“true”。
一致性:如果x.equals(y)返回是“true”,只要x和y內容一直不變,不管你重複x.equals(y)多少次,返回都是“true”。
任何情況下,x.equals(null)永遠返回是“false”;x.equals(和x不同類型的對象)永遠返回是“false”。
hashCode():
這個方法返回的是一個用來進行hash操作的整型數,可用於在Collection對象中定位特定對象的位置。Object中默認的實現是把對象內部地址轉化爲整數作爲hashCode。
hashCode()的返回值和equals()的關係如下:
如果x.equals(y)返回“true”,那麼x和y的hashCode()必須相等。
如果x.equals(y)返回“false”,那麼x和y的hashCode()有可能相等,也有可能不等。
這裏有必要說明一下HashMap的原理。爲了優化查找對象的性能,在HashMap中按照鍵值對象的hash值放了若干個箱子,當有一個鍵值對象加入進來時,調用鍵值對象的hashCode()方法,根據計算出的hash值把對象放入對應的箱子。當對鍵值對象進行查找時,首先計算對象的hash值,找到對應的箱子,然後調用equals()與箱子中的對象逐個比較,直到找出相等的對象或者遍歷了一遍。
如果x.equals(y)返回“true”,而x.hashCode() != y.hashCode()會有什麼後果呢?假設把x、y方法HashMap,即hashMap.put(x, xValue)和hashMap.put(y, yValue),在hashMap中會存在兩個元素x、y,而不是一個。原因很好理解,因爲二者的hashCode不同,在hashMap中會放入不用的箱子,從而會被認爲是兩個對象。
使用hashCode的目的是爲了把對象散列在不同的地方,從而提高檢索對象的性能,所以編寫一個好的hashCode算法相當重要。在Effective Java 2 edition 中給出了一個簡單的hashCode算法:
l 保存一些常量非0值,例如17,在一個int的result變量中。這個值可以是任意的,但是最好不是0,會增加衝突的可能。
l 對象每一個重要的域f(就是equals方法使用到那些field)做以下操作:
a)對域f計算一個int的哈希碼c
i.如果field是boolean型的,計算(f ? 1 : 0)
ii.如果field是byte,char,int之類的,計算 (int)f
iii.如果field是long, 計算 (int) (f^(f>>32))
iv.如果field是float,計算 Float.floatToIntBits(f)
v.如果field是double,計算Double.doubleToLongBits(f),然後使用2.a.iii的方法計算
hash code
vi.如果field是一個對象的引用,並且這個類的此域比較的equals方法使用了遞歸的調用equals,直接遞歸的調用hashCode方法。如果一個更復雜的比較是必須的,對此域計算”canonical representation(規範的表示)”並且在”規範的表示”上調用hashCode(invoke hashCode on the canonical representation)。如果此field爲null,返回0。
vii.如果field時array,將數組中的每個元素作爲一個單獨的field,用上述規則來計算,並使用2.b中的方式合併這些值。如果每個元素都是significant,你可以使用1.5版本添加的Arrays.hasCode方法。
b)合併2.a中哈希碼c的值到result使用下面的方法:
result=31*result+c;
l 返回result
l 當你完成hashCode函數之後,自我檢驗一下相等的實例是否有相等的hash code。編寫單元測試去檢驗你所想的結果。如果相等實例有不同的hash code,找出原因並解決。
說了那麼多,應該可以明白了。不過上面還有一個細節,那就是在上面那個實例輸出結果中有個@後面跟了個整數,其實就是hashCode的值,上面由於重寫了Dummy的hashCode方法,因此返回的是id的值。
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
ü List集合可以使用get(int index)方法取得具體指定的元素,但是Set取元素時,沒法取得第幾個,只能以Iterator接口取得所有的元素,再逐一遍歷各個元素;
ü Map與List和Set不同,他是雙列的集合,其中有put方法,定義如下:put(obj key, obj value),每次存儲時,要存儲一對key/value,不能存儲重複的key,這個重複的定義是按照equals和hashCode方法來決定的;
(4)ArrayList和Vector的異同
這兩個類都實現了List接口(List接口繼承了Collection接口),他們都是有序的集
合,即存儲在這兩個集合中的元素的位置都是有順序的,相當於一種動態的數組,我們可以按位置索引出某個元素,並且其中的數據允許重複,區別主要包括:
ü 同步性:Vector是線程安全的,也就是他的方法之間是線程同步的:
public synchronized void ensureCapacity(int minCapacity) {
modCount++;
ensureCapacityHelper(minCapacity);
}
private void ensureCapacityHelper(int minCapacity) {
int oldCapacity = elementData.length;
if (minCapacity > oldCapacity) {
Object[] oldData = elementData;
int newCapacity = (capacityIncrement > 0) ?
(oldCapacity + capacityIncrement) : (oldCapacity * 2);
if (newCapacity < minCapacity) {
newCapacity = minCapacity;
}
elementData = Arrays.copyOf(elementData, newCapacity);
}
}
public synchronized void setSize(int newSize) {
modCount++;
if (newSize > elementCount) {
ensureCapacityHelper(newSize);
} else {
for (int i = newSize ; i < elementCount ; i++) {
elementData[i] = null;
}
}
elementCount = newSize;
}
而ArrayList是線程不安全的,他的方法之間線程是不同步的。如果只有一個線程會訪問到集合,那最好是使用ArrayList,因爲他不考慮線程安全,效率會高一些;如果有多個線程會訪問到集合,那最好是使用Vector,因爲不需要我們自己再去考慮和編寫線程安全的代碼;
備註:對於Vector和ArrayList、Hashtable和HashMap,要記住線程安全的問題,記住Vector和Hashtable是舊的,是java一開始就提供了的,他們是線程安全的,ArrayList和HashMap是java2時才提供的,他們是線程不安全的。
ü 數據增長:ArrayList和Vector都有一個初始容量大小(默認爲10),當存儲他們裏面的
元素個數超過了容量時,就需要增加ArrayList和Vector的存儲空間,每次要增加存儲空間時,不是隻增加一個存儲單元,而是增加多個存儲單元:
l 對於Vector: int newCapacity = (capacityIncrement > 0) ?
(oldCapacity + capacityIncrement) : (oldCapacity * 2);
l 對於ArrayList: int newCapacity = (oldCapacity * 3)/2 + 1;