天天看点

HashSet中的元素比较详解

引例

public class Student{
    private String name;
    private int  age;

    Student(String name,int age){
        this.name = name;
        this.age = age;
    }
}

class Demo{
    list hs = new HashSet();
    hs.add("hello");
    hs.add(new Integer());
    hs.add(new Student("dqf","23"));
    hs.add2("hello");
    hs.add2(new Integer());
    hs.add2(new Student("dqf","23"));

    //请问? 哪些不能执行成功?
}
           

如果想要知道答案,必须要知道hashset的相关知识,首先HashSet实现Set接口,set中的元素没有顺序,不可以重复,该集合中的元素不可以通过索引操作。那么HashSet到底通过什么方式来判断某个元素是否已经在集合中存在了呢?

答案

HashSet中的元素比较详解

关于常量池

静态常量池: 也就是class文件中的常量池

看下面一段程序:

public class test{
    String items = "hello";
    int item =  ;
    Student student = new Student();
    public void x(){...}
}
           

上面的这段java代码,在编译成.class文件时,除了会把,类的版本、字段、方法、接口等描述信息写入class文件中外,还会把一些特定的数据类型的字面值和符号引用加入到class文件中,这些字面值和符号引用就组成了class文件的常量池,即静态常量池。 例如在class文件中就会保存【“hello”、“10”】(字面值)、【“items”、“item”】(符号引用),字面值和符号引用之间没有关系。

那么到底哪些数据类型在java文件编译的时候会写入class文件的常量池呢?

据我的了解应该是八种基本数据类型和String类。

如果想要详细了解看着:http://blog.csdn.net/newthinker_wei/article/details/8028019

再看一段小程序:

String s1 = "hello";
String s2 = "hello";
System.out.println(s1==s2);
           

结果为:true;

当创建s2时,如果常量池中有”hello”,则直接指向hello的内存地址,s1==s2.

int a = ; 
Integer b = new Integer();
System.out.println(a==b);
           

结果为:true

b自动拆箱成int类型再和a比较

String a = "hello";
String b = new String("hello");
System.out.println(a==b);//false
           

String类型没有自动装箱

String a = new String("hello");
String b = "hello"
System.out.println(a==b);//false
           
Integer f1 = , f2 = , f3 = , f4 = ;

        System.out.println(f1 == f2);//true
        System.out.println(f3 == f4);//false
           

对于 Integer f1 = 100;当我们给一个Integer对象赋一个int值的时候,会调用Integer类的静态方法valueOf。100不是自动转化成 new Integer(100); 而是自动转化成Integer.valueOf(100)

简单的说,如果整型字面量的值在-128到127之间,那么不会new新的Integer对象,而是直接引用常量池中的Integer对象,所以上面程序中f1==f2的结果是true,而f3==f4的结果是false。

Integer f5 = new Integer();
int f6 = ;
System.out.println(f5==f6);//true
           

解释

有一个约定:只要 两个对象是相同的(这里的相同是地址相同),那么他们的hashcode是一定相同的。

我们知道HashSet的底层实现是哈希表,那哈希表又是什么东西?

如果有一些对象要存在内存中,能不能按照一种特定的算法,给每一个对象分配内存地址呢?根据对象的某些属性就能直接算出这个对象的内存地址。这就是哈希表所要起的作用了。

当我们用一个对象直接算出该对象的内存地址时,两个对象算出的地址可能一样吗?如果一样怎么办?

两个对象的通过哈希算法算出的地址(hashcode值)有可能一样,一样的话就要看哈希表(散列表)的地址冲突解决策略了,有兴趣的可以看看,数据结构中好像有。

另外我还需要知道,在Object类中,当两个对象相等时,(这里说的相等是指:两个对象的内存地址相等)hashcode是一定相等的。但当hashcode相等,对象不一定相等。

在Object类中有两个方法,分别是hashcode()和equals(),equals()是比较两个对象的地址是否相等。当两个对象地址相等时,hashcode是一定相等的。但是,就像上面我将的,两个不一样的对象可能有相等的hashcode。

想要知道为什么上面这一句会添加失败,首先要判断hs集合中到底有没有该对象,首先算出该对象的hashcode,如果在hs集合中能找到hashcode相等的对象,再比较两个对象equals()是否为true。

因为:String类型,重写了equals()和hashcode()方法,String的equals是比较两个对象的值,所以equals就为true;再更具我们上面的约定,equals相等,hashcode一定相等,所以添加失败。

hs.add2(new Integer());//添加失败,解释和hs.add2("hello")相同
           
①hs.add(new Student("dqf","23"));
②hs.add2(new Student("dqf","23"));//添加成功
           

这两句中创建的Student实例是完全不一样的,虽然这两个实例的hashcode可能相等,但是equals()必然不为true,因为Student中没有重写equals()方法。所以添加成功。

继续阅读