栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

String重写equals() 和 hashCode() 对比 自定义类重写equals() 和 hashCode(),HashSet源码分析

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

String重写equals() 和 hashCode() 对比 自定义类重写equals() 和 hashCode(),HashSet源码分析

    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 hashSet = new HashSet<>();
     hashSet.add(null);
     hashSet.add(null);
     hashSet.add(null);
     hashSet.add(123);
     System.out.println(hashSet);//[null, 123]
      

    不是线程安全的

    和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。
    便于记忆,使用一个成语很形象。 七上八下。

    HashSet 添加数据,整理如下:
    	添加元素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 中元素所在类,需要重写什么方法?

    大家看看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 的 去重应用,写一个小Demo
    public 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 set = new HashSet<>();
           set.addAll(list);//去重
           return new ArrayList(set);
       }
    }
     
    问题三:看下面程序的输出结果是什么? 
    
    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 set = new HashSet<>();
            Person p1 = new Person(101, "AA");
            Person p2 = new Person(102, "BB");
            set.add(p1);
            set.add(p2);
            System.out.println(set);
            p1.setName("CC");
            set.remove(p1);
            System.out.println(set);
            set.add(p1);
            System.out.println(set);
            set.add(new Person(101,"AA"));
            System.out.println(set);
       		     
        }
    }
     
    
















    [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 set = new HashSet<>();
    Person p1 = new Person(101, "AA");
    Person p2 = new Person(102, "BB");
    set.add(p1);
    set.add(p2);
    System.out.println(set);//[Person{id=102, name='BB'}, Person{id=101, name='AA'}]
    p1.setName("CC");//修改了p1的name属性,但p1在set底层数组中的索引位置不变
    //和add()操作一样,调用p1的hashCode(),再计算得到索引位置,去底层数组找,是否存在对象。
    //因为p1的name属性变了,所以hashCode()计算的hash值也变了,在新的索引位置是不存在对象的,删除操作失败
    set.remove(p1);
    System.out.println(set);//[Person{id=102, name='BB'}, Person{id=101, name='CC'}]
    set.add(p1);//因为p1的name属性变了,所以hashCode()计算的hash值也变了,在新的索引位置存放p1引用
    System.out.println(set);//[Person{id=102, name='BB'}, Person{id=101, name='CC'}, Person{id=101, name='CC'}]
    //new Person(101,"AA")这个对象调用hashCode(),得到索引位置,去底层数组找,
    //发现p1引用就在那里,这个p1引用的对象是new Person(101, "CC"),
    //很明显两者的equals()为false,所以会以链表的形式存储。
    set.add(new Person(101,"AA"));
    //[Person{id=102, name='BB'}, Person{id=101, name='CC'}, Person{id=101, name='CC'}, Person{id=101, name='AA'}]
    System.out.println(set);
    
    
    
    转载请注明:文章转载自 www.mshxw.com
    我们一直用心在做
    关于我们 文章归档 网站地图 联系我们

    版权所有 (c)2021-2022 MSHXW.COM

    ICP备案号:晋ICP备2021003244-6号