Table of Contents

(1) registerNatives

(2) getClass

(3) hashCode

hash 的定义

Java 中 hash 值的含义

(4) equals

(5) clone

(6) toString

(7) notify

(8) notifyAll

(9) wait(long timeout)

(10) wait(long timeout, int nanos)

(11) wait()

(12) finalize()


Object类可以称之为java中最重要的一个类。

java是单根继承的语言,这是和c++的区别之一,而最顶上的类就是Object。在jdk1.8源码中的Object类中的方法有以下这些,有很多方法事实上源码的注释已经解释的非常清楚了,所以我直接把这部分注释粘贴了出来.

抛开所有的注释不看,其实object类的代码非常之少(注意少仅仅指的是java代码),整体的结构如下:

package java.lang;

/**
 * Class {@code Object} is the root of the class hierarchy.
 * Every class has {@code Object} as a superclass. All objects,
 * including arrays, implement the methods of this class.
 *
 * @author  unascribed
 * @see     java.lang.Class
 * @since   JDK1.0
 */
public class Object {

    private static native void registerNatives();
    static {
        registerNatives();
    }
 
    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 { }
}

首先,整个类上的注释是值得关注的: Object类是类结构的根(root),每一个类都有Object这个父类(superclass)。所有的对象,包括数组(arrays)都实现了Object类的方法

把这段话记清楚,很多面试问题或者说关于java体系结构的一些问题相信大家就会更加清楚更加的确定了。

类的整体方法其实很简单,registerNatives方法是注册静态方法的意思,然后object类在静态代码块里调用了它,结合类加载机制,我们可以知道在每一个类加载的时候的初始化阶段,则此方法就会执行。

另外需要指出的是,object类中的大部分方法都是native方法,意味着他们都是用其他语言实现的。非native的方法仅有 equals, toString,finalize三个方法。

其中equlas方法的实现就是两个等号,这意味着java语言机制本身如何解析两个等号,则这个方法的默认实现就是怎样,而我们已经再熟悉不过了,两个等号返回true的条件是两个对象的引用(或者说名字)事实上指代的是同一个对象,即在内存里他们指向的是同一个对象。

toString方法的实现是拼接了一个字符串,这个字符串由三个部分组成:

  1. class的名字(调用了Object类中的getClass方法)
  2. 符号@
  3. 对象的hashcode的16进制表示(这里调用了Object类中的hashcode方法,这个方法的返回值类型是int,然后调用了Integer类的toHexString方法将它转换为了一个对应的16进制的字符串)

finalize方法并未在Object里面实现,我们知道的是这个方法是由虚拟机调用,在GC的时候用于终结一个对象的生命周期的方法。Effective Java的建议是尽量不要使用这个方法,除非以它作为安全网,或是为了终结非关键的原生资源。

(1) registerNatives

一个native方法一个静态代码块,上文已有分析。

private static native void registerNatives();
static {
    registerNatives();
}

(2) getClass

返回当前这个Object运行时的class对象

public final native Class<?> getClass();

此方法与反射技术相关。

 

(3) hashCode

hash 的定义

Hash,一般翻译做“散列”,也有直接音译为“哈希”的,就是把任意长度的输入(又叫做预映射, pre-image),通过散列算法,变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。

Java 中 hash 值的含义

  • hash 值主要是用来在散列存储结构中确定对象的存储地址的,提高对象的查询效率,如HashMap、HashTable等;
  • 如果两个对象相同,那么这两个对象的 hash 值一定相等;
  • 如果要重写对象的 equals 方法,那么尽量重写对象的 hashCode 方法;
  • 两个对象的 hash 值相等,并不一定表示两个对象相同。

返回当前对象的hash code的值,这个方法是为了用于支持hash表比如HashMap

return  a hash code value for this object

public native int hashCode();

在effecitive java一书中,提到一条对所有对象都适用的规则:在每个覆盖了equals方法的类中,也必须覆盖hashcode方法。如果不这样的话,就会违反Object.hashCode的通用约定,从而导致该类无法结合所有基于散列的集合(HashMap,HashSet,HashTable)一起正常运作. 以下是java Object的规范:

  1. 在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法都必须始终如一的返回同一个整数。(原因是:如果将一个对象用put方法添加进HashMap时产生一个Hashcode值,而用get方法取出是却产生了另一个hashcode,那么就无法重新取得该对象了)在一个应用程序的多次执行过程中,每次执行所返回的整数可以不一致。 
  2. 如果两个对象根据equals方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果。 
  3. 如果两个对象根据equals方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,则不一定要产生不同的整数结果。但是程序员应该知道,给不相等的对象产生截然不同的整数结果,有可能提高散列表(hash table)的性能。(比如,当一个entity只根据id比较是否相等,但是在没实例化之前,没有id数值,那么默认的equals返回false,但是hashCode返回的值却相等。)

对于对象的hashcode方法,一个比较典型的例子是取余,假如我实现了一个hashcode方法是求这个数除以10的余数,那么这个时候,7和17这两个不相等的对象,就会具有相同的余数,也就是说他们的hashcode值就是相等的。 

为什么重写equals时也必须重写hashcode方法呢,原因是在集合当中很多实现逻辑都是根据计算键对象的哈希码来计算存储位置, 如Hashtable

我们知道在Object类中,hashCode方法是通过Object对象的地址计算出来的,因为Object对象只与自身相等,所以同一个对象的地址总是相等的,计算取得的哈希码也必然相等,对于不同的对象,由于地址不同,所获取的哈希码自然也不会相等。因此到这里我们就明白了,如果一个类重写了equals方法,但没有重写hashCode方法,将会直接违法了第2条规定,这样的话,如果我们通过映射表(Map接口)操作相关对象时,就无法达到我们预期想要的效果。

下面是一个只重写了equals方法,而未重写hashcode方法的例子:

public class MapTest {
    public static void main(String[] args) {
        Map<String, Value> map1 = new HashMap<String, Value>();
        String s1 = new String("key");
        String s2 = new String("key");
        Value value = new Value(2);
        map1.put(s1, value);
        System.out.println("s1.equals(s2):" + s1.equals(s2));
        System.out.println("map1.get(s1):" + map1.get(s1));
        System.out.println("map1.get(s2):" + map1.get(s2));

        Map<Key, Value> map2 = new HashMap<Key, Value>();
        Key k1 = new Key("A");
        Key k2 = new Key("A");
        map2.put(k1, value);
        System.out.println("k1.equals(k2):" + s1.equals(s2));
        System.out.println("map2.get(k1):" + map2.get(k1));
        System.out.println("map2.get(k2):" + map2.get(k2));
    }

    static class Key {
        private String k;

        public Key(String key) {
            this.k = key;
        }

        @Override
        public boolean equals(Object obj) {
            if (obj instanceof Key) {
                Key key = (Key) obj;
                return k.equals(key.k);
            }
            return false;
        }
    }

    static class Value {
        private int v;

        public Value(int v) {
            this.v = v;
        }

        @Override
        public String toString() {
            return "类Value的值-->" + v;
        }
    }
}

它的打印结果如下:

s1.equals(s2):true
map1.get(s1):类Value的值-->2
map1.get(s2):类Value的值-->2

k1.equals(k2):true
map2.get(k1):类Value的值-->2
map2.get(k2):null

可以看到,map2.get(k2)并没有拿到我们想要取得的对象,因为在HashMap这个容器的的逻辑里面,会把key1和key2当做两个不同的key,因为他们的hashcode值是不一样的,所以他们在散列表当中的位置也就是不一样的。

一个例子是String类的hashcode方法的实现,笔者单独写了一篇介绍String类的博客里面有涉及。

(4) equals

Indicates whether some other object is "equal to" this one

表示某一个对象与这个对象是否一样.

需要指出的是,这里的一样是指的是否是同一个对象。

/*
* The precise meaning
* of "copy" may depend on the class of the object. The general
* intent is that, for any object {@code x}, the expression:
* <blockquote>
* <pre>
* x.clone() != x</pre></blockquote>
* will be true, and that the expression:
* <blockquote>
* <pre>
* x.clone().getClass() == x.getClass()</pre></blockquote>
* will be {@code true}, but these are not absolute requirements.
* While it is typically the case that:
* <blockquote>
* <pre>
* x.clone().equals(x)</pre></blockquote>
* will be {@code true}, this is not an absolute requirement.
*/
public boolean equals(Object obj) {
    return (this == obj);
}

上面已经提到,equals方法默认的实现是java如何解析==就如何实现的,即比较的两者时一个对象的两个不同的名字的时候,就认为它们时equals的。

关于equals方法,在effecitive java书中提到了一条规则:覆盖equals方法的时候要遵守通用约定。

首先在很多的情况时不应该去覆盖equals方法的,包括:

  • 类的每个实例本质上都是唯一的
    什么意思呢, 就是说这个类表示活动实体, 而不是值, 就不需要去覆盖equals了, Object提供的实现就已经足够了. 比如说Thread, GUI的控件等等
  • 不关心类是否提供逻辑相等的测试功能
    比如说java.util.Random覆盖了equals, 用来检查两个Random实例产生的随机数序列是否相同, 其实这并没有什么卵用啊, Object对于equals的实现就够啦
  • 超类已经覆盖了equals, 对于子类也适用
    这种情况无需再去实现equals

在什么时候应该覆盖equals方法呢?如果类具有自己特有的逻辑相等概念,不同于对象等同的概念,这时我们通常需要考虑覆盖equals方法以实现期望的行为。通常值类会有这样的情形,例如Integer,或者Date。程序员在调用equals方法来比较值对象的引用时,希望知道它们仅在逻辑上是否相等,而不是想了解它们是否指向同一个对象。这样做也使得这个类的实例可以被使用做为映射表(map)的key,或者集合(set)的元素,使得集合表现出预期的行为。例如下面这个代码:

String s1 = "1";
String s2 = "1";

当我们把这两个变量用作映射表的key时,它们显然应该时一样的key才合理。

下面是在覆盖equals方法时,Object的规范:

自反性(reflexive):对于任何非null的引用值x,x.equals(x)必须返回true。 
如果违背,当你把该类的实例添加到集合(collection)中,然后该集合的contains会告诉你,没有包含你刚刚添加的实

对称性(symmetric):对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。

传递性(transitive):对于任何非null的引用值x、y和z,如果x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必须返回true。 

一致性(consistent):对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)就会一致的返回true,或者一致的返回false。 
都不要是equals方法依赖于不可靠的资源。 
非空性(Non-nullity):对于任何非null的引用值x,x.equals(null)必须返回false。 

结合这些要求,得出了以下实现高质量equals方法的诀窍:

  1. 使用==操作符检查“参数是否为这个对象的引用”。 
  2. 使用instanceof操作符检查“参数是否为正确的类型”。 
  3. 把参数转换成正确的类型。 
  4. 对于该类的每个“关键(significant)”域,检查参数中的域是否与该对象中对应的域相匹配。 
  5. 当你编写完成了equals方法之后,应该会问自己三个问题:他是否的对称的、传递的、一致的。 (覆盖equals时总要覆盖hashcode;不要企图让equals方法过于智能;不要将equals声明中的Object对象替换为其他的类型。) 

(5) clone

返回一个对象的一个克隆体

return a clone of this instance。

protected native Object clone() throws CloneNotSupportedException;

要谨慎的覆盖clone方法。

一个类要想实现克隆,需要实现Cloneable接口,表明这个类的对象具有克隆的功能。

所有实现了Cloneable接口的类都应该有一个公有的方法覆盖从Object类继承而来的clone()方法,此公有方法先调用super.clone(),

得到一个“浅拷贝”的对象,然后根据原对象的实例域的情况,修正任何需要修正的域。一般情况下,这意味这要拷贝任何包含内部“深层结构”

的可变对象。并将新对象的引用代替原来指向这些对象的引用。虽然,这些内部拷贝操作往往可以通过层层递进的调用clone()来完成(要求图中的

Apple类和Dog类也必须正确的提供公有的clone()方法),但这通常不是最佳的方法。如果该类只包含基本类型的域,和不可变类型的引用域,

那么多半情况下是没有域需要修正的。

(6) toString

返回一个用于表达一个对象的字符串。

return  a string representation of the object.

public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

我们已经知道了,它包含类名称,一个“@”符号,以及散列码的无符号十六进制表示法。这通常不是用户期望看到。

提供好的toString实现可以使类用起来更加舒适,当对象被传递给println、printf、字符串联操作符(+)以及assert或者被调试器打印出来时,toString方法会被自动调用。

1.无论是否决定指定格式,都应该在文档中明确地表明意图,通过文档注释来说明。

2.无论是否指定格式,都为toString返回值中包含的所有信息,提供一种编程式的访问途径,如使用getter方法提供。否则程序员必须去解析字符串,而这会导致大量不必要的工作量和增加程序出错的可能性。

(7) notify

从等待队列里唤醒一个线程,选择是随机的。

/*
* Wakes up a single thread that is waiting on this object's
* monitor. If any threads are waiting on this object, one of them
* is chosen to be awakened. The choice is arbitrary and occurs at
* the discretion of the implementation. A thread waits on an object's
* monitor by calling one of the {@code wait} methods.
*/
public final native void notify();

(8) notifyAll

唤醒所有在等待对象监视器的线程。

/*
* Wakes up all threads that are waiting on this object's monitor. A
* thread waits on an object's monitor by calling one of the
* {@code wait} methods
*/
public final native void notifyAll();

(9) wait(long timeout)

让当前线程(current thread)等待,直到另一个线程调用 notify方法或者notifyAll方法,或者等待的时间足够了

/**
 * Causes the current thread to wait until either another thread invokes the
 * {@link java.lang.Object#notify()} method or the
 * {@link java.lang.Object#notifyAll()} method for this object, or a
 * specified amount of time has elapsed.
 * <p>
 * The current thread must own this object's monitor.
*/
public final native void wait(long timeout) throws InterruptedException;

(10) wait(long timeout, int nanos)

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);
}

(11) wait()

让当前线程(current thread)等待,直到另一个线程调用 notify方法或者notifyAll方法。和调用wait(0)的行为,效果是一模一样的

/**
 * Causes the current thread to wait until another thread invokes the
 * {@link java.lang.Object#notify()} method or the
 * {@link java.lang.Object#notifyAll()} method for this object.
 * In other words, this method behaves exactly as if it simply
 * performs the call {@code wait(0)}.
*/
 
public final void wait() throws InterruptedException {
    wait(0);
}

在这里博主想抛出一个问题,就是为什么与线程相关的wait,notify方法要定义在Object这个类中,而不是定义在Thread类,或其他类中,这里暂且不回答这个问题,在博主的另一篇博客里对这个问题做了说明:[java] Thread类详解

(12) finalize()

由垃圾收集器(garbage collector)判定一个对象没有引用(reference)的时候对一个对象调用。子类可以通过重写这个方法来处理系统资源,或者来执行清理操作

protected void finalize() throws Throwable { }

终结方法(finalizer)通常是不可预测的,也是很危险的,一般情况下是不必要的。

关于finalize方法总结:除非作为安全网,或者是为了终止非关键的本地资源,否则请不要使用终结方法。在这些很少见的情况下,既然使用了终结方法,就要记住调用super.finalize。如果用终结方法作为安全网,要记得记录终结方法的非法用法。最后,如果需要吧终结方法与公有的非final类关联起来,请考虑使用终结方法守护者,以确保即使子类的终结方法未能调用super.finalize,该终结方法也会被执行。