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
tostring这个方法算是object比较常用的方法了,它的意义是提供将类的字段以string形式格式化输出这一功能,当然,同样的,object不可能知道子类的字段信息,所以,默认tostring输出的是:全路径类名+@+hash值。
若你想要输出类的字段信息,需要重写tostring方法,将该类字段信息以你自己的格式输出。
8.notify/notifyall/wait
这三个方法适用于线程同步,这里只简单介绍其作用,详细请参考:notify/notifyall/wait。
notify:随机唤醒等待(wait)队列中一个对象,使其需要该对象的线程继续执行;
notifyall:唤醒队列中所有对象
wait:该对象陷入等待状态,需要该对象的线程将不能再继续执行,直到该对象由其他线程调用notify/notifyall方法唤醒。
9.finalize
在对象被gc(垃圾回收,详情可参考:java gc概述)之前被调用(jvm主动调用),你可以重写这个方法,然后在这个对象回收之前做某些动作,这个方法对于这个对象来说只能调用一次,为什么会这么说呢?对象都回收了,没了,难道不是当然只能调用一次?不是这样的,若你理解了java gc原理便知道,若当你在finalize方法中,将这个对象重新赋予了强引用,gc这个对象将失败,这个对象将继续存活,而下次这个对象又成为可回收对象了,gc回收这个对象的时候,这个对象的finalize方法将不会再执行。
另外,需要区分的是:
finalize不是c/c++中的析构函数,更不是释放内存的方法,它只是提供了在回收一个对象之前做某些操作,如果你熟悉c ,那你知道c 允许你为一个类定义一个撤消函数(destructor ),它在对象正好出作用域之前被调用。java不支持这个想法也不提供撤消函数。finalize() 方法只和撤消函数的功能接近。当你对java 有丰富经验时,你将看到因为java使用垃圾回收子系统,几乎没有必要使用撤消函数。
而且,在设计之初,这个方法就是为了兼容c/c++程序员习惯(对的,貌似就是这样),后来设计者也说,这是个失败的设计,所以,可以的话,在实践中忘掉这个方法吧。
class d{ public static d d111; @override protected void finalize() throws throwable { super.finalize(); d111 = this;//这个时候该对象第一次回收将失败,而以后将不会在执行该方法 system.out.println("finalize a = " + this.a); } } d d = new d(); d = null; //程序结束 //这个时候,虽然程序结束了,new d()对象也是可回收对象了,但是并不会执行 //finzlize,因为对于jvm来说gc的触发条件是内存不足,所以不会执行gc也就不会调用 //finzlize方法
总结
以上就是本文关于java源码解析之object类的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站:java.lang.void类源码解析、浅谈java多线程处理中future的妙用(附源码)等,有什么问题可以随时留言,小编会及时回复大家的。感谢朋友们对网站的支持!
上一篇: CSS 中关于字体处理效果的思考