本文整理一些Java开发中关于Object类型的方法的使用。

equals方法

equals()方法在object类中定义如下:

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

很明显是对两个对象的地址值进行的比较(即比较引用是否相同)。

但是我们知道,String 、Math、Integer、Double等这些封装类在使用equals()方法时,已经覆盖了object类的equals()方法。

比如在String类中如下:

    /**
     * Compares this string to the specified object.  The result is {@code
     * true} if and only if the argument is not {@code null} and is a {@code
     * String} object that represents the same sequence of characters as this
     * object.
     *
     * @param  anObject
     *         The object to compare this {@code String} against
     *
     * @return  {@code true} if the given object represents a {@code String}
     *          equivalent to this string, {@code false} otherwise
     *
     * @see  #compareTo(String)
     * @see  #equalsIgnoreCase(String)
     */
    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;
    }

equals方法的性质:

对于任意不为null的引用值x,x.equals(null)返回false。

对于Object类来说,equals()方法在对象上实现的是差别可能性最大的等价关系,即,对于任意非null的引用值x和y,当且仅当x和y引用的是同一个对象,该方法才会返回true。

需要注意的是当equals()方法被override时,hashCode()也要被override。按照一般hashCode()方法的实现来说,相等的对象,它们的hash code一定相等。

举个例子

class Person {
  String name;
  int birthYear;
  byte[] raw;
  
  public boolean equals(Object obj) {
    if (!obj instanceof Person)
      return false;
    
    Person other = (Person)obj;
    return name.equals(other.name)
        && birthYear == other.birthYear
        && Arrays.equals(raw, other.raw);
  }
  
  public int hashCode() {
      return a.hashCode() + b.hashCode() + c + Arrays.hashCode(d);
  }
}

当两个对象 x 和 y ,满足 x.equals(y) == true, 则必须要满足 x.hashCode() == y.hashCode()。

hashcode方法

equals和hashCode都是Object对象中的非final方法,它们设计的目的就是被用来覆盖(override)的。

public boolean equals(Object obj)
public native int hashCode();

hashCode()方法给对象返回一个hash code值。这个方法被用于hash tables,例如HashMap。

它的性质是:

Object的hashCode方法是一个native本地方法,它的实现是根据本地机器相关的。当然我们可以在自己写的类中覆盖hashcode()方法,比如String、Integer、Double等这些类都是覆盖了hashcode()方法的。

例如在String类中定义的hashcode()方法如下:

    /**
     * Returns a hash code for this string. The hash code for a
     * {@code String} object is computed as
     * <blockquote><pre>
     * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
     * </pre></blockquote>
     * using {@code int} arithmetic, where {@code s[i]} is the
     * <i>i</i>th character of the string, {@code n} is the length of
     * the string, and {@code ^} indicates exponentiation.
     * (The hash value of the empty string is zero.)
     *
     * @return  a hash code value for this object.
     */
    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;
    }

equals和hashcode方法与Java集合

想要弄明白hashCode的作用,必须要先知道Java中的集合。  

总的来说,Java中的集合(Collection)有两类,一类是List,再有一类是Set。前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。这里就引出一个问题:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢?

这就是Object.equals方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。也就是说,如果集合中现在已经有1000个元素,那么第1001个元素加入集合时,它就要调用1000次equals方法。这显然会大大降低效率。

于是,Java采用了哈希表的原理。哈希(Hash)实际上是个人名,由于他提出一哈希算法的概念,所以就以他的名字命名了。哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上,初学者可以简单理解,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。

这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了,就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。

简而言之,在集合查找时,hashcode能大大降低对象比较次数,提高查找效率!

Java对象的eqauls方法和hashCode方法是这样规定的:

  1. 相等(相同)的对象必须具有相等的哈希码(或者散列码)。
  2. 如果两个对象的hashCode相同,它们并不一定相同。当然,哈希冲突越少越好,尽量采用好的哈希算法以避免哈希冲突。

以下是Object对象API关于equal方法和hashCode方法的说明:

  1. If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
  2. It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.

clone方法和Cloneable接口

Java中实现了Cloneable接口的类有很多,像我们熟悉的ArrayList、Calendar、Date、HashMap等等。

关于Cloneable接口

  1. 此类实现了Cloneable接口,以指示Object的clone()方法可以合法地对该类实例进行按字段复制
  2. 如果在没有实现Cloneable接口的实例上调用Object的clone()方法,则会导致抛出CloneNotSupporteddException
  3. 按照惯例,实现此接口的类应该使用公共方法重写Object的clone()方法,Object的clone()方法是一个受保护的方法

Java默认对象赋值都是直接拷贝引用地址,如果需要创建一个对象和原始对象无关,互不影响,这时就需要深拷贝。

重写clone()方法,一般会先调用super.clone()进行浅复制,然后再复制那些易变对象,从而达到深复制的效果。除了基本类型(int,double等)和不可变类型(String等),其他类型的字段都需要做深拷贝。

class Values implements Cloneable {
  String abc;
  double foo;
  int[] bars;
  Date hired;
  
  public Values clone() {
    try {
      Values result = (Values)super.clone();
      result.bars = result.bars.clone();
      result.hired = result.hired.clone();
      return result;
    } catch (CloneNotSupportedException e) {  // Impossible
      throw new AssertionError(e);
    }
  }
}

参考