初探Object

来源:互联网 发布:父母之爱 知乎 编辑:程序博客网 时间:2024/06/07 15:40

简介

Object,所有类的最终父类。这两天沉下心认真看了下jdk8源码,本文是个人对Object方法的认识与理解,主要是对源码注释的翻译、网上资料的整合以及个人有限的经验。

方法

1、registerNatives
先上代码
    private static native void registerNatives();    static {        registerNatives();    }
可以看到,除了声明外,该方法在类加载时就被调用。那么,它具体执行了什么操作呢?
在Object类中,存在一些方法,它们被native修饰,调用它们实际是调用用其他语言编写的代码。那么,JVM是怎么确定哪个对哪个呢?
这得先说到一个标准,JNI(Java Native Interface),它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。具体待深入研究,大概意思是说,其他语言编写的代码,通过一系列编译链接操作后,要被JVM正确调用,有两种方法:
a.方法名按一定规则与Java的方法名对应
比如java.lang.Object.registerNatives()和getClass()这两个方法,对应的C语言实现如下。名字有规律。
JNIEXPORT void JNICALLJava_java_lang_Object_registerNatives(JNIEnv *env, jclass cls){    (*env)->RegisterNatives(env, cls,                            methods, sizeof(methods)/sizeof(methods[0]));}JNIEXPORT jclass JNICALLJava_java_lang_Object_getClass(JNIEnv *env, jobject this){    if (this == NULL) {        JNU_ThrowNullPointerException(env, NULL);        return 0;    } else {        return (*env)->GetObjectClass(env, this);    }}
b.注册
如果嫌第一种方法过于麻烦,可以通过注册的方法进行关联对应。比如java.lang.Object类有两个方法(registerNatives和getClass)是用第一种的,其他都是通过registerNatives()进行注册。这里补充一个上面用到的methods变量的定义,具体代码可以看OpenJDK给出的源码,然而我还是没找到methods变量里列出的方法的实现
static JNINativeMethod methods[] = {    {"hashCode",    "()I",                    (void *)&JVM_IHashCode},    {"wait",        "(J)V",                   (void *)&JVM_MonitorWait},    {"notify",      "()V",                    (void *)&JVM_MonitorNotify},    {"notifyAll",   "()V",                    (void *)&JVM_MonitorNotifyAll},    {"clone",       "()Ljava/lang/Object;",   (void *)&JVM_Clone},};

2、getClass
返回当前对象的运行时类对象,无需类型转换。如下,无需cla = (Class<? extends Number>) n.getClass();
        Number n = 0;        Class<? extends Number> cla = n.getClass();        System.out.println(cla.getName());
输出
java.lang.Integer

补充,今天看到一个有趣的题目,如下:
public class FatherClass {}public class SubClass extends FatherClass{    public void test01(){        System.out.println(super.getClass().getName());    }    public static void main(String[] args){        new SubClass().test01();    }}
输出是SubClass



3、hashCode
返回当前对象对应的哈希值。主要用于支持如HashMap等类。
在Object的实现中,保证不同对象返回的值不同。
一般的,如果需要作为HashMap的key或者存储于HashSet等基于hashCode值的集合中时,必须覆盖这个方法。覆盖的约定如下:
a.同一个对象,在未改变其属性值的情况下,多次调用hashCode方法得到的结果是一致的,但这与equals()无关;
b.调用equals得到比较结果为true的两个对象,其hashCode也一定相等。
c.如果调用equals得到的比较结果为false的两个对象,其hashCode可能相等。
bc总结为一句话,hashCode不等的,equals一定不等;equals相等的,hashCode一定相等。

4、equals
用于判断两个对象是否相等,覆盖时注意要满足五条约定:
a.自反性:任何一个非null对象,满足:obj.equals(obj)==true;
b.对称性:对非null对象x,如果有x.equals(y)==true,那么y一定是非null的,且有y.equals(x)==ture;
c.传递性:对于非null对象x, y,如果有x.equals(y)==true  y.equals(z)==true,那么一定有x.equals(z)==true;
d.一致性:对两个非null对象x, y,在没有修改需要比较的属性的前提下,多次调用equals的返回值不变;
e.对任何非null对象,满足obj.equals(null)==false。
在Object中的实现是,当且仅当两个引用指向同一个对象时,才返回true。
需要注意,在覆盖equals方法后,通常也需要覆盖hashCode,以保证满足hashCode的b约定。

5、clone
用于创建一个与当前对象属性一致的拷贝。约定如下:
a.x.clone()!=x。
b.x.clone().getClass() == x.getClass()。非必须。
c.x.clone().equals(x)。非必须。
以上约定中,bc是否满足取决于你覆盖的clone(),一般来说,clone()中返回的对象都是通过super.clone()获得,这样就满足b。而是否满足c还要看你的equals()的实现。
如果要实现深克隆,还需要调用该对象的每个需要的引用对象的clone()。
如果该对象的类没有实现Cloneable,则会抛出CloneNotSupportedException,需要注意的是数组自动实现了Cloneable(浅层复制)。
继承链上的某个类没有实现Cloneable,对其子类是否Cloneable没有影响,如下,GrandFather、Son调用clone()无异常。特别的,Object就没有实现Cloneable。
class GrandFather implements Cloneable{}class Father extends GrandFather{}class Son extends Father implements Cloneable{}

6、toString
返回这个对象的字符串表示,返回的字符串应该简洁而准确。
在Object中的实现如源码,返回"当前对象的运行时类名@hashCode()的十六进制数"
    public String toString() {        return getClass().getName() + "@" + Integer.toHexString(hashCode());    }

7、notify
唤醒一个等待于该对象的线程(即调用了该对象的某个wait()的线程),如果有多个,则随机唤醒其中一个。
被唤醒的线程需要与其他线程争夺基于该对象的锁,争夺成功后才能在调用wait()的地方开始继续执行。
只有那些持有该对象的锁后的线程才能调用这个方法,否则抛出IllegalMonitorStateException。有三种情况让一个线程持有一个对象的锁:
a.调用被synchronized修饰的static方法,此时,锁是x.getClass()。
b.调用被synchronized修饰的实例方法,此时,锁是x。
c.调用一个方法,这个方法中某一块代码被synchronized包裹,此时,锁是synchronized中指定的对象。

8、notifyAll
与notify类似,不过这个方法会唤醒所有等待于该对象的线程。

9、wait
wait有三个重载的方法,分别是wait(); wait(long); wait(long, int);。基本大同小异,放在这里一起说。
让当前线程等待在基于该对象的集合中,直到其他线程调用了这个对象的notify()或notifyAll()后才有机会被唤醒。
线程调用这个方法前必须持有该对象的锁,否则抛出IllegalMonitorStateException。
在调用wait()方法后,当前线程会释放该对象的锁。在被唤醒后,再次获得该对象的锁后才会继续执行(从调用wait()处开始)
因为中断与虚假唤醒是很有可能存在的,所以代码最好这样写
synchronized(obj){    while(condition){        obj.wait();    }    //do something}
其中,
wait()的唤醒途径有:notify()、notifyAll()、以及被中断;
wait(long)比wait()多了一个限定时间,即除了wait()的三种途径外,在达到给定参数ms时自动唤醒(如果参数为0,表示不限时);
wait(long, int)比wait(long)控制的更精确。(然而并没有什么感觉。。。)。
下面给出代码
    public final native void wait(long timeout) throws InterruptedException;    public final void wait() throws InterruptedException {        wait(0);    }    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);    }

10、finalize
这是与垃圾回收机制相关的一个方法。按约定,只有jvm可以调用它。
在垃圾回收时,如果这个对象没有被别的对象引用,那么它占用的空间就该被回收。在回收前,垃圾回收器会先调用它的finalize方法。
你可以在finalize中做任何操作,比如
a.让另一个对象引用这个对象,以免被回收;
b.释放系统资源及其他资源清理操作;
c.在《Java编程思想》中提到,清理由其他语言申请占用的内存、验证对象可清除。
在Object中的实现是空实现。
在你覆盖这个方法之前,有几个地方必须注意
a.该方法不保证被调用。比如在进程终止时,一定会有许多对象未被垃圾回收,自然,它们的finalize()也未被调用。
b.调用finalize()的线程不会持有任何用户可见的同步锁。但是如果你在finalize设置了同步锁,就跟普通代码一样处理(即同一时间只有一个线程能持有同步锁,其他线程阻塞)。
c.不会处理finalize()抛出的异常。
d.垃圾回收器在回收一个对象时,在这个周期调用待回收对象的finalize(),等下个周期再回收这个对象(因为finalize()可能会造成该对象再次被引用,所以下个周期先判断是否可以回收,然后再回收这个对象)。
e.与d对应,垃圾回收器会且仅会调用一次这个对象的finalize()。


参考
JNI-百度百科
OpenJDK:Object的native代码
0 0