- String重写equals() 和 hashCode()
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
System.out.println("a".hashCode());//97
System.out.println("ab".hashCode());//3105
System.out.println("ab".hashCode() == "ba".hashCode());//false
- 自定义类重写equals() 和 hashCode()
import java.util.Objects;
public class Son extends Father{
private String name;
private int age;
private int sex;
public Son() {
}
public Son(String name) {
this.name = name;
}
public Son(int age) {
super(123);
this.age = age;
}
public Son(String name, int age) {
this(name);
}
public Son(String name, int age, int sex) {
this(name, age);
this.sex = sex;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Son son = (Son) o;
return age == son.age && sex == son.sex && Objects.equals(name, son.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age, sex);
}
}
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}
public static int hash(Object... values) {
return Arrays.hashCode(values);
}
public static int hashCode(Object a[]) {
if (a == null)
return 0;
int result = 1;
for (Object element : a)
result = 31 * result + (element == null ? 0 : element.hashCode());
return result;
}
HashSet 添加数据,不可重复的逻辑
HashSet底层:数组+链表的结构(JDK7),HashMap(JDK8)
HashSet按hash算法来存储集合中的元素,因此具有很好的存取、查找、删除性能
特点:
无序的:不能保证元素按索引顺序排列
因为它是根据hash算法来决定对象在HashSet底层数组中的存储位置,而不是像ArrayList按索引顺序存储元素对象,这就是无序性。
不可重复的:集合元素可以是null,但也不能重复,也就是最多只能有一个null
HashSet
不是线程安全的
和ArrayList类似,JDK7及之前,空参构造器创建对象,底层是创建一个容量为16的数组;JDK8,空参构造器创建对象,底层是创建一个空数组,当第一次add( )添加元素时,才会创建一个容量为16的数组。以后添加的元素多了,容量不够,底层也会自动扩容。
初始容量16,当使用率超过75%,16*0.75 = 12,就会扩大容量为原来的2倍,依次是32,64,128,…。
添加元素A,先调用A的hashCode( )计算A的hash值,通过散列函数(hash算法)决定该对象在HashSet底层数组中的存储位置(即为数组索引),我们假设散列函数是对hash值 %16(取模,也就是取余)。
假设A的hash值是18,那么取模就是2,去看数组中这个位置有没有其他元素,发现没有,就把A直接放入到这个位置。
接着添加元素B,假设B的hash值也是18,那么取模也是2,去看数组中这个位置有没有其他元素,发现元素A就在这个位置,说明两个元素的hash值相同,那么将要存放到数组中的位置也就一样,这种情况下,再执行B.equals(A),返回true,则说明A和B是相同的元素,就是重复了,让它添加失败,这样就保证了不可重复性。
接着添加元素C,假设C的hash值是2,那么取模也是2,去看数组中这个位置有没有其他元素,发现元素A就在这个位置,说明两个元素的hash值虽然不同,但是将要存放到数组中的位置也可能是一样的,这个完全取决于根据存放位置的算法得到的结果。此时A和C存放的位置一样,因为两个的hash值不一样,所以不用再去执行equals()方法。
但是那个位置已经存放了A,你让C再怎么放进去呢?这就需要使用链表结构了
JDK7是将把A拿出来,将新添加的C放进那个位置,然后C使用链指向A。
JDK8是A不动,使用链指向新添加的C。
便于记忆,使用一个成语很形象。 七上八下。
添加元素A,调用A的hashCode()方法,得到哈希值,通过某种算法计算在HashSet底层数组中的存放位置(即索引位置) ---判断数组此位置上是否有元素 ---没有其他元素,元素A添加成功 ——>添加成功情况1 ---有其他元素B(或者以链表形式存在的多个元素),判断A和B的哈希值 ---如果哈希值不相等,则A添加成功 ——>添加成功情况2 ---如果哈希值相等,调用A的equals(B)方法 ---返回false,A和B不同,则A添加成功 ——>添加成功情况3 ---返回true,A和B重复,则A添加失败 对于添加成功的情况2和3,A与已经存放在指定索引位置上的元素B以链表形式存储 ---JDK7,新添加的A放在数组中,指向原来的元素B。 ---JDK8,数组中的B指向新添加的A
总结:
- HashSet添加元素时,判断元素相等(重复)的标准:两个对象通过HashCode()方法返回值比较相等,并且两个对象的equals()方法返回true。对于存放HashSet容器中的对象,对应的类一定要重写hashCode( ) 和 equals( ) 这两个方法,已实现判断对象相等规则,即“相等的对象必须有相等的散列值”。
散列函数(或散列算法,又称哈希函数,英语:Hash
Function)是一种从任何一种数据中创建小的数字“指纹”的方法。散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来。该函数将数据打乱混合,重新创建一个叫做散列值(hash
values,hash codes,hash
sums,或hashes)的指纹。散列值通常用一个短的随机字母和数字组成的字符串来代表。好的散列函数在输入域中很少出现散列冲突。在散列表和数据处理中,不抑制冲突来区别数据,会使得数据库记录更难找到。
重写hashCode()方法的基本原则
- 在程序运行时,同一个对象多次调用hashCode()方法,应该返回相同的值当两个对象的equals()方法返回true时,则他们调用hashCode()方法的返回值也应该是相等的。对象中用作equals()方法判断的Filed,都应该用来计算hashCode值小技巧:对于以上3点,使用IDEA,直接快捷键来生成equals()和 hashCode()就可以了。
所以这也解释了为什么自定义的类,都需要重写hashCode( ) 和 equals( ) 这两个方法。
因为当HashSet添加元素,会调用元素所在类的hashCode( ) 和 equals( )方法。
大家看看ArrayList 的几个方法,
- 删除指定元素: remove(Object o)是否包含指定元素: contains(Object o)指定元素的索引: indexOf(Object o)方法
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
看到了吧,需要重写equals()方法。
问题二:HashSet 的 去重应用,写一个小Demopublic class MyTest {
public static void main(String[] args) {
ArrayList list = new ArrayList<>();
list.add(123);
list.add(456);
list.add(456);
list.add(123);
System.out.println(list);
List newList = duplicateList(list);
System.out.println(newList);
}
public static List duplicateList(List list) {
HashSet
问题三:看下面程序的输出结果是什么?
import java.util.Objects;
public class Person {
private int id;
private String name;
public Person() {
}
public Person(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + ''' +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return id == person.id && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
public class MyTest {
public static void main(String[] args) {
HashSet
仔
细
思
考
一
会
儿
,
再
来
看
打
印
结
果
:
[Person{id=102, name='BB'}, Person{id=101, name='AA'}]
[Person{id=102, name='BB'}, Person{id=101, name='CC'}]
[Person{id=102, name='BB'}, Person{id=101, name='CC'}, Person{id=101, name='CC'}]
[Person{id=102, name='BB'}, Person{id=101, name='CC'}, Person{id=101, name='CC'}, Person{id=101, name='AA'}]
解析:
HashSet



