- Set不重復(fù)集淮腾,無(wú)序。
- Set不能通過(guò)下標(biāo)獲取指定的元素屉佳。因?yàn)樗詻](méi)有下標(biāo)谷朝。可以使用Iterator的方式迭代集合武花。若我們把List看成是有許多格子的盒子圆凰,那么Set就好像一個(gè)袋子。
- 常用實(shí)現(xiàn)類:
HashSet:使用散列算法(哈希)實(shí)現(xiàn)Set集合体箕。
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* Set集合 無(wú)序且不重復(fù)集
* 常用實(shí)現(xiàn)類:
* HashSet:使用散列算法(哈希)實(shí)現(xiàn)Set集合
*/
public class SetTest {
public static void main(String[] args) {
//實(shí)例化一個(gè)HashSet集合
Set<String> set = new HashSet<String>();
/**
* 向集合中添加元素也使用add
* 但是add方法不是向集合末尾追加元素专钉,因?yàn)闊o(wú)序
*/
set.add("One");
set.add("Two");
set.add("Three");
/**
* Set集合沒(méi)有g(shù)et(int index)方法
* 我們不能像使用List那樣,根據(jù)下標(biāo)獲取元素累铅。
* 想獲取元素需要使用Iterator
*/
Iterator<String> it = set.iterator();
while (it.hasNext()){
String element = it.next();
System.out.println(element);
}
/*
* 宏觀上講:元素的順序和存放順序是不同的跃须。
* 但是在內(nèi)容不變的前提下,存放順序是相同的(使用相同的算法)娃兽。
* 但是我們使用的時(shí)候菇民,要當(dāng)做是無(wú)序的使用。
* */
//使用For-Each循環(huán)遍歷Set集合
for (String element:set){
System.out.println(element);
}
}
}
hashCode()方法
- hashCode()方法是Object定義的方法换薄。所以每個(gè)類都會(huì)有該方法玉雾。若我們定義的類重寫了equals()方法,就要重寫hashCode()方法(Object提供的hashCode方法將返回該對(duì)象所在內(nèi)存地址的整數(shù)形式)轻要。
- 若equals方法返回true复旬,那么這兩個(gè)對(duì)象應(yīng)該有相同的hashCode值,反過(guò)來(lái)不是必須的冲泥,當(dāng)時(shí)最好是這樣驹碍”谙眩可以提高諸如HashSet這樣的數(shù)據(jù)結(jié)構(gòu)的效率,一般情況下可以使用IDE提供的工具自動(dòng)生成hashCode方法志秃。
HashSet和hashCode方法的關(guān)系
- HashSet是Set接口的實(shí)現(xiàn)類怔球,通過(guò)hash表的方式實(shí)現(xiàn);在將對(duì)象加入HashSet集合中時(shí)浮还,需要獲取對(duì)象的hashCode值通過(guò)hash算法索引到對(duì)應(yīng)的存儲(chǔ)空間竟坛。
- 進(jìn)行hash算法確定存儲(chǔ)空間,當(dāng)通過(guò)contains方法判斷Set集合中是否包含某個(gè)對(duì)象是钧舌,需要首先根據(jù)對(duì)象的hashCode值索引到特定的空間担汤,然后再和空間中的對(duì)象調(diào)用equals方法進(jìn)行比較,這種查找方式不同于線性表的逐個(gè)比較洼冻,有很高的效率崭歧。
import java.util.HashSet;
import java.util.Set;
/**
* hashCode
*/
public class HashTest {
public static void main(String[] args) {
Set<MyPoint> set = new HashSet<MyPoint>();
set.add(new MyPoint(1,2));
set.add(new MyPoint(3,4));
/**
* 查看新創(chuàng)建的對(duì)象是否在set中被包含
* 雖然這里是新創(chuàng)建的對(duì)象,但是通過(guò)散列算法找到了位置后
* 和里面存放的元素進(jìn)行equals比較為true撞牢,所以依然認(rèn)為
* 是被包含的率碾。
*/
System.out.println(set.contains(new MyPoint(1,2)));//true
}
}
class MyPoint{
private int x;
private int y;
public MyPoint(int x, int y) {
this.x = x;
this.y = y;
}
/**
*equals()和hashCode()方法我們一般讓IDE幫我們自動(dòng)生成
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyPoint point = (MyPoint) o;
if (x != point.x) return false;
return y == point.y;
}
/**
* 若我們不重寫hashCode,那么使用的就是Object提供的屋彪,
* 該方法是返回句柄(對(duì)象存放地址)所宰!換句話說(shuō),不同的對(duì)象hashCode不同
* @return
*/
@Override
public int hashCode() {
int result = x;
result = 31 * result + y;
return result;
}
}
equals()方法和hashCode()方法對(duì)HashSet的影響
import java.util.HashSet;
import java.util.Set;
public class HashTest {
public static void main(String[] args) {
Set<MyPoint> set1 = new HashSet<MyPoint>();
MyPoint p1 = new MyPoint(1,2);
MyPoint p2 = new MyPoint(1,2);
System.out.println("p1和p2是否為同一個(gè)對(duì)象"+(p1==p2));
//重寫equals方法和hashCode方法時(shí)的結(jié)果:false
//重寫equals方法未重寫hashCode方法時(shí)的結(jié)果:false
//未重寫equals方法重寫hashCode方法時(shí)的結(jié)果:false
System.out.println("p1和p2內(nèi)容是否一樣"+p1.equals(p2));
//重寫equals方法和hashCode方法時(shí)的結(jié)果:true
//重寫equals方法未重寫hashCode方法時(shí)的結(jié)果:true
//未重寫equals方法重寫hashCode方法時(shí)的結(jié)果:false
System.out.println("p1和p2的hashCode是否一樣"+(p1.hashCode()==p2.hashCode()));
//重寫equals方法和hashCode方法時(shí)的結(jié)果:true
//重寫equals方法未重寫hashCode方法時(shí)的結(jié)果:false
//未重寫equals方法重寫hashCode方法時(shí)的結(jié)果:true
set1.add(p1);
set1.add(p2);
System.out.println("hashset集合的元素?cái)?shù)"+set1.size());
//重寫equals方法和hashCode方法時(shí)的結(jié)果:1
//重寫equals方法未重寫hashCode方法時(shí)的結(jié)果:2
//未重寫equals方法重寫hashCode方法時(shí)的結(jié)果:2
System.out.println(set1);
//重寫equals方法和hashCode方法時(shí)的結(jié)果:[MyPoint{x=1, y=2}]
//重寫equals方法未重寫hashCode方法時(shí)的結(jié)果:[MyPoint{x=1, y=2}, MyPoint{x=1, y=2}]
//未重寫equals方法重寫hashCode方法時(shí)的結(jié)果:[MyPoint{x=1, y=2}, MyPoint{x=1, y=2}]
/**
* 當(dāng)我們重寫了Point的equals方法和hashCode方法后我們發(fā)現(xiàn)
* 雖然p1和p2是兩個(gè)對(duì)象撼班,但是當(dāng)我們將它們同時(shí)放入集合中時(shí)歧匈,
* p2對(duì)象并沒(méi)有被添加進(jìn)集合。因?yàn)閜1在放入后砰嘁,p2放入時(shí)根據(jù)
* p2的hashCode計(jì)算的位置中p2與該位置的p1的equals比較為true件炉,
* HashSet認(rèn)為該對(duì)象已經(jīng)存在,所有拒絕將p2存入集合矮湘。
*/
/**
* 若我們不重寫MyPoint的hashCode方法斟冕,但是重寫了equals方法。
* 兩個(gè)對(duì)象都可以放入HashSet集合中缅阳,因?yàn)閮蓚€(gè)對(duì)象具有不同的
* hashCode值磕蛇,那么當(dāng)它們?cè)诜湃爰蠒r(shí)通過(guò)hashCode值進(jìn)行
* 散列算法結(jié)果就不相同,那么它們會(huì)被放入集合的不同位置十办。
* 位置不相同秀撇,HashSet則認(rèn)為他們不同,所有它們可以全部
* 被存入集合向族。
*/
/**
* 若我們重寫了hashCode但是不重寫equals方法呵燕,
* hashCode相同的情況下,在存放元素時(shí)件相,它們會(huì)在相同的位置再扭,
* HashSet會(huì)在相同的位置上將后放入的對(duì)象與該位置其它對(duì)象依次
* 進(jìn)行equals比較氧苍,若不相同,則將其存入泛范。
* 在同一個(gè)位置若干元素让虐,這些元素會(huì)被放入一個(gè)鏈表中。
* 由此可以看出罢荡。我們應(yīng)該盡量使得所有類的不同對(duì)象的hashCode
* 值不同赡突。這樣才可以提高HashSet在檢索元素上的效率!
* 否則可能檢索效率還不如List柠傍。
*/
/**
* 結(jié)論:
* 使用HashSet集合存放元素時(shí)
* 應(yīng)保證equals與hashCode方法在api上定義的要求
* 存放規(guī)則:
* 不同對(duì)象存放時(shí)麸俘,不會(huì)存放hashCode相同(存放位置相同)并且equals相同
* (內(nèi)容相同)的
* 對(duì)象。缺一不可惧笛,否則HashSet不認(rèn)為它們是重復(fù)對(duì)象。
*/
}
}
class MyPoint{
private int x;
private int y;
public MyPoint(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public String toString() {
return "MyPoint{" +
"x=" + x +
", y=" + y +
'}';
}
/**
*equals()和hashCode()方法我們一般讓IDE幫我們自動(dòng)生成
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyPoint point = (MyPoint) o;
if (x != point.x) return false;
return y == point.y;
}
/**
* 若我們不重寫hashCode逞泄,那么使用的就是Object提供的患整,
* 該方法是返回句柄(對(duì)象存放地址)!換句話說(shuō)喷众,不同的對(duì)象hashCode不同
* @return
*/
@Override
public int hashCode() {
int result = x;
result = 31 * result + y;
return result;
}
}
最后編輯于 :
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者