- Set不重复集,无序。
- Set不能通过下标获取指定的元素。因为所以没有下标。可以使用Iterator的方式迭代集合。若我们把List看成是有许多格子的盒子,那么Set就好像一个袋子。
- 常用实现类:
HashSet:使用散列算法(哈希)实现Set集合。
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* Set集合 无序且不重复集
* 常用实现类:
* HashSet:使用散列算法(哈希)实现Set集合
*/
public class SetTest {
public static void main(String[] args) {
//实例化一个HashSet集合
Set<String> set = new HashSet<String>();
/**
* 向集合中添加元素也使用add
* 但是add方法不是向集合末尾追加元素,因为无序
*/
set.add("One");
set.add("Two");
set.add("Three");
/**
* Set集合没有get(int index)方法
* 我们不能像使用List那样,根据下标获取元素。
* 想获取元素需要使用Iterator
*/
Iterator<String> it = set.iterator();
while (it.hasNext()){
String element = it.next();
System.out.println(element);
}
/*
* 宏观上讲:元素的顺序和存放顺序是不同的。
* 但是在内容不变的前提下,存放顺序是相同的(使用相同的算法)。
* 但是我们使用的时候,要当做是无序的使用。
* */
//使用For-Each循环遍历Set集合
for (String element:set){
System.out.println(element);
}
}
}
hashCode()方法
- hashCode()方法是Object定义的方法。所以每个类都会有该方法。若我们定义的类重写了equals()方法,就要重写hashCode()方法(Object提供的hashCode方法将返回该对象所在内存地址的整数形式)。
- 若equals方法返回true,那么这两个对象应该有相同的hashCode值,反过来不是必须的,当时最好是这样。可以提高诸如HashSet这样的数据结构的效率,一般情况下可以使用IDE提供的工具自动生成hashCode方法。
HashSet和hashCode方法的关系
- HashSet是Set接口的实现类,通过hash表的方式实现;在将对象加入HashSet集合中时,需要获取对象的hashCode值通过hash算法索引到对应的存储空间。
- 进行hash算法确定存储空间,当通过contains方法判断Set集合中是否包含某个对象是,需要首先根据对象的hashCode值索引到特定的空间,然后再和空间中的对象调用equals方法进行比较,这种查找方式不同于线性表的逐个比较,有很高的效率。
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));
/**
* 查看新创建的对象是否在set中被包含
* 虽然这里是新创建的对象,但是通过散列算法找到了位置后
* 和里面存放的元素进行equals比较为true,所以依然认为
* 是被包含的。
*/
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帮我们自动生成
*/
@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提供的,
* 该方法是返回句柄(对象存放地址)!换句话说,不同的对象hashCode不同
* @return
*/
@Override
public int hashCode() {
int result = x;
result = 31 * result + y;
return result;
}
}
equals()方法和hashCode()方法对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是否为同一个对象"+(p1==p2));
//重写equals方法和hashCode方法时的结果:false
//重写equals方法未重写hashCode方法时的结果:false
//未重写equals方法重写hashCode方法时的结果:false
System.out.println("p1和p2内容是否一样"+p1.equals(p2));
//重写equals方法和hashCode方法时的结果:true
//重写equals方法未重写hashCode方法时的结果:true
//未重写equals方法重写hashCode方法时的结果:false
System.out.println("p1和p2的hashCode是否一样"+(p1.hashCode()==p2.hashCode()));
//重写equals方法和hashCode方法时的结果:true
//重写equals方法未重写hashCode方法时的结果:false
//未重写equals方法重写hashCode方法时的结果:true
set1.add(p1);
set1.add(p2);
System.out.println("hashset集合的元素数"+set1.size());
//重写equals方法和hashCode方法时的结果:1
//重写equals方法未重写hashCode方法时的结果:2
//未重写equals方法重写hashCode方法时的结果:2
System.out.println(set1);
//重写equals方法和hashCode方法时的结果:[MyPoint{x=1, y=2}]
//重写equals方法未重写hashCode方法时的结果:[MyPoint{x=1, y=2}, MyPoint{x=1, y=2}]
//未重写equals方法重写hashCode方法时的结果:[MyPoint{x=1, y=2}, MyPoint{x=1, y=2}]
/**
* 当我们重写了Point的equals方法和hashCode方法后我们发现
* 虽然p1和p2是两个对象,但是当我们将它们同时放入集合中时,
* p2对象并没有被添加进集合。因为p1在放入后,p2放入时根据
* p2的hashCode计算的位置中p2与该位置的p1的equals比较为true,
* HashSet认为该对象已经存在,所有拒绝将p2存入集合。
*/
/**
* 若我们不重写MyPoint的hashCode方法,但是重写了equals方法。
* 两个对象都可以放入HashSet集合中,因为两个对象具有不同的
* hashCode值,那么当它们在放入集合时通过hashCode值进行
* 散列算法结果就不相同,那么它们会被放入集合的不同位置。
* 位置不相同,HashSet则认为他们不同,所有它们可以全部
* 被存入集合。
*/
/**
* 若我们重写了hashCode但是不重写equals方法,
* hashCode相同的情况下,在存放元素时,它们会在相同的位置,
* HashSet会在相同的位置上将后放入的对象与该位置其它对象依次
* 进行equals比较,若不相同,则将其存入。
* 在同一个位置若干元素,这些元素会被放入一个链表中。
* 由此可以看出。我们应该尽量使得所有类的不同对象的hashCode
* 值不同。这样才可以提高HashSet在检索元素上的效率!
* 否则可能检索效率还不如List。
*/
/**
* 结论:
* 使用HashSet集合存放元素时
* 应保证equals与hashCode方法在api上定义的要求
* 存放规则:
* 不同对象存放时,不会存放hashCode相同(存放位置相同)并且equals相同
* (内容相同)的
* 对象。缺一不可,否则HashSet不认为它们是重复对象。
*/
}
}
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帮我们自动生成
*/
@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提供的,
* 该方法是返回句柄(对象存放地址)!换句话说,不同的对象hashCode不同
* @return
*/
@Override
public int hashCode() {
int result = x;
result = 31 * result + y;
return result;
}
}
网友评论