【码艺杂谈】Java中的相同与不同

Java中有很多场景需要判断两个对象或者两个值,那么

  • 判断是否相同的依据是什么?
  • 如何判断是否相同呢?

为了解释这个问题,我们从Java语言的根说起,那Java语言的根在哪里?我们知道Java是一种面向对象的编程语言,对象是类的实例,所有的类都隐式继承Object类,那Object类就是所有类的父类,也就是我们所说的根。
Object类中方法不多,其中有两个方法,一个叫equals,另一个叫hashCode;

JDK中对equals的定义是:

Indicates whether some other object is “equal to” this one.

意思是说equals方法是用来判断两个对象是否相同的,到这里是不是已经得到了文章开始的问题答案了呢,其实只能说得到了三分之一的答案,再看equals方法的实现,

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

代码很简单,通过“== ”判断两个对象是否相同;这里就要解释一下Java中“== ”符号的作用,文章开头也说了Java是一种面向对象的编程语言,所以Java中全都是对象,这样表述是否正确呢?应该算不完全正确,因为Java中还有一类基本数据类型,比如byte、short、int、long、float、double、boolean;当“== ”符号作用于基本数据类型时,其比较的是值,当==符号作用于类对象时其比较的是对象在堆内存的地址。
显然Object类的equals方法适用的是类对象;也就是equals比较的是对象的堆内存地址,如果自定义的类不重写Object的equals方法,那么比较这个类的两个对象相当于比较这两个对象的堆内存地址,实际情况是,很多自定义的类对象的比较并不想通过判断对象的堆内存地址判断两个对象是否相同,而需要定义一些符合业务需求的规则,也就是说需要重写equals方法;比如String类就重写了Object的equals方法,

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方法,开头还提到了hashCode方法,hashCode方法有什么作用呢?

Object类的hashCode方法定义如下,可以看出其是一个本地方法,该方法的解释是“Returns a hash code value for the object.”

public native int hashCode();

hashCode方法的定义中描述了其general contract:

  • 相同的对象每次执行hashCode的结果是相同的,这里的相等是指通过equals比较相同。
  • 如果根据equals方法得到两个对象相同,那么这两个对象的hashCode方法的结果也一定相同。
  • 如果根据equals方法得到两个对象不相同,那么两个对象的hashCode方法的结果不一定不相同,我们可以利用这一点来提高散列表的性能。

hashCode方法的定义还有一句话:This method is supported for the benefit of hash tables such as those provided by {@link java.util.HashMap}.
这句话什么意思呢?直白一点就是hashCode方法只有在散列表中才有作用;那什么是散列表?下面是维基百科的定义:

In computing, a hash table (hash map) is a data structure that implements an associative array abstract data type, a structure that can map keys to values. A hash table uses a hash function to compute an index into an array of buckets or slots, from which the desired value can be found.

散列表是一个以空间换时间的数据结构,通过key查找value,通过一个计算hash值的函数得到每个key在结构中的位置索引,通过位置索引能够快速定位value。HashMap是Java语言中散列表的一种实现,下面是HashMap的hash方法和put方法的源码,

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

可以看到在计算key的散列值时,用到了key的hashCode方法。

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node[] tab; Node p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
        ...
    }

当网map中放值时,如果key的hash值相等时,用equals方法判断key是否相等,如果相等说明key在map中存在,则用新的value替换当前的value。说了这么多,到底想说明什么?我们反过来思考一下,如果没有hashCode方法,我们判断某个键是否已经存在,要通过equals方法逐个比较这个key和map中的所有key,数据量小的情况下是可以接受的,如果数据量大,这个比较的开销是难以接受的,这就体现出了hashCode的作用,不需要和map中所有的key逐个比较,只需要比较hash值相同的即可,大大减少了比较的时间。这里即用到了key的equals方法又用到了hashCode方法,如果自定义类按照业务逻辑重写了equals方法,但没有按照类似的逻辑重写hashCode方法,key值是否重复的判断结果就会有问题,所以一般情况下如果重写了equals方法,一定要重写hashCode方法。

最后回到文章开头的问题,如何判断两个对象或值是否相同?这个问题其实有两方面的含义,一方面是判断的方法,另一方面是判断的效率。

  • 判断的方法:equals方法和符号“== ”,分别用于对象和基本数据类型。
  • 在需要判断的数据量很大的情况下,用equals方法逐个比较效率是很低的,这时候hashCode方法就派上用场了,hashCode方法的定义决定了其一些特性,相同对象的hashCode方法返回的值是相等的,不同的对象的hashCode方法返回值不一定不相同,所以通过hashCode方法能够大大缩小比较的范围,提高比较的效率。

你可能感兴趣的:(Java,语言)