对于今晚上讲课的总结:
public class HashSet_ {
public static void main(String[] args) {
HashSet set = new HashSet();
set.add("lucy");//会返回一个boolean值
set.add("lucy");//加入不了
set.add(new Dog("tom"));//true
set.add(new Dog("tom"));//true
System.out.println("set=" + set);
//非常经典的面试题
set.add(new String("111"));//true
set.add(new String("111"));//false
System.out.println("set=" + set);
}
}
class Dog { //定义了 Dog 类
private String name;
public Dog(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
'}';
}
}
输出:
true
true
set=[Dog{name='tom'}, Dog{name='tom'}, lucy]
true
false
set=[111, Dog{name='tom'}, Dog{name='tom'}, lucy]
对于为什么第二个lucy不能添加,第二个tom可以添加成功以及为什么第二个111不能添加成功的解释:
首先我们要明白hsahset添加元素的原理:HashSet是Java中的一种集合类,它基于哈希表实现。在HashSet中,每个元素都会被存储在一个桶(bucket)中,桶是根据元素的哈希值来确定的。因此,如果两个元素的哈希值相同,它们就会放在同一个桶里,称为哈希冲突。
在HashSet中,元素的存储顺序不是按照插入的顺序来存储的,而是按照元素的哈希值来存储的。当需要查找某个元素时,HashSet会先计算这个元素的哈希值,然后根据哈希值找到对应的桶,最后再在桶中进行查找操作。
当我们向HashSet中添加一个新元素时,HashSet会首先计算该元素的哈希值,并根据哈希值确定该元素应该放置的桶。如果桶为空,就直接将该元素添加到桶中;否则就需要遍历桶中已有的元素,判断是否存在相同的元素。如果已存在相同元素,则不会添加,否则就将其添加到桶中。
HashSet的内部采用了数组加链表或红黑树的方式来管理桶。当桶中元素较少时,采用数组加链表的方式;当元素数量增多时,会自动转换为红黑树的方式,以提高查找效率。
当添加元素到 HashSet 中时,首先会根据元素的 hashCode() 值找到它在哈希表中对应的位置,然后调用该元素的 equals() 方法,判断该位置上是否有相同的元素。如果有相同的元素,则不添加新元素,返回 false;否则添加新元素,返回 true。
- 对于第二个 "lucy" 字符串和第一个 "lucy" 字符串来说,它们的 hashCode() 值是相同的,因为它们的内容相同。当添加第二个 "lucy" 字符串时,HashSet 会先根据该字符串的 hashCode() 找到它在哈希表中的位置,然后调用第一个 "lucy" 字符串的 equals() 方法进行比较。由于两个字符串的内容相同,equals() 方法返回 true,因此 HashSet 认为第二个 "lucy" 字符串已经存在于 HashSet 中,不再添加新元素,直接返回 false。
- 对于第二个 Dog 对象和第一个 Dog 对象来说,虽然它们是两个不同的对象,但它们的 name 属性相同。在默认情况下,Object 类中的 hashCode() 方法会根据对象的内存地址计算其哈希值,而 equals() 方法则只比较两个对象的内存地址是否相同。因此,两个不同的 Dog 对象的 hashCode() 和 equals() 方法的返回值都不相同,HashSet 认为它们是不同的元素。但是,我们可以在 Dog 类中重写 hashCode() 和 equals() 方法,让它们根据对象的属性计算其哈希值和相等性,这样就可以根据属性判断两个不同的 Dog 对象是否相同了。
- 这两个 "111" 字符串具有相同的内容,但是在示例代码中它们是两个不同的对象。由于 String 类已经重写了 hashCode() 方法和 equals() 方法,它们会根据字符串的内容计算哈希值和相等性判断结果。
- 因此,当将第一个 "111" 字符串添加到 HashSet 中时,HashSet 会先调用它的 hashCode() 方法来计算哈希值,然后根据哈希值找到它在哈希表中的位置,最后再调用它的 equals() 方法,判断该位置上是否已经有相同元素。由于此时哈希表为空,HashSet 会将第一个 "111" 字符串添加到哈希表中,并返回 true。
- 当将第二个 "111" 字符串添加到 HashSet 中时,HashSet 同样会先调用它的 hashCode() 方法来计算哈希值,然后根据哈希值找到它在哈希表中的位置,最后再调用它的 equals() 方法,判断该位置上是否已经有相同元素。由于这两个字符串的 hashCode() 和 equals() 方法返回值都相同,HashSet 认为这两个字符串是相同的元素,不能将第二个 "111" 字符串添加到 HashSet 中,而是直接返回 false。
- 请注意,String 类的 hashCode() 方法和 equals() 方法都是根据字符串的内容来计算的,因此具有相同内容的字符串在 HashSet 中被认为是相同的元素,而与它们是不是同一个对象没有关系。
这里可能有人会对第二个例子和第三个例子有疑惑。
因为Object 类中的 hashCode() 方法和 equals() 方法的计算方式与 String 类不同。在默认情况下,Object 类中的 hashCode() 方法会返回当前对象的内存地址经过处理后的哈希值,而 equals() 方法只比较两个对象的内存地址是否相同。因此,对于不同的对象,它们的 hashCode() 和 equals() 方法的返回值通常是不同的。由于 Object 类中的 hashCode() 方法和 equals() 方法无法根据对象的属性来计算哈希值和相等性判断结果,因此如果需要使用 HashSet 这样的集合类型来存储自定义的对象,我们通常需要在自定义类中重写这两个方法,让它们根据对象的属性进行计算。而对于 String 类来说,它已经重写了 hashCode() 方法和 equals() 方法,让它们根据字符串的内容来计算哈希值和相等性判断结果。因此,无论字符串是不是同一个对象,只要它们的内容相同,它们的 hashCode() 和 equals() 方法的返回值都是相同的。关于string类的hashCode() 方法和 equals() 方法的计算在 String 类中,hashCode() 方法被重写为根据字符串的内容计算哈希值。具体地说,它会将字符串看作一个字符序列,对每个字符计算一个权重值,然后将这些权重值相加,最后将结果转换成 int 类型作为哈希值返回。这种计算方式可以保证相同内容的字符串具有相同的 hashCode() 返回值。
在 String 类中,equals() 方法也被重写为根据字符串的内容进行相等性判断。具体地说,它会先比较两个字符串的长度是否相等,如果不相等,则认为两个字符串不相等;如果长度相等,则逐个比较两个字符串中的字符是否相等。如果所有的字符都相等,则认为两个字符串相等,否则认为它们不相等。由于 String 类的 hashCode() 方法和 equals() 方法都是根据字符串的内容计算的,因此相同内容的字符串在 HashSet 中被认为是相同的元素,而与它们是不是同一个对象没有关系。
对重写的解释:
要使第二个名为 "tom" 的 Dog 对象无法添加到 HashSet 中,需要在 Dog 类中重写 hashCode() 和 equals() 方法,让它们根据 Dog 对象的 name 属性进行计算。
重写后 :
class Dog {
private String name;
public Dog(String name) {
this.name = name;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
'}';
}
@Override
public int hashCode() {
// 根据 name 属性计算哈希值
return Objects.hash(name);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Dog)) return false;
Dog other = (Dog) obj;
// 比较 name 属性是否相等
return Objects.equals(name, other.name);
}
}
重写后输出
true
false
set=[Dog{name='tom'}, lucy]
true
false
set=[Dog{name='tom'}, 111, lucy]
在上述代码中,我们重写了 Dog 类的 hashCode() 和 equals() 方法。在 hashCode() 方法中,我们使用 Objects 类的 hash() 方法根据 name 属性计算哈希值;在 equals() 方法中,我们首先判断 obj 是否是当前对象本身,如果是则直接返回 true;然后判断 obj 是否是一个 Dog 对象,如果不是则返回 false;最后将 obj 强制转换为 Dog 对象,并比较两个对象的 name 属性是否相等,如果相等则返回 true,否则返回 false。通过这样的 hashCode() 和 equals() 方法,可以让 HashSet 根据 Dog 对象的 name 属性进行哈希值计算和相等性判断,从而确保同名的 Dog 对象不会被重复添加到 HashSet 中。
因为在默认情况下,HashSet 会使用对象的 hashCode() 方法和 equals() 方法来进行元素的哈希值计算和相等性判断。而在示例代码中,两个名为 "tom" 的狗虽然名字相同,但是它们是两个不同的对象,并且没有重写 hashCode() 和 equals() 方法。因此,HashSet 只会根据这两个对象的内存地址来判断它们是否相等,尽管它们的名字相同,但是它们分别分配了不同的内存地址,因此它们被认为是两个不同的元素。如果没有重写 hashCode() 和 equals() 方法,无法根据对象的属性来进行元素的哈希值计算和相等性判断,从而会导致同名的狗被重复添加到 HashSet 中。
String s1= new String("111");
String s2 = new String("111");
System.out.println(s1==s2);//输出false
String s3 = new String("111");
String s4 = new String("111");
System.out.println(s1.equals(s2)); // 输出 true
Java 中,== 和 equals() 都是用来比较两个对象的方法,但它们的比较方式不同。== 操作符比较的是两个对象的引用地址是否相同,也就是说它们是否指向同一块内存地址。使用 == 进行比较时,如果比较的两个对象引用地址相同,则返回 true,反之则返回 false。equals() 方法比较的是两个对象的内容是否相同,也就是说它们所包含的数据是否相等。默认情况下,equals() 方法比较的是两个对象的引用地址是否相同,和 == 操作符的作用相同。但是,对于一些类(如 String 类),它们重写了 equals() 方法以实现按值比较。一般来说,如果我们要比较两个对象的值是否相等,应该使用 equals() 方法进行比较。如果我们要比较两个对象的引用地址是否相等,可以使用 == 操作符进行比较。