欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java中的Object类

程序员文章站 2022-04-04 23:51:31
...

一、Object类的概述

Java的Object是所有其他类的父类,从继承的层次来看它就是最顶层根类,所以它也是唯一一个没有父类的类。所有类都默认直接或间接继承Object类。
Java中的Object类

如上图所示,Object类定义了对象常用的一些方法,包括非final方法:equals,hashCode,toString,clone(访问限制级别:protected)和finalize(访问限制级别:protected)方法。这些方法是可以被子类重写的,且它们都有通用的约定,任何一个类,它在覆盖这些方法的时候,都有责任遵守这些通用的约定,如果不能做到这一点,其他依赖于这些约定的类就无法结合该类一起正常工作。除这些方法外,其他方法都是final修饰的,是不可以重写的。另外,除equals,toString和finalize方法外,其他方法都是native方法,即:其具体实现是在C(C++)动态库中,通过JNI(Java Native Interface)调用。native关键字要在方法的返回值类型之前。Java语言本身不能对操作系统底层进行访问与操作,但可以通过JNI接口来调用其他语言来实现对底层的访问,JNI已加入Java标准。

二、Object类的方法说明

1. 构造方法

Object类中没有显示的提供构造方法,这是编译器默认提供的。

2.registerNatives()方法

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

其主要作用是将C/C++中的方法映射到Java中的native方法,实现方法命名的解耦。函数的执行是在静态代码块中执行的,在类首次进行加载的时候执行。

3.getClass()方法

public final native Class<?> getClass();

getClass()也是一个native方法,返回的是此Object对象的类对象/运行时类对象Class<?>。效果与Object.class相同。
首先解释下”类对象”的概念:在Java中,类是是对具有一组相同特征或行为的实例的抽象并进行描述,对象则是此类所描述的特征或行为的具体实例。作为概念层次的类,其本身也具有某些共同的特性,如都具有类名称、由类加载器去加载,都具有包,具有父类,属性和方法等。于是,Java中有专门定义了一个类,Class,去描述其他类所具有的这些特性,因此,从此角度去看,类本身也都是属于Class类的对象。为与经常意义上的对象相区分,在此称之为”类对象”。

4.hashCode()方法

/**
 * Returns a hash code value for the object. This method is
 * supported for the benefit of hash tables such as those provided by
 * {@link java.util.HashMap}.
 * <p>
 * The general contract of {@code hashCode} is:
 * <ul>
 * <li>Whenever it is invoked on the same object more than once during
 *     an execution of a Java application, the {@code hashCode} method
 *     must consistently return the same integer, provided no information
 *     used in {@code equals} comparisons on the object is modified.
 *     This integer need not remain consistent from one execution of an
 *     application to another execution of the same application.
 * <li>If two objects are equal according to the {@code equals(Object)}
 *     method, then calling the {@code hashCode} method on each of
 *     the two objects must produce the same integer result.
 * <li>It is <em>not</em> required that if two objects are unequal
 *     according to the {@link java.lang.Object#equals(java.lang.Object)}
 *     method, then calling the {@code 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.
 * </ul>
 * <p>
 * As much as is reasonably practical, the hashCode method defined by
 * class {@code Object} does return distinct integers for distinct
 * objects. (This is typically implemented by converting the internal
 * address of the object into an integer, but this implementation
 * technique is not required by the
 * Java<font size="-2"><sup>TM</sup></font> programming language.)
 *
 * @return  a hash code value for this object.
 * @see     java.lang.Object#equals(java.lang.Object)
 * @see     java.lang.System#identityHashCode
 */
public native int hashCode();

hashCode()方法是一个本地native方法,返回的是对象引用中存储的对象的内存地址。注释给出了几点规定,大致意思是这样的:

  • 1.在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
  • 2.如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
  • 3.如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。

在实际使用中,要尽量保证对于不同的对象产生不同的哈希码。

5.equals(Object obj)方法

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

equals方法主要是比较两个对象是否相同,Object中的equals比较的是对象的内存地址(对象堆地址(引用存储的))。

6.clone() 方法

protected native Object clone() throws CloneNotSupportedException;

clone方法是创建并且返回一个对象的拷贝之后的结果,这实现了浅拷贝。关于浅拷贝和深拷贝:浅拷贝是指当拷贝对象内部中有引用类型的属性变量,在拷贝时候,只拷贝一份引用,拷贝的引用和原引用都指向原来的对象地址。

浅拷贝浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。
深拷贝深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。

7.toString()方法

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

可以看到Object中toString方法的实现是返回类的名称(全限定名称)加上@,然后 加上此类的哈希码的16进制表示

8.wait方法

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 >= 500000 || (nanos != 0 && timeout == 0)) {
        timeout++;
    }

    wait(timeout);
}

public final void wait() throws InterruptedException {
    wait(0);
}

可见wait()和wait(long timeout, int nanos)都在在内部调用了wait(long timeout)方法。 wait方法会引起当前线程阻塞,直到另外一个线程在对应的对象上调用notify或者notifyAll()方法,或者达到了方法参数中指定的时间。 调用wait方法的当前线程一定要拥有对象的监视器锁。 wait方法会把当前线程T放置在对应的object上的等到队列中,在这个对象上的所有同步请求都不会得到响应。线程调度将不会调用线程T,在以下四件事发生之前,线程T一直处于休眠状态。

  1. 当其他的线程在对应的对象上调用notify方法,而在此对象的对应的等待队列中将会任意选择一个线程进行唤醒。
  2. 其他的线程在此对象上调用了notifyAll方法
  3. 其他的线程调用了interrupt方法来中断线程T
  4. 等待的时间已经超过了wait中指定的时间。如果参数timeout的值为0,不是指真实的等待时间是0,而是线程等待直到被另外一个线程唤醒。被唤醒的线程T会被从对象的等待队列中移除并且重新能够被线程调度器调度。之后,线程T会像平常一样跟其他的线程竞争获取对象上的锁;一旦线程T获得了此对象上的锁,那么在此对象上的所有同步请求都会恢复到之前的状态,也就是恢复到wait被调用的情况下。然后线程T从wait方法的调用中返回。因此,当从wait方法返回时,对象的状态以及线程T的状态跟wait方法被调用的时候一样。线程在没有被唤醒,中断或者时间耗尽的情况下仍然能够被唤醒,这叫做伪唤醒。虽然在实际中,这种情况很少发生,但是程序一定要测试这个能够唤醒线程的条件,并且在条件不满足时,线程继续等待。换言之,wait操作总是出现在循环中,就像下面这样:
synchronized(对象){
    while(条件不满足){
     对象.wait();
  }
  对应的逻辑处理
}

线程被其他的线程在当前线程等待之前或者正在等待时调用了interrupt()中断了,那么会抛出InterruptedExcaption异常。直到这个对象上面的锁状态恢复到上面描述的状态以前,这个异常是不会抛出的。 要注意的是,wait方法把当前线程放置到这个对象的等待队列中,解锁也仅仅是在这个对象上;当前线程在其他对象上面上的锁在当前线程等待的过程中仍然持有其他对象的锁。

wait(long timeout, int nanos)方法的实现中只要nanos大于0,那么timeout时间就加上一毫秒,主要是更精确的控制时间,其他的跟wait(long timeout)一样。

wait方法和Thread.sleep方法的区别:Thread.sleep不会导致锁行为的改变,如果当前线程是拥有锁的,那么Thread.sleep不会让线程释放锁。

9.notify()方法

public final native void notify();

通知可能等待该对象的对象锁的其他线程。由JVM(与优先级无关)随机挑选一个处于wait状态的线程。

  • 在调用notify()之前,线程必须获得该对象的对象级别锁
  • 执行完notify()方法后,不会马上释放锁,要直到退出synchronized代码块,当前线程才会释放锁
  • notify()一次只随机通知一个线程进行唤醒

10.notifyAll()方法

public final native void notifyAll();

和notify()差不多,只不过是使所有正在等待池中等待同一共享资源的全部线程从等待状态退出,进入可运行状态,让它们竞争对象的锁,只有获得锁的线程才能进入就绪状态 。
每个锁对象有两个队列:就绪队列和阻塞队列

  • 就绪队列:存储将要获得锁的线程
  • 阻塞队列:存储被阻塞的线程

11.finalize()方法

protected void finalize() throws Throwable { }

子类可重写此方法,以实现非内存资源的清理。此方法在GC回收给对象之前,会自动调用此方法。如果用户显示地调用此方法,代表普通方法调用,与对象的回收销毁无关。
一般不建议使用此方法来进行释放非内存资源,它不同于C++的析构函数,析构函数在对象作用域调用,执行时间点确定。而此方法,是在内存不足,GC发生时进行调用,由于GC是不确定随机的,所以无法确定此方法的执行时间。

三、方法的约定

尽管Objcet类是一个具体类,但是设计它主要为了扩展。它所有的非final方法(equals,hashCode,toString,clone,finalize)都有明确的通用约定,下边我们就详细解释一下。

1、覆盖equals时遵循的通用约定

若equals方法覆盖不当时,会产生十分严重的后果。当不覆盖equals方法时,类的每个实例只与它的自身相等。如果类具有自己特有的“逻辑相等”概念,而且超类还没有覆盖equals以实现期望的行为,这时我们就需要覆盖equals方法。这通常属于“值类”,当利用equals方法比较值对象的引用时,希望知道他们在逻辑上是否相等,这需要覆盖equals方法。equals方法覆盖时,需要遵守的通用规定如下,来自JDK1.8 equlas 方法的注释:

 /**
  * Indicates whether some other object is "equal to" this one.
  * <p>
  * The {@code equals} method implements an equivalence relation
  * on non-null object references:
  * <ul>
  * <li>It is <i>reflexive</i>: for any non-null reference value
  *     {@code x}, {@code x.equals(x)} should return
  *     {@code true}.
  * <li>It is <i>symmetric</i>: for any non-null reference values
  *     {@code x} and {@code y}, {@code x.equals(y)}
  *     should return {@code true} if and only if
  *     {@code y.equals(x)} returns {@code true}.
  * <li>It is <i>transitive</i>: for any non-null reference values
  *     {@code x}, {@code y}, and {@code z}, if
  *     {@code x.equals(y)} returns {@code true} and
  *     {@code y.equals(z)} returns {@code true}, then
  *     {@code x.equals(z)} should return {@code true}.
  * <li>It is <i>consistent</i>: for any non-null reference values
  *     {@code x} and {@code y}, multiple invocations of
  *     {@code x.equals(y)} consistently return {@code true}
  *     or consistently return {@code false}, provided no
  *     information used in {@code equals} comparisons on the
  *     objects is modified.
  * <li>For any non-null reference value {@code x},
  *     {@code x.equals(null)} should return {@code false}.
  * </ul>
  * <p>
  * The {@code equals} method for class {@code Object} implements
  * the most discriminating possible equivalence relation on objects;
  * that is, for any non-null reference values {@code x} and
  * {@code y}, this method returns {@code true} if and only
  * if {@code x} and {@code y} refer to the same object
  * ({@code x == y} has the value {@code true}).
  * <p>
  * Note that it is generally necessary to override the {@code hashCode}
  * method whenever this method is overridden, so as to maintain the
  * general contract for the {@code hashCode} method, which states
  * that equal objects must have equal hash codes.
  *
  * @param   obj   the reference object with which to compare.
  * @return  {@code true} if this object is the same as the obj
  *          argument; {@code false} otherwise.
  * @see     #hashCode()
  * @see     java.util.HashMap
  */

总结一下就是需要保证:自反性,对称性,传递性和一致性。必须遵守,有许多类,包括所有的集合类,都依赖传递给他们的对象是否遵守了equals约定。

2、hashCode方法约定
在每个覆盖了equals方法的类中,也必须覆盖hashCode方法。如果不这样做的话,就会违反Object.hashCode的通用规定,从而导致该类无法结合所有基于散列的集合一起正常运作。我们可直接看JDK1.8 的hashCode的注释代码:

/**
 * Returns a hash code value for the object. This method is
 * supported for the benefit of hash tables such as those provided by
 * {@link java.util.HashMap}.
 * <p>
 * The general contract of {@code hashCode} is:
 * <ul>
 * <li>Whenever it is invoked on the same object more than once during
 *     an execution of a Java application, the {@code hashCode} method
 *     must consistently return the same integer, provided no information
 *     used in {@code equals} comparisons on the object is modified.
 *     This integer need not remain consistent from one execution of an
 *     application to another execution of the same application.
 * <li>If two objects are equal according to the {@code equals(Object)}
 *     method, then calling the {@code hashCode} method on each of
 *     the two objects must produce the same integer result.
 * <li>It is <em>not</em> required that if two objects are unequal
 *     according to the {@link java.lang.Object#equals(java.lang.Object)}
 *     method, then calling the {@code 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.
 * </ul>
 * <p>
 * As much as is reasonably practical, the hashCode method defined by
 * class {@code Object} does return distinct integers for distinct
 * objects. (This is typically implemented by converting the internal
 * address of the object into an integer, but this implementation
 * technique is not required by the
 * Java&trade; programming language.)
 *
 * @return  a hash code value for this object.
 * @see     java.lang.Object#equals(java.lang.Object)
 * @see     java.lang.System#identityHashCode
 */

3、始终覆盖toString

虽然Objcet类中提供了toString方法的一个实现,但是它返回的并不是友好的。toString的通用约定指出,被返回的字符串应该是一个“简洁的、信息丰富并且易于阅读的表达形式”。toString约定建议所有的子类都覆盖这个方法。当对象被传递给println、print、字符串+操作和调试器打印时,toString会被自动调用。

4、谨慎覆盖clone

要使用clone方法,该类需要实现Cloneable接口(标志接口,与Serializable接口类似),它决定了受保护的clone方法实现的行为,如果一个类实现了Cloneable,Object的clone方法就返回该对象的拷贝,否则会抛出CloneNotSupportException异常。而且在重写clone方法时,需用:super.clone();来实现。如果类的所有超类都遵守这个规则,那么调用clone方法最终会调用到Object类的clone方法。也可以实现深拷贝。clone是实现原型设计模式的方法。 重写时,可放大访问限制符权限,不可缩小。

5、避免使用finalize方法

finalize方法通常是不可预测的,会导致不稳定、降低性能、以及可移植性问题。finalize方法的缺点是在于不能保证会被及时地执行,从一个对象变得不可达开始,到它的终结方法被执行,所花费的时间是任意长的,这意味着:注重时间的任务不应该由finalize方法来完成。比如:用finalize方法来关闭已打开的文件,这是严重的错误,因为打开文件描述符是一种很有限的资源,由于JVM进行GC的时间不确定性,所以finalize方法调用也不确定,所以导致大量的文件保留在打开状态。

相关标签: Object jdk