配色: 字号:
Java源码解析
2016-09-28 | 阅:  转:  |  分享 
  
Java源码解析

一Object

Java基类Object



Java.lang.Object,Java所有类的父类,在你编写一个类的时候,若无指定父类(没有显式extends一个父类)编译器(一般编译器完成该步骤)会默认的添加Object为该类的父类(可以将该类反编译看其字节码,不过貌似Java7自带的反编译javap现在看不到了)。

再说的详细点:假如类A,没有显式继承其他类,编译器会默认添加Object为其父类;若有,那么那个显式父类呢?要么是没有显式继承,那么Object是这个父类的父类,那肯定也是类A的父类,如果有,以此类推,所以,Object是Java所有类的祖先类(父类)。



声明



1.本系列是JDK1.7的源码分析,若和你查看到的源码有差异,请对比JDK版本。

2.本系列是本人对Java源码的解析,但由于本人水平有限,势必不能做到完全解读,甚至只能说通过搜索阅读学习,做一些表面的解析,有不足之处,望指教,望谅解。



Object源码



publicclassObject{

//本地方法,C/C++在DLL中实现,通过JNI调用

privatestaticnativevoidregisterNatives();

//类初始化调用此方法

static{

registerNatives();

}

//返回此Object的运行时类(每个类的Class类对象)

publicfinalnativeClassgetClass();

//获得该对象的hash值

publicnativeinthashCode();

//对比两对象的内存地址,如果不重写,equals方法比较的是对象地址

publicbooleanequals(Objectobj){

return(this==obj);

}

//本地clone方法,用于对象的赋值

protectednativeObjectclone()throwsCloneNotSupportedException;

//返回对象的的字符串表示,默认是:类名+@+hash值

publicStringtoString(){

returngetClass().getName()+"@"+Integer.toHexString(hashCode());

//notify()/notifyAll()/wait()以及wait两个重载方法都是线程同步相关方法

publicfinalnativevoidnotify();

publicfinalnativevoidnotifyAll();

publicfinalnativevoidwait(longtimeout)throwsInterruptedException;

publicfinalvoidwait(longtimeout,intnanos)throwsInterruptedException{

if(timeout<0){

thrownewIllegalArgumentException("timeoutvalueisnegative");

}



if(nanos<0||nanos>999999){

thrownewIllegalArgumentException(

"nanosecondtimeoutvalueoutofrange");

}



if(nanos>=500000||(nanos!=0&&timeout==0)){

timeout++;

}



wait(timeout);

}

publicfinalvoidwait()throwsInterruptedException{

wait(0);

}

//对象被回收时调用,不管如何,一个对象只调用一次

protectedvoidfinalize()throwsThrowable{}



概述



因为Object是Java所有类的祖先类,所以Java所有类都有Object中的方法,在看这些方法的时候要联系这些方法不是针对Objec一个类,而是所有类。

既然是所有类共有,设计的时候肯定想的是所有类的共性,比如:equals方法就是用来比较任意两个相同类型对象是否相等的,toString是用来将任意对象转换成String,方便打印查看。当然,以上方法的实现都是默认的,想要实现自己的逻辑需要在自己类中覆盖重写。

以上的native方法,在Oracle的jdk是看不到的,但在OpenJDK或其他开源JDK是可以找到对应的C/C++代码的。



源码详解



1.构造方法

源码中并没有Object的构造方法,但是,同样的,编译器在编译期间会给Object(事实上,所有的Java类,只要类中没有构造方法,编译器都会默认的给一个空构造方法,若已有构造方法,则不会添加)一个默认的空的构造方法:



publicObject(){}



2.registerNatives

带有native修饰的都是本地方法,所谓的本地方法是不通过Java语言实现的方法,但可以通过JNI,像调用Java方法一样调用这些方法。详细的可以搜索查看JNI。

这个方法的作用是对Object以下几个本地方法(hashCode/clone/notify等)进行注册(可以理解为,这个方法是告诉JVM这几个本地方法的实现映射),每一个有本地方法的都会有这个方法,但其内容不一样(因为注册的方法不一样嘛)。

3.getClass

每一个类在被加载的时候,都会生成一个Class类实例,而这个方法就可以在运行时期获得对象(这里的对象是堆里的那个对象,也就是获得的是动态类型的那个类)的Class对象,Class对象主要用于反射。



classA{}

classBextendsA{}

classCextendsB{}

Aa=newC();//对象newC()的静态类型是A,动态类型是C

Bb=(B)a;//引用b指向的还是newC(),动态类型还是C

Cc=(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后面那个类型(构造对象的那个类型),但是静态类

//型是由指向它的引用决定的,事实上可以这样理解对象只有动态类型,引用类型才是静态类型

//以上说的对象指的是堆里对象,而不是泛指Objecto=newObject()中的o

//不明白静态类型,动态类型的可以自行百度



4.hashCode

获得该对象的hash值,Java虚拟机规范并没有规定这个方法的具体实现,只是规定了同一个对象两次调用(任何条件情形下)这个方法返回的int值要想等(但并没有规定两个不同对象hash值一定不相同),具体实现由各个JVM厂商自己实现,所以返回的值意义并不一定(这里特指Object的hashCode方法),有可能返回的是对象的内存地址,也有可能是某个特定计算公式计算出来的值。

5.equals

原则上或则说语义上,设计目的上,equals的作用意义,是用来比较两个对象是否相等,这里是我们通常理解的相等:即两个对象其内容是否相等,而不是程序上来看,两个对象是否是同一个对象,即比较其内存地址;如果想比较两个对象是否是同一个对象(这里是说两个引用是否指向同一个对象),直接用==比较即可(==比较的就是对象的内存地址)。但这里重要的是,对于Object来说,它并不能知道子类是如何判断他们的两个实例是如何equals的,所以,默认的equals实现,比较的是两对象内存地址,即,若子类不重写equals方法,其作用等同于==。



//如何重写equals方法实现判断内容相等?

//关键点取决于你的逻辑,你想让两个对象在什么时候相等,你逻辑上就怎么写

classA{

publicinta;

publicStringb;

publicDd;



@Override

publicbooleanequals(Objecto){

if(this==o)returntrue;//如果指向同一个对象,当然equals

//如果o为null或两个对象类型都不相同,当然不equals

if(o==null||getClass()!=o.getClass())returnfalse;

//动态类型相同,强制转换

Aa1=(A)o;

/下面是自己的逻辑,判断两个对象是否相同,逻辑1Begin/

if(a!=a1.a)returnfalse;

if(b!=null?!b.equals(a1.b):a1.b!=null)returnfalse;

returnd!=null?d.equals(a1.d):a1.d==null;

//全部字段相同,则equals。如果对象越复杂,想要实现全部字段相同,也就越复杂

/逻辑1End/

/逻辑2begin/

//只要字段a相同,就认为两个对象equals

if(a==a1.a)returntrue;

/逻辑2end/

}

@Override

publicinthashCode(){

intresult=a;

result=31result+(b!=null?b.hashCode():0);

result=31result+(d!=null?d.hashCode():0);

returnresult;

}

}

classD{

publicinta;

}



网上说的,重写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,这主要是针对类中间具有引用类型而言划分的,详情可参看:Javaclone深度解析。

classA{}

Aa=newA();

a.clone();//报错,即抛CloneNotSupportedException异常

classAimplementsCloneable{}//这样才不会

//但,若你重写clone方法,并且在这个方法中没有调用父clone(也就是Object)方法

classA{

@Override

publicObjectclone()throwsCloneNotSupportedException{

returnnewA();

}

}

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(垃圾回收,详情可参考:JavaGC概述)之前被调用(JVM主动调用),你可以重写这个方法,然后在这个对象回收之前做某些动作,这个方法对于这个对象来说只能调用一次,为什么会这么说呢?对象都回收了,没了,难道不是当然只能调用一次?不是这样的,若你理解了JavaGC原理便知道,若当你在finalize方法中,将这个对象重新赋予了强引用,GC这个对象将失败,这个对象将继续存活,而下次这个对象又成为可回收对象了,GC回收这个对象的时候,这个对象的finalize方法将不会再执行。

另外,需要区分的是:

finalize不是C/C++中的析构函数,更不是释放内存的方法,它只是提供了在回收一个对象之前做某些操作,如果你熟悉C,那你知道C允许你为一个类定义一个撤消函数(destructor),它在对象正好出作用域之前被调用。Java不支持这个想法也不提供撤消函数。finalize()方法只和撤消函数的功能接近。当你对Java有丰富经验时,你将看到因为Java使用垃圾回收子系统,几乎没有必要使用撤消函数。

而且,在设计之初,这个方法就是为了兼容C/C++程序员习惯(对的,貌似就是这样),后来设计者也说,这是个失败的设计,所以,可以的话,在实践中忘掉这个方法吧。



classD{

publicstaticDd111;



@Override

protectedvoidfinalize()throwsThrowable{

super.finalize();

d111=this;//这个时候该对象第一次回收将失败,而以后将不会在执行该方法

System.out.println("finalizea="+this.a);

}

}

Dd=newD();

d=null;

//程序结束

//这个时候,虽然程序结束了,newD()对象也是可回收对象了,但是并不会执行

//finzlize,因为对于JVM来说GC的触发条件是内存不足,所以不会执行GC也就不会调用

//finzlize方法



二ParameterizedType

Java类型之参数化类型



源码



publicinterfaceParameterizedTypeextendsType{

//1.获得<>中实际类型

Type[]getActualTypeArguments();

//2.获得<>前面实际类型

TypegetRawType();

//3.如果这个类型是某个类型所属,获得这个所有者类型,否则返回null

TypegetOwnerType();

}



Java类型Type详见:TypeJava类型



概述



ParameterizedType,参数化类型,形如:Object,即常说的泛型,是Type的子接口。



源码详解



1.getActualTypeArguments

获得参数化类型中<>里的类型参数的类型,因为可能有多个类型参数,例如Map,所以返回的是一个Type[]数组。

【注意】无论<>中有几层<>嵌套,这个方法仅仅脱去最外层的<>,之后剩下的内容就作为这个方法的返回值,所以其返回值类型不一定。

例如:

1.Lista1;//这里返回的是,ArrayList,Class类型

2.List>a2;//这里返回的是ArrayList,ParameterizedType类型

3.Lista3;//返回的是T,TypeVariable类型

4.Lista4;//返回的是WildcardType类型

5.List[]>a5;//GenericArrayType

要注意,ArrayList与ArrayList的不同。



publicstaticvoidmain(String[]args)throwsException

{

Methodmethod=newMain().getClass().getMethod("test",List.class);//这里的第二个参数,和getRawType()意义类似

Type[]types=method.getGenericParameterTypes();

ParameterizedTypepType=(ParameterizedType)types[0];

Typetype=pType.getActualTypeArguments()[0];

System.out.prinwww.shanxiwang.nettln(type);

//type是Type类型,但直接输出的不是具体Type的五种子类型,而是这五种子类型以及WildcardType具体表现形式

System.out.println(type.getClass().getName());

}

publicvoidtest(List[]>a){

}

//读者可将test中参数用相应类型代替



2.getRawType

返回最外层<>前面那个类型,即Map的Map



Mapmaps=newHashMap<>();

ParameterizedTypepType=(ParameterizedType)maps.getClass().getGenericSuperclass();//获得HashMap的父类

System.out.println(pType.getRawType());//classjava.util.AbstractMap

if(pType.getRawType()instanceofClass){

System.out.println("true");//true

}

//注意类型(Type)与类(Class)的区别



3.getOwnerType

获得这个类型的所有者的类型



publicstaticvoidmain(String[]args)

{

Methodmethod=newGenericDemo().getClass().getMethod("applyMethod",Map.Entry.class);

Type[]types=method.getGenericParameterTypes();

ParameterizedTypepType=(ParameterizedType)types[0];

//返回所有者类型,打印结果是interfacejava.util.Map

System.out.println(pType.getOwnerType());

}

publicstaticvoidapplyMethod(Map.EntrymapEntry){

}

//Map接口就是Map.Entry的所有者



三TypeVariable

TypeVariable——类型变量



TypeVariable,类型变量,描述类型,表示泛指任意或相关一类类型,也可以说狭义上的泛型(泛指某一类类型),一般用大写字母作为变量,比如K、V、E等。



源码



publicinterfaceTypeVariableextendsType{

//获得泛型的上限,若未明确声明上边界则默认为Object

Type[]getBounds();

//获取声明该类型变量实体(即获得类、方法或构造器名)

DgetGenericDeclaration();

//获得名称,即K、V、E之类名称

StringgetName();

}



概述



说到TypeVariable就不得不提起Java泛型中另一个比较重要的接口对象,GenericDeclaration接口对象。该接口用来定义哪些对象上是可以声明(定义)范型变量,所谓范型变量就是或者,也就是TypeVariable这个接口的对应的对象,TypeVariable中的D是extendsGenericDeclaration的,用来通过范型变量反向获取拥有这个变量的GenericDeclaration。

目前实现GenericDeclaration接口的类包括Class,Method,Constructor,也就是说只能在这几种对象上进行范型变量的声明(定义)。GenericDeclaration的接口方法getTypeParameters用来逐个获取该GenericDeclaration的范型变量声明。详情可查看:Java源码解析(附录)(3)——GenericDeclaration(编辑中)。

类型变量的声明(定义):,前后需加上尖括号



//1.在类(Class)上声明(定义)类型变量

classA{

Ta;

}//之后这里可用任意类型替换T,例如

Aas=newA();

//是否看着有点像集合?不错,集合就是泛型的一个典型运用

//2.在方法上声明(定义)

publicvoidtest(Ee){}

//方法上,类型变量声明(定义)不是在参数里边,而且必须在返回值之前,static等修饰后

//3.声明(定义)在构造器上

publicA(Kk){}



【注意】类型变量声明(定义)的时候不能有下限(既不能有super),否则编译报错。为什么?TextendsclassA表示泛型有上限classA,当然可以,因为这样,每一个传进来的类型必定是classA(具有classA的一切属性和方法),但若是TsuperclassA,传进来的类型不一定具有classA的属性和方法,当然就不适用于泛型,说的具体点:



//假设

classA{

Tt;

publicvoidtest(){

//这个时候你不能用t干任何事,因为你不确定t具有哪些属性和方法

//当然,t肯定是有Object方法的,但没意义

}

}



源码详解



1.getBounds

获得该类型变量的上限(上边界),若无显式定义(extends),默认为Object,类型变量的上限可能不止一个,因为可以用&符号限定多个(这其中有且只能有一个为类或抽象类,且必须放在extends后的第一个,即若有多个上边界,则第一个&后必为接口)。



classA{

Kkey;

Vvalue;

publicstaticvoidmain(String[]args)throwsException

{

Type[]types=Main.class.getTypeParameters();

for(Typetype:types){

TypeVariablet=(TypeVariable)type;

System.out.println(t.getGenericDeclaration());

intsize=t.getBounds().length;

System.out.println(t.getBounds()[size-1]);

System.out.println(t.getName()+"\n-------------分割线-------------");

}

}

}

//输出结果

classcom.fcc.test.Main

interfacecom.fcc.test.interfaceB

K

-------------分割线-------------

classcom.fcc.test.Main

classjava.lang.Object

V

-------------分割线-------------



2.getGenericDeclaration

获得声明(定义)这个类型变量的类型及名称,即如:

classcom.xxx.xxx.classA或

publicvoidcom.fcc.test.Main.test(java.util.List)或

publiccom.fcc.test.Main()



Constructorconstructor=Main.class.getConstructor();

TypeVariabletypeVariable=constructor.getTypeParameters()[0];

System.out.println(typeVariable.getGenericDeclaration());

//获得方法中声明(定义)的类型变量与上面类似



3.getName

获得这个类型变量在声明(定义)时候的名称

献花(0)
+1
(本文系网络学习天...首藏)