1. 程式人生 > >Java集合之四Set、HashSet、LinkedHashSet、TreeSet

Java集合之四Set、HashSet、LinkedHashSet、TreeSet

Set

Set介面是Collection的子介面,set介面沒有提供額外的方法。

Set 集合不允許包含相同的元素,最多允許一個null值,如果試把兩個相同的元素加入同一個 Set 集合中,則新增操作失敗。

Set 判斷兩個物件是否相同不是使用 == 運算子,而是根據 equals 方法。

Set集合是如何判斷兩個元素是否相等的?

是通過物件的hashCode()方法和equals()方法判斷的。所以建議自定義類要重寫hashCode()方法和equals()方法。

首先Set集合是通過雜湊演算法來儲存元素的,當像Set中新增物件時,首先呼叫此物件所在類的hashCode()方法,計算此物件的雜湊值,此雜湊值決定了此物件在Set中儲存的位置。若此位置沒有物件儲存,則直接把物件儲存進來,如果此位置已經有一個物件了,則通過equals()方法比較這兩個物件是否相同,如果不同則儲存進去,如果相同則這個物件不能儲存進Set裡。所以我們重寫hashCode()方法和equals()方法,還要保持這兩個方法一致,即hashCode相同的物件equals也要相同。

Set 集合判斷兩個元素相等的標準:兩個物件通過 hashCode() 方法比較相等,並且兩個物件的 equals() 方法返回值也相等。

重寫 hashCode() 方法的基本原則
在程式執行時,同一個物件多次呼叫 hashCode() 方法應該返回相同的值
當兩個物件的 equals() 方法比較返回 true 時,這兩個物件的 hashCode() 方法的返回值也應相等
物件中用作 equals() 方法比較的 Field,都應該用來計算 hashCode 值

下面是一個沒有重寫hashCode()方法和equals()方法的例子

public class Person {
	
	private String name;
	private int age;
	
	public Person(String name,int age){
		this.name = name;
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	

}

測試類
@Test
	public void testSet(){
		Set set = new HashSet();
		Person p1 = new Person("jack",10);
		Person p2 = new Person("jack",10);
		set.add(p1);
		set.add(p2);
		System.out.println(p1.hashCode());
		System.out.println(p2.hashCode());
		System.out.println(p1.equals(p2));
		System.out.println(set);
	}
輸出結果
99550389
1598924227
false
[[email protected], [email protected]]
所以我們希望第二個物件不能儲存進去的話就必須重寫,現在的hashCode()方法和equals()方法都是用的Object的方法。

現在重寫hashCode()方法和equals()方法

public class Person {
	
	private String name;
	private int age;
	
	public Person(String name,int age){
		this.name = name;
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + age;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Person other = (Person) obj;
		if (age != other.age)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}

}
輸出結果
3255510
3255510
true
[[email protected]]
HashSet

HashSet集合內的元素是無序的,注意並不是隨機性。它是按 Hash 演算法來儲存集合中的元素,因此具有很好的存取和查詢效能。
HashSet 具有以下特點:
①不能保證元素的排列順序
②HashSet 不是執行緒安全的,
如果需要多執行緒訪問它的話,可以用 Collections.synchronizedSet 方法來包裝它:

Set s = Collections.synchronizedSet(new HashSet(...));

③集合元素可以是 null

LinkedHashSet

LinkedHashSet 是 HashSet 的子類,不允許集合元素重複。LinkedHashSet 根據元素的 hashCode 值來決定元素的儲存位置,但它同時使用連結串列維護元素的次序,這使得元素看起來是以插入順序儲存的。LinkedHashSet插入效能略低於 HashSet,但在迭代訪問 Set 裡的全部元素時有很好的效能。
TreeSet

TreeSet 是 SortedSet 介面的實現類,TreeSet 可以確保集合元素處於排序狀態。

特點:①TreeSet裡新增的元素必須是同一型別的。

           ②可以按照新增進集中中的元素的指定順序遍歷,如String類、包裝類預設按照從小到大的順序遍歷。

           ③當像TreeSet內新增自定義類的物件時,可以有兩種排序方法:1.自然排序2.定製排序

自然排序

TreeSet 會呼叫集合元素的 compareTo(Object obj) 方法來比較元素之間的大小關係,然後將集合元素按升序排列。如果試圖把一個物件新增到 TreeSet 時,則該物件的類必須實現 Comparable 介面。實現 Comparable 的類必須實現 compareTo(Object obj) 方法,兩個物件即通過 compareTo(Object obj) 方法的返回值來比較大小。

當需要把一個物件放入 TreeSet 中,重寫該物件對應的 equals() 時,應保證與 compareTo(Object obj) 方法有一致的結果:如果兩個物件通過 equals() 方法比較返回 true,則通過 compareTo(Object obj) 方法比較應返回 0,要求equals()方法和hashCode()方法和compareTo()方法保持一致。

public class Person implements Comparable {
	
	private String name;
	private Integer age;
	
	public Person(String name, Integer age) {
		super();
		this.name = name;
		this.age = age;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((age == null) ? 0 : age.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Person other = (Person) obj;
		if (age == null) {
			if (other.age != null)
				return false;
		} else if (!age.equals(other.age))
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
	@Override
	public int compareTo(Object o) {
		if(o instanceof Person){
			Person p = (Person)o;
			int result = this.age.compareTo(p.getAge());
			if(result == 0){
				result = this.name.compareTo(p.getName());
			}
			return result;
		}
		return 0;
	}
	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
	
	

}

測試類

@Test
	public void testTreeSet(){
		Set set = new TreeSet();
		set.add(new Person("jack",10));
		set.add(new Person("jack",20));
		set.add(new Person("abc",10));
		set.add(new Person("tom",15));
		set.add(new Person("abc",10));
		Iterator it = set.iterator();
		while(it.hasNext()){
			System.out.println(it.next());
		}
	}
輸出結果
Person [name=abc, age=10]
Person [name=jack, age=10]
Person [name=tom, age=15]
Person [name=jack, age=20]
定製排序
TreeSet的自然排序是根據集合元素的大小,進行元素升序排列。如果需要定製排序,比如降序排列,可通過Comparator介面的幫助。需要重寫compare(To1,T o2)方法。利用int compare(To1,T o2)方法,比較o1和o2的大小:如果方法返回正整數,則表示o1大於o2;如果返回0,表示相等;返回負整數,表示o1小於o2。要實現定製排序,需要將實現Comparator介面的例項作為形參傳遞給TreeSet的構造器。此時,仍然只能向TreeSet中新增型別相同的物件。否則發生ClassCastException異常。使用定製排序判斷兩個元素相等的標準是:通過Comparator比較兩個元素返回了0,要求equals()方法和hashCode()方法和compareTo()方法保持一致。
public class Person {
	private Integer id;
	private String name;
	private Integer age;
	
	public Person(Integer id,String name, Integer age) {
		this.id = id;
		this.name = name;
		this.age = age;
	}
	
	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
	
	@Override
	public String toString() {
		return "Person [id=" + id + ", name=" + name + ", age=" + age + "]";
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((age == null) ? 0 : age.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Person other = (Person) obj;
		if (age == null) {
			if (other.age != null)
				return false;
		} else if (!age.equals(other.age))
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}

}
@Test
	public void test1(){
		//建立一個實現Comparator介面的物件
		Comparator com = new Comparator() {
			//重寫compare方法,定義Person類的排序規則,這裡定義按id從小到大排序
			@Override
			public int compare(Object o1, Object o2) {
				if(o1 instanceof Person && o2 instanceof Person){
					Person p1 = (Person)o1;
					Person p2 = (Person)o2;
					return p1.getId().compareTo(p2.getId());
				}
				return 0;
			}
		};
		//把com物件當做形參傳到TreeSet構造器中
		TreeSet set = new TreeSet(com);
		set.add(new Person(1,"jack",12));
		set.add(new Person(21,"jack",12));
		set.add(new Person(11,"jack",12));
		set.add(new Person(9,"jack",12));
		Iterator it = set.iterator();
		while(it.hasNext()){
			System.out.println(it.next());
		}
	}
輸出結果
Person [id=1, name=jack, age=12]
Person [id=9, name=jack, age=12]
Person [id=11, name=jack, age=12]
Person [id=21, name=jack, age=12]

下面看一個加強版

定義一個Employee類,該類包含:private成員變數name,age,birthday,其中 birthday 為 MyDate 類的物件;定義一個MyDate類包含:private成員變數month,day,year;

分別按以下兩種方式對集合中的元素進行排序,並遍歷輸出:

1). 使Employee實現Comparable 介面,並按 name 排序

2). 建立TreeSet 時傳入 Comparator物件,按生日日期的先後排序。

注意:Employee類和MyDate類都要重寫hashCode()方法和equals()方法
public class Employee implements Comparable<Employee>{
	
	private String name;
	private Integer age;
	private MyDate birthDay;
	
	public Employee(String name, Integer age, MyDate birthDay) {
		super();
		this.name = name;
		this.age = age;
		this.birthDay = birthDay;
	}
	@Override
	public String toString() {
		return "Employee [name=" + name + ", age=" + age + ", birthDay=" + birthDay + "]";
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public Integer getAge() {
		return age;
	}
	public void setAge(Integer age) {
		this.age = age;
	}
	public MyDate getBirthDay() {
		return birthDay;
	}
	public void setBirthDay(MyDate birthDay) {
		this.birthDay = birthDay;
	}
	@Override
	public int compareTo(Employee o) {
		return this.getName().compareTo(o.getName());
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((age == null) ? 0 : age.hashCode());
		result = prime * result + ((birthDay == null) ? 0 : birthDay.hashCode());
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Employee other = (Employee) obj;
		if (age == null) {
			if (other.age != null)
				return false;
		} else if (!age.equals(other.age))
			return false;
		if (birthDay == null) {
			if (other.birthDay != null)
				return false;
		} else if (!birthDay.equals(other.birthDay))
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		return true;
	}
	

}
public class MyDate {
	
	private Integer year;
	private Integer month;
	private Integer day;
	
	public MyDate(Integer year, Integer month, Integer day) {
		super();
		this.year = year;
		this.month = month;
		this.day = day;
	}
	@Override
	public String toString() {
		return "MyDate [year=" + year + ", month=" + month + ", day=" + day + "]";
	}
	public Integer getYear() {
		return year;
	}
	public void setYear(Integer year) {
		this.year = year;
	}
	public Integer getMonth() {
		return month;
	}
	public void setMonth(Integer month) {
		this.month = month;
	}
	public Integer getDay() {
		return day;
	}
	public void setDay(Integer day) {
		this.day = day;
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((day == null) ? 0 : day.hashCode());
		result = prime * result + ((month == null) ? 0 : month.hashCode());
		result = prime * result + ((year == null) ? 0 : year.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		MyDate other = (MyDate) obj;
		if (day == null) {
			if (other.day != null)
				return false;
		} else if (!day.equals(other.day))
			return false;
		if (month == null) {
			if (other.month != null)
				return false;
		} else if (!month.equals(other.month))
			return false;
		if (year == null) {
			if (other.year != null)
				return false;
		} else if (!year.equals(other.year))
			return false;
		return true;
	}
}
/**
	 * 使用自然排序 根據name排序
	 */
	@Test
	public void test2(){
		TreeSet<Employee> set = new TreeSet<Employee>();
		set.add(new Employee("abc", 12, new MyDate(2013, 10, 1)));
		set.add(new Employee("adc", 13, new MyDate(2013, 10, 1)));
		set.add(new Employee("jwbc", 23, new MyDate(2013, 10, 1)));
		set.add(new Employee("c2bc", 35, new MyDate(2013, 10, 1)));
		set.add(new Employee("wdabc", 55, new MyDate(2013, 10, 1)));
		set.add(new Employee("abc", 12, new MyDate(2013, 10, 1)));
		Iterator<Employee> it = set.iterator();
		while(it.hasNext()){
			System.out.println(it.next());
		}
	}

	/**
	 * 使用定製排序,根據生日先後排序
	 */
	@Test
	public void test3(){
		Comparator<Employee> com = new Comparator<Employee>() {

			/**
			 * 這樣寫排序規則後,相同的生日的Employee會插不進來,所以你也可以再加上判斷name和age的規則
			 * 這裡就只對生日進行排序了,TreeSet會先後呼叫compare方法、hashCode方法、equals方法來
			 * 判斷插進來的物件是否存在於Set中。
			 * @param o1
			 * @param o2
			 * @return
			 */
			@Override
			public int compare(Employee o1, Employee o2) {
				MyDate birth1 = o1.getBirthDay();
				MyDate birth2 = o2.getBirthDay();
				if(!birth1.getYear().equals(birth2.getYear())){
					return birth1.getYear().compareTo(birth2.getYear());
				}else{
					if(!birth1.getMonth().equals(birth2.getMonth())){
						return birth1.getMonth().compareTo(birth2.getMonth());
					}else{
						return birth1.getDay().compareTo(birth2.getDay());
					}
				}
			}
		};
		
		TreeSet<Employee> set = new TreeSet<Employee>(com);
		set.add(new Employee("abc", 12, new MyDate(1913, 10, 1)));
		set.add(new Employee("adc", 13, new MyDate(2013, 8, 7)));
		set.add(new Employee("jwbc", 23, new MyDate(2000, 4, 23)));
		set.add(new Employee("c2bc", 35, new MyDate(1993, 1, 11)));
		set.add(new Employee("wdabc", 55, new MyDate(2017, 12, 9)));
		set.add(new Employee("abc", 12, new MyDate(2013, 11, 1)));
		set.add(new Employee("abc", 12, new MyDate(2013, 11, 1)));
		Iterator<Employee> it = set.iterator();
		while(it.hasNext()){
			System.out.println(it.next());
		}
	}


test2輸出結果

Employee [name=abc, age=12, birthDay=MyDate [year=2013, month=10, day=1]]
Employee [name=adc, age=13, birthDay=MyDate [year=2013, month=10, day=1]]
Employee [name=c2bc, age=35, birthDay=MyDate [year=2013, month=10, day=1]]
Employee [name=jwbc, age=23, birthDay=MyDate [year=2013, month=10, day=1]]
Employee [name=wdabc, age=55, birthDay=MyDate [year=2013, month=10, day=1]]
test3輸出結果
Employee [name=abc, age=12, birthDay=MyDate [year=1913, month=10, day=1]]
Employee [name=c2bc, age=35, birthDay=MyDate [year=1993, month=1, day=11]]
Employee [name=jwbc, age=23, birthDay=MyDate [year=2000, month=4, day=23]]
Employee [name=adc, age=13, birthDay=MyDate [year=2013, month=8, day=7]]
Employee [name=abc, age=12, birthDay=MyDate [year=2013, month=11, day=1]]
Employee [name=wdabc, age=55, birthDay=MyDate [year=2017, month=12, day=9]]





相關推薦

Java集合SetHashSetLinkedHashSetTreeSet

Set Set介面是Collection的子介面,set介面沒有提供額外的方法。 Set 集合不允許包含相同的元素,最多允許一個null值,如果試把兩個相同的元素加入同一個 Set 集合中,則新增操作失敗。 Set 判斷兩個物件是否相同不是使用 == 運算子,而是根據 eq

Java集合框架:SetHashSet,LinkedHashSet,TreeSet

Set概述  Set幾乎都是內部用一個Map來實現, 因為Map裡的KeySet就是一個Set,而value是假值,全部使用同一個Object。Set的特徵也繼承了那些內部Map實現的特徵。 HashSet 1. 定義 package java.util; p

Java集合HashSetLinkedHashSetTreeSet

討論集合關注的問題: 底層資料結構 增刪改查方式 初始容量,擴容方式,擴容時機 執行緒安全與否 是否允許空,是否允許重複,是否有序 1. 概述 前篇,我寫了關於Map系列的集合(點選跳轉);本篇重新回顧Coll

java集合列表:ArrayListVectorLinkedList

sta pop arraylist 允許 dex nsa pack java jdk 1 package com.jdk7.chapter4; 2 3 import java.util.ArrayList; 4 import java.util.Link

Java集合Map】HashMapHashTableTreeMapLinkedHashMap區別

前言 Java為資料結構中的對映定義了一個介面java.util.Map,它有四個實現類,分別是HashMap、HashTable、LinkedHashMap和TreeMap。本節例項主要介紹這4中例項的用法和區別 幾種Map類結構 public clas

Java-Collection原始碼分析(十二)——SetAbstractSetHashSetLinkedHashSet

該類提供了Set介面的骨架實現,以最大限度地減少實現此介面所需的工作量。 通過擴充套件此類來實現集合的過程與通過擴充套件AbstractCollection實現集合的過程相同,除了此類的子類中的所有方法和建構函式都必須遵守由Set介面施加的附加約束(例如,新增方法不能允許將一個物件的多個例項新增到集合中)。

Java集合系列():HashMapHashtableLinkedHashMapTreeMap的使用方法及區別

本篇部落格主要講解Map介面的4個實現類HashMap、Hashtable、LinkedHashMap、TreeMap的使用方法以及三者之間的區別。 注意:本文中程式碼使用的JDK版本為1.8.0_191 值得注意的是,Map介面是獨立的介面,並沒有繼承Collection介面(這裡是重點,面試常問):

Java集合系列HashSetLinkedHashSet解析

inpu skin lam 繼承 depend try put args port 2017-07-29 16:58:13 一、簡介 1、Set概念 Set可以理解為集合,非常類似數據概念中的集合,集合三大特征:1、確定性;2、互異性;3、無序性,因此Set實現類也有類似的

java集合set

ash 字符數組 his err new rgs return 清除 single 1 public class Demo1_Set { 2 3 /* 4 * set集合無序、不可重復、無索引 5 */ 6 p

Java 集合Set簡介

返回值 接口模擬 區別 key mov 無序 刪除 tor 可能 一.Set集合Set:不包含重復元素的集合。 更正式地,集合不包含一對元素e1和e2 ,使得e1.equals(e2) ,並且最多一個空元素。正如其名稱所暗示的那樣,這個接口模擬了數學集抽象。一些集合實現對它

Java 集合HashSet常用方法實例介紹

java args arr 子類 boolean 常用 地址 比例 可能 一.簡介HashSet是Set常見的子類對象,此類實現Set接口,由哈希表(實際為HashMap實例)支持。 對集合的叠代次序不作任何保證; 特別是,它不能保證訂單在一段時間內保持不變。這個類允許nu

java集合遍歷中的向下轉型泛型

java中集合儲存字串時,集合的get(i)方法是獲取集合中的第i+1個元素,而這個元素是Object型別,而Object型別沒有length()方法,遍歷的時候如果直接.length()會報錯。如果想使用字串的方法,就必須把元素還原成字元(向下轉型)。 集合的遍歷。其實就是依次獲取集合

Java中的種引用型別(強虛)

為什麼需要不同的引用型別 從Java1.2開始,JVM開發團隊發現,單一的強引用型別,無法很好的管理物件在JVM裡面的生命週期,垃圾回收策略過於簡單,無法適用絕大多數場景。為了更好的管理物件的記憶體,更好的進行垃圾回收,JVM團隊擴充套件了引用型別,從最早的強引用型別增加到強、軟、弱、虛四個引用

Java練習Set集合管理課程

說明: 1.提供備選課程。 2.建立學生物件,並給學生新增三門課程(新增在學生的courses—set集合中)。要求能夠顯示備選課程,迴圈三次,每次輸入課程ID,並向學生的courses屬性中新增與輸入ID相匹配的課程,最後輸出學生所選的課程。 public class SetTest

(Java)集合框架(一)Collection介面方法Iterator迭代器增強for迴圈

【Collection介面】  import java.util.ArrayList; import java.util.Collection; /* * Collection介面中的方法 是集合中所有實現類必須擁有的方法 * 程式演示,使用Collection

Java語句順序結構&選擇結構(ifswitch)&迴圈結構(forwhile)

Java中,語句分為三大類,順序結構,選擇結構(if語句、switch語句),迴圈結構(for語句、while語句)。 一、順序結構 就是按照輸出語句來 例: System.out.printl

[瘋狂Java]集合:Collection的迭代器Iterator使用Predicate篩選集合中的元素

1. Iterator——迭代器:     1) 和C++中迭代器的概念一樣,二要素:          i. 迭代器必定從屬於某個容器,其作用就是用來遍歷所屬容器中的元素的!          ii. 迭代器是在容器的資料檢視之上進行迭代,因此不能再迭代過程中修改容器中的

JAVA集合 Deque 與 Queue 實現類 ArrayDeque(佇列雙端佇列) 原始碼淺析

文章目錄 JAVA集合 Deque實現類 ArrayDeque(雙端佇列) 原始碼淺析 一、簡述: 二、ArrayDeque 類結構與屬性 三、ArrayDeque 構造方法 四、Queue 的方法 1.

Java 集合 Set 詳解與原始碼分析

    Set集合與List一樣,都是繼承自Collection介面,常用的實現類有HashSet和TreeSet。值得注意的是,HashSet是通過HashMap來實現的而TreeSet是通過TreeMap來實現的,所以HashSet和TreeSet都沒有自己的資料結構,具

死磕 java集合TreeMap源碼分析()-內含彩蛋

留言 cti 簡單 刪除元素 回顧 over foreach hub rst 歡迎關註我的公眾號“彤哥讀源碼”,查看更多源碼系列文章, 與彤哥一起暢遊源碼的海洋。 二叉樹的遍歷 我們知道二叉查找樹的遍歷有前序遍歷、中序遍歷、後序遍歷。 (1)前序遍歷,先遍歷我,再遍歷我的