关于Set的非重复判断以及“==”和“equals”的区别

来源:互联网 发布:电脑弹钢琴的软件知乎 编辑:程序博客网 时间:2024/06/05 14:47
面试时经常被问到set里面是如何判断元素是否重复的,今天特意看了下HashSet的contains源码。。

    private transient HashMap<E,Object> map;

    public boolean contains(Object o) {
          return map.containsKey(o);
    }

可以看到,HashSet里面的元素是存在HashMap 的key区里面,检查重复的方法是借用了HashMap 检查key重复的方法containsKey(),再看看HashMap 中containsKey()的源码:

    public boolean containsKey(Object key) {
        return getEntry(key) != null;
    }

    final Entry<K,V> getEntry(Object key) {
        int hash = (key == null) ? 0 : hash(key.hashCode());
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }

当中e.hash == hash是先比较两者的hashcode,再比较key的内存空间的地址和地址的内容是否相同。面试很多时候会被问到set比较重复是用==还是equals方法,网上说法不一,但是从源码((k = e.key) == key || (key != null && key.equals(k)))来看,其实是两者都用到了,但关键判断还是equals,为什么这样说?因为key是一个Object类,而equals是Object里面自带的方法,看下Object的equals源码就知道,   

public boolean equals(Object obj) {
    return (this == obj);
    }

Object的equals方法直接是返回==的比较值,而Object的子类在复写equals方法时基本都会先判==,例如String的equals:

    public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = count;
        if (n == anotherString.count) {
        char v1[] = value;
        char v2[] = anotherString.value;
        int i = offset;
        int j = anotherString.offset;
        while (n-- != 0) {
            if (v1[i++] != v2[j++])
            return false;
        }
        return true;
        }
    }
    return false;
    }

String也是先用==比较内存空间的地址,再逐个字符值判断的。。那是不是意味着如果equals为true的话,==也肯定为true??我找不到反例,但是相反内存空间的地址不同,但指向的堆空间的值相同这个倒是有可能的,用代码说话:

        final Set set = new HashSet();
        final String str1 = new String("aaa");
        final String str2 = "aaa";
        System.out.println("str1 == str2:" + (str1 == str2));
        System.out.println("str1.equals(str2):" + str1.equals(str2));
        set.add(str1);
        set.add(str2);
        System.out.println("in the set: " + set);

输出结果很明显:
str1 == str2:false
str1.equals(str2):true
in the set: [aaa]

这或许就是网上更倾向于说set是用equals判断的原因吧。。

顺便说下==和equals之间的区别,一下内容摘自网络:

"equals()"比较的是在内存地址空间的内容是否相同,即堆中的内容是否相同;

"=="比较的是在内存空间的地址是否相同,即栈中的内容是否相同;

1.比较方式角度:

= =是面向过程的操作符;equals是面向对象的操作符

= =不属于任何类,equals则是任何类(在Java中)的一个方法;

我们可以1)Primitive1 (基本类型)= = Primitive2(基本类型);

          2)Object Reference1(对象引用)= = Object Reference2(对象引用)

          3)Object Reference1 (对象引用) .equals(Object Reference2 (对象引用))

            这三种比较

            但却不能Primitive1 (基本类型).equals( Primitive2(基本类型));

2. 比较目的角度:

1)     如果要比较两个基本类型是否相等,请用= =;

2)     如果要比较两个对象引用是否相等,请用= =;

3)     如果要比较两个对象(逻辑上)是否一致,请用equals;


对两个对象(逻辑上)是否一致的阐释:

    有人会问:在C++中, 比较两个对象相等不是也可以用==吗?我知道您是指运算符重载,但是很遗憾,Java中不支持运算符重载(java中亦有重载过运算符,他们是“+”,“+=”,不过也仅此两个,而且是内置实现的);所以,对象的是否相等的比较这份责任就交由   equals()来实现。    

这个“逻辑上”其实就取决于人类的看法,实际开发中,就取决于用户的需求;

有人会有看法:“取决于人类的看法”太过宽泛和不严肃,如果某人要两件

风牛马不相及的事物也相等,equals是否也能作出这样的比较呢?我们说可以的

下面这个例子说明了这一点:



class Horse {

        String Type;

        int Legs;

   //相等的标准:腿的数目相等

        public boolean equals(Object o){

           if(this.Legs==((Cattle)o).Legs){

              return true;

           }

        return false;

}

public Horse(String Type,int legs){

            this.Type=Type;

         this.Legs=legs;

}     

}

class Cattle

{

     String Type;

        int   Legs;

      //相等的标准:腿的数目相等

         public Cattle(String Type,int legs){

            this.Type=Type;

         this.Legs=legs;

        }

        public boolean equals(Object o){

           if(this.Legs==((Horse)o).Legs){

              return true;
           }
        return false;
        }
}
public class EqualsTest{

public static void main(String[] args)

        {   

            Cattle c=new Cattle("I'm the Cattle",4);

             Horse h=new Horse("I'm the Horse",4);

            if(c.equals(h)){

                  System.out.println(c.Type);

                  System.out.println(h.Type);

                 System.out.println("Cattle Equals Horse");

             }

        }

}
输出结果:"I'm the Cattle"

           "I'm the Horse"

           "Cattle Equals Horse"

您瞧瞧:牛果真等于了马,为何相等?因为我们定义的相等标准是:腿的数目相等;您会说:“这太滑稽”,是滑稽,可这是人类的看法,计算机可没有滑稽的概念,当然也没有“不滑稽”的概念,我们定义了什么相等标准,他就踏踏实实的为我们实现了;

所以说:相等标准(即需求)一定要定好,否则,滑稽的事可就多了

第三节:equals()缘起:

         equals()是每个对象与生俱来的方法,因为所有类的最终基类就是Object(除去Object本身);而equals()是Object的方法之一。

         我们不妨观察一下Object中equals()的source code:

          public boolean equals(Object obj) {

                       return (this == obj);

           }

         注意 “return (this == obj)”

         this与obj都是对象引用,而不是对象本身。所以equals()的缺省实现就是比较

         对象引用是否一致;为何要如此实现呢?前面我们说过:对象是否相等,是由我们的需求决定的,世界上的类千奇百怪(当然,这些类都是我们根据模拟现实世界而创造的),虽然Object是他们共同的祖先,可他又怎能知道他的子孙类比较相等的标准呢?但是他明白,任何一个对象,自己总是等于自己的,何谓“自己总是等于自己”呢,又如何判断“自己总是等于自己”呢?一个对象在内存中只有一份,但他的引用却可以有无穷多个,“对象自己的引用1=对象自己的引用2”,不就能判断“自己总是等于自己”吗?所以缺省实现实现自然也就是

         “return (this == obj)”;

         而到了我们自己编写的类,对象相等的标准由我们确立,于是就不可避免的要覆写

         继承而来的public boolean equals(Object obj);

         如果您有过编覆写过equals()的经验(没有过也不要紧),请您思考一个问题:

          “两个对象(逻辑上)是否一致”实际上是比较什么?没错,或许您已脱口而出:

        就是对象的属性(即field,或称数据成员)的比较。方法是不可比较的哦。(这个问题是不是有些弱智呢?哈哈)

第四节:对一个推论的思考

推论如下:一言以蔽之:欲比较栈中数据是否相等,请用= =;

                       欲比较堆中数据是否相等,请用equals;

因为(根)基本类型,(根)对象引用都在栈中; 而对象本身在堆中;

          这句话又对又不对,问题出在哪,就是“数据”二字,先看栈中,数据或为基本类型,或为对象引用,用==比较当然没错;但是堆中呢?对象不是堆中吗?不是应该用equals比较吗?可是,我们比较的是堆中“数据”,堆中有对象,对象由什么构成呢?可能是对象引用,可能是基本类型,或两者兼而有之。如果我们要比较他们,该用什么呢,用”equals()”?不对吧,只能是”= =”!所以正确的结论是:欲比较栈中数据是否相等,请用= =; 欲比较堆中数据是否相等,请用equals;

因为(根)基本类型,(根)对象引用都在栈中(所谓“根”,指未被任何其他对象所包含); 而对象本身在堆中。
原创粉丝点击