深入理解Object类

众所周知 java 是一门面向对象的高级语言,除了基本类型之外,其他的所有类(包括枚举类 enum)都继承于基类,也就是 Object 类。所以深入了解 java 语言的类特征,Object 类的基本方法是非常有必要的。
下面请先看几个问题,来检测你对 Object 类了解多少。知不足,方可改之。

1. Object 类中有哪些方法

算上构造方法,Object 基类中共有13种方法

void registerNatives(); //私有方法 无须关心
// 获取当前对象的类型信息
native Class<?> getClass();
// native方法 一般自定义的类使用时需要重写
int hashCode();
// 判断两个对象是否相等 默认为 ==
// 一般重写hashCode也重写equals方法
boolean equals(Object obj);
// 对象拷贝方法 使用需重写
Object clone();
// print()打印时和字符串连接时会自动调用
String toString();
// 重写后第一次垃圾回收时执行的方法
void finalize();
// 接下来的方法都为同步方法
// 只有在synchronized同步条件下才能调用
void notify();    // 线程调度器唤醒一个线程
void notifyAll(); // 唤醒所有等待中的线程
// wait方法使当前线程等待,并释放同步锁
// 直到其他线程调用此对象的notify或notifyAll方法被唤醒
// 或者等待了指定时间后,停止等待,进入就绪状态
void wait(long timeout);
void wait(long timeout, int nanos);
void wait(); // 不指定时间,只能被其他线程唤醒

2. Object 类中哪些方法被标注为 native,是什么意思

众所周知,java 是运行在 JVM 上的语言,它拥有一次编写,到处执行,以及不用考虑底层处理的优势。
既然 java 语言本身无须考虑底层,那么自然就需要另一种语言,比如 c,c++,来完成对底层的处理。或者对于一些十分重视性能的方法,也需要一些更加贴合硬件的语言来编写。
因而,java 需要调用一些其它语言所书写的代码,Java Native Interface (JNI) 也就因此诞生了。
而 native,就是用来告知 JVM,该方法的调用在外部,让它根据本地方法执行。
而我们只需要关心这些方法的作用是什么,至于是如何实现的,我们则就无须关心了。
其中,registerNatives(),getClass(),hashCode(),clone(),notify(),notifyAll(),wait(),finalize(),都是 native 方法

void registerNatives();
native Class<?> getClass();
int hashCode();
Object clone();
void finalize();
void notify();
void notifyAll();
void wait(long timeout);
void wait(long timeout, int nanos);
void wait();

3. 如果不重写 equals 方法,那它的判定条件是什么

判定条件是 ==

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

4. 基类中原 hashCode 方法是如何实现的

是 native 方法,所以一般自定义类时,需要使用 hashCode() 方法,一般重写

5. 重写 hashCode 方法,是否需要重写 equals 方法,为什么

一般 hashCode() 方法是在 HashMap,HashSet,ConcurrentHashMap 等等集合中使用的,对于重复对象的判定方法是,既要 HashCode() 值相等,又需要 equals() 方法返回 true,所以重写 hashCode() 和 equals() 方法时,要保证 hashCode() 值相等时 equals() 方法返回 true,hashCode() 值不等时 equals() 方法返回 false。

6. 重写 equals() 方法需要遵循哪些规范

  1. 自反性:任何对象和其自身调用 equals() 方法一定返回 true。
    a.equals(a) 返回 true。
  2. 对称性:任意两个对象,互相调用 equals() 方法一定同为 true 或 false。
    a.equals(b) 返回 true,则b.equals(a) 返回 true。
    a.equals(b) 返回 false,则b.equals(a) 返回 false。
  3. 传递性:如果一个对象与其它两个对象调用 equals(方法) 返回 true,则那两个对象调用 equals() 方法也一定返回true。
    a.equals(b) 返回 true,a.equals© 返回 true,则 b.equals© 返回 true
  4. 一致性:对于任意两个对象,在自身没有改变的情况下,多次调用 equals() 方法的返回值必须相同
  5. 对于任意对象,其对 null 调用 equals() 方法返回值必须为 false。
// 举例说明 一般equals()方法这么重写
class Element {
	int attribute; // 对象属性 为方便描述例子没有写为private
	public boolean equals(Object obj) { // 参数类型为Object
		if(null == obj) // 规则5,必须返回false
			return false;
		if(this == obj) // 规则1,必须返回true
			return true;
		/**
		* 对于instanceof这种写法,一般子类不作方法重写
		* 这种情况子类可以当做它的父类进行equals比较
		* !!! 如果子类要进行重写,一般说明子类不希望与父类相等
		* 则父子都要用getClass()来进行类型判断
		*/
		if(obj instanceof Element) {
			// 在是同一种类时强转类型
			Element element = (Element) obj;
			// 然后一般比较对象属性
			if(this.attribute == element.attribute)
				return true;
			return false; // 属性不相同返回false
		}
		return false; // 不属于同一个类一般返回false
	}
}

7. 任意子类继承的 clone 方法能否被调用,为什么

不能,因为 clone() 方法是 protected 修饰的,只能被子类继承,外部一般无法调用

8. clone 方法是什么,如何使用

clone,顾名思义,就是克隆的意思,在 java 中就是复制对象的作用。
由于 clone() 方法默认可见性是 protected,外部无法调用,因此一般实现可复制对象需要
1、实现 Cloneable 接口,作为标志接口。
2、重写clone()方法,使可见性提升为public。
众所周知,java 中有不止一种创建对象的机制,new,反射创建,clone() 方法复制创建。
clone() 方法与 new 创建对象类似,都是先分配内存,之后在给属性赋值,只不过里面个属性的值是原对象中赋值过来的。
对于默认的拷贝方式,也就是浅拷贝,拷贝方式都是值传递。

class A implements Cloneable {
	public Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
}
// 赋值过程如同下面的代码
b.x = a.x; b.y = a.y; b.z = a.z;

我们知道,java 中基本类型使用值传递没有问题,但对于非基本类型的对象,保存对象的变量都是存储的对象引用,因而值传递时传递过去的都是对象的引用,所以对于对象中的非基本类型的对象属性,拷贝后的对象与原对象还是公用同一个对象。
举个小例子

public class A implements Cloneable {
	private String str = "I'm a string.";
	public Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
	public static void main(String[] ars) throws CloneNotSupportedException {
		A a1 = new A();
		A a2 = (A) a1.clone();
		System.out.println(a1.str == a2.str);
	}
}

输出 true,也就是说拷贝前后的两个 String 对象是同一个对象,内部 String 对象并没有被复制。
要实现深拷贝,则必须对 clone() 方法进行一定程度的重写。
可以首先调用 super.clone() 方法完成对基本类型的拷贝,然后对每个非基本类型对象也要进行一次 clone() 拷贝。

public class A implements Cloneable {
	private B b = new B();
	public Object clone() throws CloneNotSupportedException {
		A a = (A) super.clone();  //先浅拷贝一个对象
		// 再对每一个非基本类型对象也进行clone()拷贝
		a.b = (B) this.b.clone();
		return a;
	}
	public static void main(String[] ars) throws CloneNotSupportedException {
		A a1 = new A();
		A a2 = (A) a1.clone();
		System.out.println(a1.b == a2.b);
	}
}
class B implements Cloneable {
	public Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
}

输出 false。
由此我们看出拷贝出的对象中的非基本类型也属性也是属于不同的对象,从而完成深拷贝。
总结得出,如果要深拷贝,需要被复制对象的继承链引用链上的每一个对象都实现克隆机制。

9. toString 方法的作用

toString() 方法是用来描述一个对象的方法,它将返回一个字符串来描述对象信息,在执行 print() 方法或者与字符串相加时会自动调用该方法。
由于基类的 toString() 方法默认返回对象类名称和@内存地址,我们要这一串字符信息是没有作用的,所以一般需要对对象信息加以描述表示时,需要重写 toString() 方法,按照我们需要的表示方式进行表示。

10. wait 方法是什么

  • wait() 方法是同步块中才可以使用的方法,或者在同步方法中可以调用的方法。
    synchronized 关键字可以修饰方法,也可以修饰任意对象,也可以修饰类,在synchronized 代码块中,会锁定一个对象(或类,因为类是一种特殊的对象),只有当前线程能够操作代码块中的内容,其他线程会无法获取到对该对象的锁定,因而代码无法执行下去,直到该线程抢到该对象的锁,才能执行到代码。
  • 在 synchronized 代码块中,调用 wait() 方法,将会释放掉当前线程对该对象的锁,并进入等待池中等待,直到有一个线程调用 notify() 或 notifyAll() 方法唤醒后才会进入就绪状态,并进入锁池继续抢锁,抢锁成功后继续向下执行代码。
  • wait 方法有3种重载,一种是无参,一种是1个参数,一种是2个参数。其中 wait() 方法等同于 wait(0) 等同于 wait(0, 0) 方法,它们都表示等待的时间无限长,只有notify 方法才能够将其唤醒。另外两种传入的参数第一个数字表示毫秒,如果要传入第二个参数,则表示纳秒,线程可以被 notify 唤醒,也可以在等待指定时间之后自动唤醒,防止永久等待的情况发生。

11. notify 方法是什么

  • notify() 和 notifyAll() 也是同步块中才可以调用的方法,notify() 方法会根据线程调度唤醒一个线程进入锁池进行抢锁,至于唤醒哪一个线程,不是程序员能手动控制的,因此具有随机性。所以有些时候为了使特定线程能够执行,则需唤醒所有线程共同进入锁池抢锁。
  • notifyAll() 方法则会唤醒所有线程进入锁池进行抢锁执行,但是最终终究只有一个线程,或者这些线程中没有一个能够获取对该对象的同步锁,因此在并发量高的情况下,会对性能造成影响,如果没有对哪个线程有特殊的要求,可以使用 notify() 方法,只唤醒一个线程,并且自身释放锁,给该线程机会获取锁,则可以避免大量线程争抢的情况出现。

12. finalize 方法是什么

  • finalize() 方法是在GC(垃圾回收)时才有可能调用的代码。在要回收某个不可达对象之前,会先判断该对象的 finalize() 方法是否被重写,如果没有,则直接回收,如果被重写了,则会调用该方法。所以,这个方法可以实现在对象即将被垃圾回收时自救的作用,即在该方法中使自身被重新引用,避免被回收。但该 finalize() 方法只会被调用一次。在第一次GC时如果调用了该方法并成功自救,第二次GC时不会再调用该方法,该对象会被直接回收。
  • 但是,在 java 中,由于GC自动回收机制,不能保证 finalize() 方法的执行时间,也不能保证是否会被执行(自始至终未进行垃圾回收)。所以该方法具有不确定性,从程序开始执行到调用该方法之间的时间是任意不可控的。而且重写 finalize() 方法会延长垃圾回收时间,因而不推荐重写该方法。
  • 有些书籍或者博客会建议在 finalize() 方法中做资源的回收处理,这不是可取的,由于 finalize() 方法存在不确定性,不能保证资源的及时释放,甚至至始自终不释放。因此,我们通常需要手动调用 close() 方法释放资源
// 自救代码 仅做参考 不推荐在任何地方重写该方法
public class Test {
	static Test test;
	public void finalize() {
		test = this;
		System.out.println("我成功自救了");
	}
	public static void main(String[] ars) throws InterruptedException {
		test = new Test();
		test = null;
		System.gc();
		Thread.sleep(1000);// 等待gc执行完毕
		System.out.println(test);
		test = null;
		System.gc();
		Thread.sleep(1000);// 等待gc执行完毕
		System.out.println(test);
	}
}

我成功自救了
test.Test@15db9742
null

由此可见,在第一次GC时,该对象成功建立了静态变量 test 的引用,第二次GC时,将不会调用该方法,将被彻底回收。
Object 基类源码

package java.lang;
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 { }
}
发布了9 篇原创文章 · 获赞 123 · 访问量 4549

猜你喜欢

转载自blog.csdn.net/weixin_44051223/article/details/104096003