Java源码解析之object类
|
在源码的阅读过程中,可以了解别人实现某个功能的涉及思路,看看他们是怎么想,怎么做的。接下来,我们看看这篇Java源码解析之object的详细内容。 Java基类Object java.lang.Object,Java所有类的父类,在你编写一个类的时候,若无指定父类(没有显式extends一个父类)编译器(一般编译器完成该步骤)会默认的添加Object为该类的父类(可以将该类反编译看其字节码,不过貌似Java7自带的反编译javap现在看不到了)。 再说的详细点:假如类A,没有显式继承其他类,编译器会默认添加Object为其父类;若有,那么那个显式父类呢?要么是没有显式继承,那么Object是这个父类的父类,那肯定也是类A的父类,如果有,以此类推,所以,Object是Java所有类的祖先类(父类)。 声明 1.本系列是JDK1.7(oracle)的源码分析,若和你查看到的源码有差异,请对比JDK版本。 2.本系列是本人对Java源码的解析,但由于本人水平有限,势必不能做到完全解读,甚至只能说通过搜索阅读学习,做一些表面的解析,有不足之处,望指教,望谅解。 Object源码
public class Object {
//本地方法,C/C++在DLL中实现,通过JNI调用
private static native void registerNatives();
//类初始化调用此方法
static {
registerNatives();
}
//返回此Object的运行时类(每个类的Class类对象)
public final native Class<?> getClass();
//获得该对象的hash值
public native int hashCode();
//对比两对象的内存地址,如果不重写,equals方法比较的是对象地址
public boolean equals(Object obj) {
return (this == obj);
}
//本地clone方法,用于对象的赋值
protected native Object clone() throws CloneNotSupportedException;
//返回对象的的字符串表示,默认是:类名+@+hash值
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
//notify()/notifyAll()/wait()以及wait两个重载方法都是线程同步相关方法
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 { }
概述 因为Object是Java所有类的祖先类,所以Java所有类都有Object中的方法,在看这些方法的时候要联系这些方法不是针对Objec一个类,而是所有类。 既然是所有类共有,设计的时候肯定想的是所有类的共性,比如:equals方法就是用来比较任意两个相同类型对象是否相等的,toString是用来将任意对象转换成String,方便打印查看。当然,以上方法的实现都是默认的,想要实现自己的逻辑需要在自己类中覆盖重写。 以上的native方法,在Oracle的jdk是看不到的,但在OpenJDK或其他开源JDK是可以找到对应的C/C++代码的。 1.构造方法 源码中并没有Object的构造方法,但是,同样的,编译器在编译期间会给Object(事实上,所有的Java类,只要类中没有构造方法,编译器都会默认的给一个空构造方法,若已有构造方法,则不会添加)一个默认的空的构造方法:
public Object(){}
2.registerNatives 带有native修饰的都是本地方法,所谓的本地方法是不通过Java语言实现的方法,但可以通过JNI,像调用Java方法一样调用这些方法。详细的可以搜索查看JNI。 这个方法的作用是对Object以下几个本地方法(hashCode/clone/notify等)进行注册(可以理解为,这个方法是告诉JVM这几个本地方法的实现映射),每一个有本地方法的都会有这个方法,但其内容不一样(因为注册的方法不一样嘛)。 3.getClass 每一个类在被加载的时候,都会生成一个Class类实例,而这个方法就可以在运行时期获得对象(这里的对象是堆里的那个对象,也就是获得的是动态类型的那个类)的Class对象,Class对象主要用于反射。
class A{}
class B extends A{}
class C extends B{}
A a = new C();//对象new C()的静态类型是A,动态类型是C
B b = (B)a;//引用b指向的还是new C(),动态类型还是C
C c = (C)b;
System.out.println(a.getClass().getName());
System.out.println(b.getClass().getName());
System.out.println(c.getClass().getName());
//打印结果均是:com.xxx.test.C
//对象的动态类型是不会变的,即new后面那个类型(构造对象的那个类型),但是静态类
//型是由指向它的引用决定的,事实上可以这样理解对象只有动态类型,引用类型才是静态类型
//以上说的对象指的是堆里对象,而不是泛指Object o = new Object()中的o
//不明白静态类型,动态类型的可以自行百度
4.hashCode 获得该对象的hash值,Java虚拟机规范并没有规定这个方法的具体实现,只是规定了同一个对象两次调用(任何条件情形下)这个方法返回的int值要想等(但并没有规定两个不同对象hash值一定不相同),具体实现由各个JVM厂商自己实现,所以返回的值意义并不一定(这里特指Object的hashCode方法),有可能返回的是对象的内存地址,也有可能是某个特定计算公式计算出来的值。 5.equals 原则上或则说语义上,设计目的上,equals的作用意义,是用来比较两个对象是否相等,这里是我们通常理解的相等:即两个对象其内容是否相等,而不是程序上来看,两个对象是否是同一个对象,即比较其内存地址;如果想比较两个对象是否是同一个对象(这里是说两个引用是否指向同一个对象),直接用==比较即可(==比较的就是对象的内存地址)。但这里重要的是,对于Object来说,它并不能知道子类是如何判断他们的两个实例是如何equals的,所以,默认的equals实现,比较的是两对象内存地址,即,若子类不重写equals方法,其作用等同于==。
//如何重写equals方法实现判断内容相等?
//关键点取决于你的逻辑,你想让两个对象在什么时候相等,你逻辑上就怎么写
class A {
public int a;
public String b;
public D d;
@Override
public boolean equals(Object o) {
if (this == o) return true;//如果指向同一个对象,当然equals
//如果o为null或两个对象类型都不相同,当然不equals
if (o == null || getClass() != o.getClass()) return false;
//动态类型相同,强制转换
A a1 = (A) o;
/*下面是自己的逻辑,判断两个对象是否相同,逻辑1 Begin*/
if (a != a1.a) return false;
if (b != null ? !b.equals(a1.b) : a1.b != null) return false;
return d != null ? d.equals(a1.d) : a1.d == null;
//全部字段相同,则equals。如果对象越复杂,想要实现全部字段相同,也就越复杂
/* 逻辑1 End */
/* 逻辑2 begin*/
//只要字段a相同,就认为两个对象equals
if(a == a1.a) return true;
/* 逻辑2 end*/
}
@Override
public int hashCode() {
int result = a;
result = 31 * result + (b != null ? b.hashCode() : 0);
result = 31 * result + (d != null ? d.hashCode() : 0);
return result;
}
}
class D{
public int a;
}
网上说的,重写equals方法,必重写hashCode,其实不然,若确定所有地方都没有用到类似Map的地方,就不必重写hashCode,因为Map的诸多方法是有用到hashCode方法判断两对象是否相等,而若你仅仅是自己用来判断两个对象是否equals,也就不必重写hashCode(当然,还要确定其他地方不会用到hashCode的地方,比如,以后用,别人用等,不过一般的,推荐重写hashCode方法,这样保证任何地方都不会因此出错)。 若hash值不相等,则两个对象肯定不等(不equals); 若hash值相等,两个对象不一定相等(不一定equals)。 equals相等,hash值肯定想等,也就是说,hash值相等时equals相等的必要条件。 hashCode方法一般用来判断两个对象equals前置条件,用来排除,这样做的原因是,hashCode方法速度快,不相等的可快速否决掉,若hash相同,则再调用equals判断。 6.clone 克隆对象,克隆一个与原先对象所有字段值相等的对象,从而获得一个新的对象,需要注意的是: 想要使用这个方法,对象类型必须实现Cloneable接口,否则会报错,原因是Object的clone方法有对对象类型验证,如没实现则报错抛异常; clone方法返回的是一个新的对象,这个对象的创建不是通过new(除非你像下面那样不通过Object的clone方法重写)指令,而是JVM通过其他指令创建的; clone有深度clone和浅clone,这主要是针对类中间具有引用类型而言划分的,详情可参看:Java clone深度解析。
class A{}
A a = new A();
a.clone();//报错,即抛CloneNotSupportedException异常
class A implements Cloneable{}//这样才不会
//但,若你重写clone方法,并且在这个方法中没有调用父clone(也就是Object)方法
class A{
@Override
public Object clone() throws CloneNotSupportedException{
return new A();
}
}
a.clone();//这个时候调用clone方法即使没有实现Cloneable方法也不会报错
//说白了,你要理解为什么调用clone方法要实现Cloneable的原因,而不是仅仅是记住
//当你理解了,你就能熟练掌握这些规则,而不是记住他们
7.toString (编辑:安卓应用网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
