Java集合筆記(二):Set(集)
Set的一般用法
Set是最簡單的一種集合,集合的物件不按特定方式排序,並且沒有重複物件.Set介面主要有兩個實現類:HashSet和TreeSet.HashSet按照雜湊演算法來存取集合中的物件,存取速度比較快.HashSe類還有一個子類LinkedHashSet類,它不僅實現了雜湊演算法,而且實現了連結串列資料結構,連結串列資料結構能提高插入和刪除元素的效能.
HashSet類
HashSet是Set介面的典型實現,大多數時候使用Set集合時就是使用這個實現類,HashSet按Hash演算法來儲存集合中的元素,因此具有很好的存取和查詢效能. HashSet具有以下特點:
- 不能保證元素的排列順序,順序可能與新增順序不同,順序也可能發生變化;
- HashSet不是同步的,如果多個執行緒同時訪問同一個HashSet,必須通過程式碼來保證其同步;
- 集合元素可以是null值;
HashSet集合判斷兩個元素相等的標準是兩個物件通過equals()方法比較相等,並且兩個物件的hashCode()方法返回值也相等;
//類A的equals()方法總是返回true,但沒有重寫其hashCode()方法
public class A {
public boolean equals(Object obj){
return true;
}
}
//類B的hashCode()方法總是返回1,但沒有重寫其equals()方法
public class B {
public int hashCode(){
return 1;
}
}
//類C的hashCode()方法總是返回2,且重寫其equals()方法總是返回true
public class C {
public int hashCode(){
return 2;
}
public boolean equals(Object obj){
return true;
}
}
public class HashSetTest {
public static void main (String[] args) {
HashSet books = new HashSet<>();
books.add(new A());
books.add(new A());
books.add(new B());
books.add(new B());
books.add(new C());
books.add(new C());
System.out.println(books);
}
}
輸出結果:
[com.company.set.B@1, com.company.set.B@1, com.company.set.C@2, com.company.set.A@7f31245a, com.company.set.A@14ae5a5]
即使兩個A物件通過equals()方法比較返回true,但hashSet依然把它們當成兩個物件;
即使兩個B物件的hashCode相同值(1),但HashSet依然把它們當成兩個物件;
如果兩個物件的hashCode值相等,但通過equals()方法比較返回false時:因為兩個物件的hashCode值相同,HashSet試圖把它們儲存在同一個位置,但又不行(否則將只剩一個物件).所以實際上會在這個位置用鏈式結構來儲存多個物件;而HashSet訪問集合元素也是根據元素的hashCode值來快速定位的,如果HashSet中兩個以上的元素具有相同的hashCode值,將會導致效能下降;
總結:
當向HashSet集合中存入一個元素時,HashSet會呼叫物件的HashCodeI()方法來得到該物件的hashCode值,然後根據hashCode值決定該物件在HashSet中的儲存位置.如果有兩個元素通過equals方法比較返回true,但它們的hashCode方法返回值不相等,HashSet將會把它們儲存在不同的位置.
注意:
如果需要把某個物件儲存到HashSet集合中,重寫這個類的equals方法和hashCode方法時,應該儘可能保證兩個物件通過equals方法比較返回true時,它們的hashCode方法返回值也相等
TreeSet類
TreeSet類實現了SortedSet介面,能夠對集合中的物件進行排序. 以下程式建立了TreeSet物件,然後向集合中加入了4個Integer物件:
public class TreeSetTest01 {
public static void main(String[] args) {
Set<Integer> set = new TreeSet<>();
set.add(8);
set.add(7);
set.add(6);
set.add(9);
for (Integer i : set) {
System.out.println(i.toString());
}
}
}
以上程式的列印結果為:
6 7 8 9
TressSet支援兩種排序方式:自然排序和客戶化排序.預設情況下TreeSet採用自然排序方式.
自然排序
在JDK類庫中,有一部分實現了Comparable介面,如Integer,Double和String等.Comparable介面有一個CompareTo(Object o)方法,它返回整數型別.對於表示式x.compareTo(y),如果返回值為0,表示x和y相等;如果返回值大於0,表示x大於y;如果返回值小於0.表示x小於y. TreeSet呼叫物件的compareTo()方法比較集合中物件的大小,然後進行升序排列,這種排序方式稱為自然排序;
下面簡單總結一下 JDK類庫中實現了Comparable介面的一些類的排序方式 :
類 | 排序 |
---|---|
BigDecimal,BigInteger,Byte,Double, Float ,Integer,Long,Short | 按數字大小排序 |
Charactor | 按字元的Unicode值的數字大小排序 |
String | 按字串字元的Unicode值的數字大小排序 |
注意: 使用自然排序時,只能向TreeSet集合中加入相同型別的物件,並且這些物件的類必須**實現了Compareable介面;
Comparable 是一個物件本身就已經支援自比較所需要實現的介面(如 String、Integer 自己就可以完成比較大小操作,已經實現了Comparable介面), 此介面強行對實現它的每個類的物件進行整體排序。這種排序被稱為類的自然排序,類的 compareTo 方法被稱為它的自然比較方法。
比如你有一個Customer類 想讓這個類的例項加入集合後自動就具有某種排序功能只要這些例項加入集合後 就會按照你給Customer物件設定的方式排序 ,程式碼: Customer實體類:
public class Customer implements Comparable {
private String name;
private Integer age;
/*
判斷兩個Customer物件相等的條件為name屬性和age屬性都相等
*/
@Override
public int compareTo(Object o) {
Customer other = (Customer) o;
//先按照name屬性排序
if (this.name.compareTo(other.getName()) > 0) {
return 1;
}
if (this.name.compareTo(other.getName()) < 0) {
return -1;
}
//再按照age屬性排序
if (this.age.compareTo(other.getAge()) > 0) {
return 1;
}
if (this.age.compareTo(other.getAge()) < 0) {
return -1;
}
return 0;
}
public Customer(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public Customer setName(String name) {
this.name = name;
return this;
}
public Integer getAge() {
return age;
}
public Customer setAge(Integer age) {
this.age = age;
return this;
}
/*
equals方法應採用和CompareTo方法中採用相同的比較規則
*/
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Customer)) {
return true;
}
final Customer other = (Customer) o;
if (this.name.equals(other.getName()) && this.age == other.getAge()) {
return true;
} else {
return false;
}
}
/*
如果一個類重新實現了equals()方法,那麼也應該重新實現HashCode()方法,並且保證當兩個物件相等時,他們的雜湊碼值相同
*/
public int hashCode() {
int result;
result = (name == null ? 0 : name.hashCode());
result = 29 * result + age;
return result;
}
}
測試類:
public class TreeSetTest02 {
public static void main(String[] args) {
TreeSet<Customer> set = new TreeSet<>();
set.add(new Customer("Tom",20));
set.add(new Customer("Tom",15));
set.add(new Customer("Mike",15));
for (Customer customer : set) {
System.out.println(customer.getName() + " " + customer.getAge());
}
}
}
輸出結果:
Mike 15
Tom 15
Tom 20
值得注意的是,對於TreeSet中已經修改了它們的name屬性和age屬性,TreeSet不會對集合進行重新排序, Java程式碼:
import java.util.TreeSet;
public class TreeSetTest02 {
public static void main(String[] args) {
TreeSet<Customer> set = new TreeSet<>();
Customer customer1 = new Customer("Tom", 15);
Customer customer2 = new Customer("Tom", 16);
set.add(customer1);
set.add(customer2);
//customer1.setAge(20);
for (Customer customer : set) {
System.out.println(customer.getName() + " " + customer.getAge());
}
}
}
上邊我先把
customer1.setAge(20);
這句註釋掉 ,輸出結果:
Tom 15
Tom 16
然後我們把那句取消註釋之後的列印結果是:
Tom 20
Tom 16
Tom 20,如果按照升序應該在下邊 但是卻在上邊 說明TreeSet沒有給它重新排序哦在實際應用中Customer物件的name屬性和age屬性肯定應該是可以被修改的,因此不適合用TreeSet來排序。那大家也應該能想到最適合用TreeSet排序的就是不可變類,比如Integer,Double,String等 所謂不可變類,是指當建立了這個類的例項後,就不允許修改它的屬性值。大家以後用還是小心點好兒!
客戶化排序
除了自然排序,TreeSet還支援客戶化排序, java.util.Comparator介面提供具體的排序方式,指定被比較的物件的型別,Comparator有個compare(Type x,Type y)方法,用於比較兩個物件的大小,當compare(x,y)大於0時表示x大於y,小於0表示x小於y; 來個例子如果希望TreeSet按照Customer物件的name屬性進行降序排列,可以先建立一個實現Comparator介面的類, 程式碼:
import java.util.Comparator;
public class CustomerComparator implements Comparator<Customer> {
@Override
public int compare(Customer customer1, Customer customer2) {
if(customer1.getName().compareTo(customer2.getName())>0){return -1;}
if(customer1.getName().compareTo(customer2.getName())<0){return 11;}
return 0;
}
}
public class TreeSetTest03 {
public static void main(String[] args) {
TreeSet<Customer> set = new TreeSet<>(new CustomerComparator());
Customer customer1 = new Customer("Tom", 15);
Customer customer2 = new Customer("Jack", 16);
Customer customer3 = new Customer("Mike", 26);
set.add(customer1);
set.add(customer2);
set.add(customer3);
for (Customer customer : set) {
System.out.println(customer.getName() + " " + customer.getAge());
}
}
}
輸出結果:是倒序 …
Tom 15
Mike 26
Jack 16
那你現在是不知道了comparable介面和Comparator介面的區別了並且也能更好的使用TreeSet集合了 總結一下吧
- 用自定義類實現Comparable介面,那麼這個類就具有排序功能,Comparable和具體你要進行排序的類的例項邦定。而Comparator比較靈活,只需要通過構造方法指定一個比較器就行了,這種方式比較靈活。我們的要排序的類可以分別和多個實現Comparator介面的類繫結,從而達到可以按自己的意願實現按多種方式排序的目的。Comparable——“靜態繫結排序”,Comparator——“動態繫結排序”。
說說編寫java類時應該養成一些好習慣吧
-
如果java類重新定義了equals方法,那麼這個類也必須重新定義hashCode()方法,並且保證當兩個物件用equals方法比較結果為true時,這兩個物件的hashCode()方法的返回值相等.
-
如果java類實現了Comparable介面,那麼這個類應該從新定義compareTo() equals() 和hashCode()方法,保證compareTo()和equals()方法採用相同的比較規則來比較兩個物件是否相等,並且保證當兩個物件用equals()方法比較的結果為true時,這兩個物件的hashCode()方法的返回值相等.
-
HashSet和HashMap具有較好的效能,是Set和Map首選實現類,只有在需要排序的場合,才考慮使用TreeSet和TreeMap. LinkedList 和 ArrayList各有優缺點,如果經常對元素執行插入和刪除操作,那麼可以用LinkedList,如果經常隨機訪問元素,那麼可以用ArrayList.