Dolphin的博客

Java的Object类

今天面试的时候,面试官问起了Object有哪些方法,回答出来大半,不过还是不完全,理解不够深入,平时还没有细细研究这些内容,回家时仔细总结,并记录如下。Java中的Object类是所有类的父类,它提供了以下11个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public final native Class<?> getClass();
public native int hashCode();
public boolean equals(Object obj) {
return (this == obj);
}
protected native Object clone() throws CloneNotSupportedException;
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
public final native void notify();
public final native void notifyAll();
public final native void wait(long timeout) throws InterruptedException;
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}

if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}

if (nanos > 0) {
timeout++;
}

wait(timeout);
}
public final void wait() throws InterruptedException {
wait(0);
}
protected void finalize() throws Throwable { }

getClass方法

getClass方法是一个final方法,不允许子类重写,并且也是一个native方法。

返回当前运行时对象的Class对象,注意这里是运行时,比如以下代码中n是一个Number类型的实例,但是java中数值默认是Integer类型,所以getClass方法返回的是java.lang.Integer:

1
2
3
4
"str".getClass() // class java.lang.String
"str".getClass == String.class // true
Number n = 0;
Class<? extends Number> c = n.getClass(); // class java.lang.Integer

hashCode方法

hashCode方法也是一个native方法。

该方法返回对象的哈希码,主要使用在哈希表中,比如JDK中的HashMap。

哈希码的通用约定如下:

在java程序执行过程中,在一个对象没有被改变的前提下,无论这个对象被调用多少次,hashCode方法都会返回相同的整数值。对象的哈希码没有必要在不同的程序中保持相同的值。
如果2个对象使用equals方法进行比较并且相同的话,那么这2个对象的hashCode方法的值也必须相等。
如果根据equals方法,得到两个对象不相等,那么这2个对象的hashCode值不需要必须不相同。但是,不相等的对象的hashCode值不同的话可以提高哈希表的性能。
通常情况下,不同的对象产生的哈希码是不同的。默认情况下,对象的哈希码是通过将该对象的内部地址转换成一个整数来实现的。

String的hashCode方法实现如下, 计算方法是 s[0]31^(n-1) + s[1]31^(n-2) + … + s[n-1],其中s[0]表示字符串的第一个字符,n表示字符串长度:

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;

}
比如”fo”的hashCode = 102 31^1 + 111 = 3273, “foo”的hashCode = 102 31^2 + 111 * 31^1 + 111 = 101574 (‘f’的ascii码为102, ‘o’的ascii码为111)

hashCode在哈希表HashMap中的应用:

// Student类,只重写了hashCode方法
public static class Student {

private String name;
private int age;

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

@Override
public int hashCode() {
    return name.hashCode();
}

}

Map<Student, String> map = new HashMap<Student, String>();
Student stu1 = new Student(“fo”, 11);
Student stu2 = new Student(“fo”, 22);
map.put(stu1, “fo”);
map.put(stu2, “fo”);
上面这段代码中,map中有2个元素stu1和stu2。但是这2个元素是在哈希表中的同一个数组项中的位置,也就是在同一串链表中。 但是为什么stu1和stu2的hashCode相同,但是两条元素都插到map里了,这是因为map判断重复数据的条件是 两个对象的哈希码相同并且(两个对象是同一个对象或者两个对象相等[equals为true])。 所以再给Student重写equals方法,并且只比较name的话,这样map就只有1个元素了。

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return this.name.equals(student.name);
}
这个例子直接说明了hashCode中通用约定的第三点:

第三点:如果根据equals方法,得到两个对象不相等,那么这2个对象的hashCode值不需要必须不相同。但是,不相等的对象的hashCode值不同的话可以提高哈希表的性能。 –> 上面例子一开始没有重写equals方法,导致两个对象不相等,但是这两个对象的hashCode值一样,所以导致这两个对象在同一串链表中,影响性能。

当然,还有第三种情况,那就是equals方法相等,但是hashCode的值不相等。

这种情况也就是违反了通用约定的第二点:

第二点:如果2个对象使用equals方法进行比较并且相同的话,那么这2个对象的hashCode方法的值也必须相等。 违反这一点产生的后果就是如果一个stu1实例是Student(“fo”, 11),stu2实例是Student(“fo”, 11),那么这2个实例是相等的,但是他们的hashCode不一样,这样是导致哈希表中都会存入stu1实例和stu2实例,但是实际情况下,stu1和stu2是重复数据,只允许存在一条数据在哈希表中。所以这一点是非常重点的,再强调一下:如果2个对象的equals方法相等,那么他们的hashCode值也必须相等,反之,如果2个对象hashCode值相等,但是equals不相等,这样会影响性能,所以还是建议2个方法都一起重写。