Java源码阅读------Object
描述
java中一切事物几乎都可以看作类的实例化对象,而所有的类都与Object息息相关,Object类是所有类的父类先祖,甚至是数组arrays都是要实现Object类中定义的方法,如果一个类没有别的继承声明那么这个类一定是继承了Object类。
相关实现
常用函数
equals
这是比较常用的一个方法,在源码中的实现也很简单。
public boolean equals(Object obj) {
return (this == obj);
}
通过传入的引用和自身比对,如果是对同一个对象的引用返回为真。这里引用是指实例化对象在堆中的地址。
toString
这个函数也是比较常用的,是用于对对象进行文字性描述的方法。我们在使用时一般是重载这个方法。
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
在Object中的直接使用时,返回的是对象运行时的类名与hashCode()获取的表示内存地址信息的值。比如
Object o=new Object();
System.out.println(o.toString());
//输出java.lang.Object@6b884d57
native方法
在java中有很多的方法不是完全使用java实现的,有些对系统底层的调用还需要使用C语言来实现,对这些函数的声明是用native来声明的。
registerNatives
Object类中也有许多的native方法,这些方法在初始化的时候需要将它们与对应的C语言方法对应,而实现这个过程的方法是registerNatives。
private static native void registerNatives();
static {
registerNatives();
}
在类中对这一方法进行了初始的静态调用,对本地的方法进行注册,在其他类中也用类似的使用,比如System。
再往深一点看看,就是jni的实现了
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},
};
这里是将java中的函数与在C语言中的函数连接,左边是java中的函数名,右边是在C语言中的函数名,中间的像“()I”中的“I”是jni中对数据类型的转换,V是void,I是int,Object对应是Ljava/lang/Object。还有一点是其中并没有getClass这一函数,但是在调用时还是会按这个名称Java_java_lang_Object_getClass调用。
再看看详细的实现
JNIEXPORT void JNICALL
Java_java_lang_Object_registerNatives(JNIEnv *env, jclass cls)
{
(*env)->RegisterNatives(env, cls, methods, sizeof(methods)/sizeof(methods[0]));
}
//将本地的几个方法methods注册。
hashCode
public native int hashCode();
使用这一函数对不同的对象进行区分(为不同的对象返回不同的整数(原文)),一般由对象实例化后在堆中的地址转换为整数实现,不是由java自己实现(native)。
除此之外,该函数为有关哈希表的实现提供支持,比如java.util.HashMap。
这个函数可以与equals函数呼应,对于equals不相等的函数,使用hashCode得到的是不等的值。(对同一对象的引用,返回的是相等的地址信息,对不同对象的引用返回的是不同的)
clone
protected native Object clone() throws CloneNotSupportedException;
这一方法实现了对于已有对象的克隆,但是需要实现Cloneable接口,而且是浅拷贝,具体可以参照Java技巧------Cloneable接口与clone方法。
并发与同步
synchronized大家应该不陌生,我们通过它来同步某个对象或是代码块,我们称之为锁,锁是干啥的呢?事实上在多线程中往往会出现多个线程对同一对象进行修改,但是我们无法实时更新变量的实际状况,大多数时候在我们刷新数据时,这个数据还在进行变化,最常见的就是买票这一类,因此某一数据在任意时刻仅允许一个线程对其进行更改即拿到了锁(拿到了就锁起来只能自己改),没有拿到的线程就无法进行操作即所谓得阻塞式访问。
wait(long)
这个方法有三种种形式,这个是基本的本地实现。
public final native void wait(long timeout) throws InterruptedException;
首先,调用这一方法的类已经被线程获取到了锁,正在进行相关的逻辑操作,但是由于设计原因,比如异步操作,一些逻辑需要等待一段时间进行时使用这一方法,使用时正在使用的线程放弃使用解锁等待并让出使用权,直到别的线程调用该类的notify或notifyAll方法或者超过了timeout设置的时间,亦或者其他的线程使用了interrupt()方法,再重新获得锁。参数是指超时时间,如果超时时间为0,则只能等着其他线程通知了。
wait(long, int)
这个函数用于更好的去控制超时时间,第二个参数为毫秒量。
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);
}
wait()
特殊情况,超时时间为0。
public final void wait() throws InterruptedException {
wait(0);
}
notify
通过这一函数来通知等待操作的其他线程,如果有多个线程在等待就选取其中一个。之后,等到正在操作的对象放弃使用权解锁时,该线程运行并获得锁。
public final native void notify();
notifyAll
通过这一函数来通知等待操作的其他线程,之后,等到正在操作的对象放弃使用权解锁时,这些线程运行。
public final native void notifyAll();
强调
所谓唤醒与获得锁是两个概念,不是说notify就直接可以获得锁并执行,那样notifyAll会存在多个线程同时获得锁。
被wait的线程要先被唤醒,之后才能参与锁的争夺。
所以大多数使用是将wait写到循环里,防止判断条件被唤醒的线程打破。
反射
java中的反射,即指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。
getClass
public final native Class<?> getClass();
返回这一对象运行时的类Class(这里是指Class这个类)的实例。简单的举个例子。
Object o=new Object();
Object s= "123";
System.out.println(o.getClass().getName());
System.out.println(s.getClass().getName());
//输出
//java.lang.Object
//java.lang.String
返回的是运行时的类的实例,所以第二个是String,不是Object。
finalize
该方法不是什么理解上的析构函数,gc在回收对象之前会调用该函数。我们可以重写该函数实现对象回收前的资源清理。
protected void finalize() throws Throwable { }
Java无法保证finalize的及时执行,甚至不保证其执行,只是在回收之前调用,除此之外该方法由优先级较低的线程执行,gc至多自动处理一次。
小结
Object类还是很重要的,短短的几百行代码(含注释)中蕴含着许多的设计思想,作为众多类的祖先,其中的相关特性值得我们去深究。