对Object类中方法的深入理解

时间:2023-03-09 16:28:35
对Object类中方法的深入理解

看一下API中关于Object的介绍:

类 Object 是类层次结构的根类。每个类都使用 Object 作为超类。
所有对象(包括数组)都实现这个类的方法。

那么Object中到底有哪些方法,各自有什么应用呢?
这个问题也经常出现在面试中,如果平时没有关注,可能很难回答好,这里简单整理一下。

首先看一下java.lang.Object的源码:

public class Object {
private static native void registerNatives();
static {
registerNatives();
}
/**
* 创建并返回此对象的副本。
* “复制”的准确含义可能根据对象的不同有变化。
*/
public final native Class<?> getClass(); /**
* 1.在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,
* 前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
* 2.如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
* 3.如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode方法不 要求一定生成不同的整数结果
* 但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。
*/
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 >= 500000 || (nanos != 0 && timeout == 0)) {
timeout++;
}
wait(timeout);
}
public final void wait() throws InterruptedException {
wait(0);
}
/**
* 当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法
*/
protected void finalize() throws Throwable { }
}

源码的注释非常详细,现在针对这几个方法做分析。

>>hashCode()和equals(Object obj)方法

public native int hashCode();

native关键字表示该方法不是用java实现的,JDK源码中并不包含,对于不同的平台,这部分的实现也是不同的。

Java要实现对底层的控制,就需要其他语言的帮助,JVM将控制调用本地方法的所有细节。

hashCode方法的说明是:

Whenever it is invoked on the same object more than once during an execution of a Java application,the hashCode method must consistently return the same integer.

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.

the programmer should be aware that producing distinct integer results
for unequal objects may improve the performance of hash tables.
开发人员应该知道,为不相等的对象产生不同的整数结果可能会提高哈希表的性能。
(这句没理解,再看)

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 programming language.
hashcode一般是通过将该对象的内部地址转换成一个整数来实现的,不需要使用Java实现。

再来看equals()方法:

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

java中==运算符作用在基本数据类型和引用数据类型是不同的,
基本数据类型 byte, short, char, int, long, float, double, boolean
他们之间的比较,应用双等号(==),比较的是他们的值。

引用数据类型用(==)进行比较的时候,
比较的是在内存中的存放地址,所以,除非是实例化的同一个对象比较后的结果才为true,否则比较后结果为false。
注意一下,String,Integer,Date这些类重写了equals()方法,不再是比较类在堆内存中的存放地址了。

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

可以看到String对内部的字符数组进行了逐个字符的判断是否相等,

《Effective JAVA》中认为,99%的情况下,当你覆盖了equals方法后,请务必覆盖hashCode方法。

重写hashcode()方法:

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 方法,以维护在hashCode 方法最开始的声明,即相等对象必须具有相等的哈希码。

1.notify()/notifyAll()和wait()

wait()的作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。
而notify()和notifyAll()的作用,则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。

wait(),notify() 和 notifyAll() 可以让线程协调完成一项任务。例如,一个线程生产,另一线程消费。生产线程不能在前一产品被消费之前运行,而应该等待前一个被生产出来的产品被消费之后才被唤醒,进行生产。同理,消费线程也不能在生产线程之前运行,即不能消费不存在的产品,所以应该等待生产线程执行一个之后才执行。利用这些方法,就可以实现这些线程之间的协调。

来看一个例子:

public class WTThread extends Thread{
public WTThread(String name) {
super(name);
} public void run() {
//同步锁
synchronized (this) {
System.out.println(Thread.currentThread().getName()+" call notify()");
/**
* 这个线程执行的过程中会去唤醒当前对象的wait线程
*/
notify();
}
}
}

 主线程的执行方法:

public class UseWaitNotify {

	public static void main(String[] args) throws InterruptedException{
WTThread t1=new WTThread("t1");
//同步锁
synchronized(t1) {
//启动线程t1
System.out.println(Thread.currentThread().getName()+" start t1");
t1.start(); /**
* 主线程进入等待状态
* 同时wait()释放主线程持有的同步锁
*/
System.out.println(Thread.currentThread().getName()+" wait()");
t1.wait();
/**
* 主线程等待后,释放了锁,t1线程就可以执行了
* 于是会操作 下面的执行体
* System.out.println(Thread.currentThread().getName()+" call notify()");
* notify();
*/
/**
* t1执行完毕后,唤醒主线程,主线程持有锁,继续执行
* 打印查看目前正在执行的线程
*/
System.out.println(Thread.currentThread().getName()+" continue");
}
}
}

  控制台输出:

main start t1
main wait()
t1 call notify()
main continue

这段代码来自这篇文章,Java多线程系列--“基础篇”05之 线程等待与唤醒

这几个方法也都是native的,是调用的系统底层方法实现。

2.不同的wait()方法有什么区别

查看源码可以发现这里有三个不同wait方法:

public final native void wait(long timeout) throws InterruptedException;
public final void wait(long timeout, int nanos) throws InterruptedException {}
public final void wait() throws InterruptedException {
wait(0);
}

没有参数的 wait() 方法被调用之后,线程就会一直处于等待状态,直到本对象(就是 wait() 被调用的那个对象)调用 notify() 或 notifyAll() 方法。
相应的,wait(long timeout) 和wait(long timeout, int nanos) 方法中,
当等待时间结束或者被唤醒(无论哪一个先发生)时将会结束等待。

3.finalize()方法

/**
* Called by the garbage collector on an object when garbage collection
* determines that there are no more references to the object.
*/
protected void finalize() throws Throwable { }

可以看到这个方法是protected的。

特殊情况下,需要程序员实现finalize,当对象被回收的时候释放一些资源,比如:一个socket链接,在对象初始化时创建,整个生命周期内有效,那么就需要实现finalize,关闭这个链接。
使用finalize还需要注意一个事,调用super.finalize();
一个对象的finalize()方法只会被调用一次,而且finalize()被调用不意味着gc会立即回收该对象,所以有可能调用finalize()后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会调用finalize(),产生问题。
一般来说,不推荐使用finalize()方法,它跟析构函数不一样。

4.getClass()方法

/**
* Returns the runtime class of this Object.
* The returned Class object is the object that is locked by
* "static""synchronized" methods of the represented class.
*/
public final native Class<?> getClass();

返回这个Object对象的运行时类。

5.clone()方法

clone方法就是复制对象。所谓的复制对象,就是要分配一个和源对象同样大小的空间,在这个空间中创建一个新的对象。
这里涉及到浅拷贝和深拷贝的关系:

Person p = new Person(23, "zhang");
Person p1 = p;

这段代码中,P和P1其实是引用,指向同一个对象。

Person p = new Person(23, "zhang");
Person p1 = (Person) p.clone();

但是这段代码是创建了两个相同的对象,P和P1的引用分别指向不同的对象,

但是Clone执行的方法是浅拷贝,需要注意这个,P和P1里的name字段指向的是同一块堆内存。

对Object类中方法的深入理解

具体的可以查看这篇文章:
详解Java中的clone方法

6.toString()方法

返回对象表示的字符串,实际上我们应用的都是对这个类的重写。