java源碼之Class
源碼的重要性不言而喻,雖然枯燥,但是也有拍案叫絕。這是我的源碼系列第二彈,后續還會一直更新,歡迎交流。String源碼可以看我的Java源碼之String,如有不足,希望指正。
1.class這個類是什么
Class的本質也是一個類,只不過它是將我們定義類的共同的部分進行抽象,比如我們常定義的類都含有構造方法,類變量,函數,而Class這個類就是來操作這些屬性和方法的。當然我們常定義的類包含的類型都可以通過Class間接的來操作。而類的類型包含一般的類,接口,枚舉類型,注解類型等等。這么說可能有點太理論,我們看下面這個例子:
我們將生活中的一類事物抽象為一個類的時候,往往是因為他們具有相同的共性和不同的個性。定義一個類的作用就是將相同的共性抽離出來。一般的類都包含屬性和方法(行為),下面我們定義水果和汽車這兩個大類:
代碼如下:
汽車類:
class Car{
// 定義屬性
private String name;
private String color;
/**
* 定義兩個構造方法
*/
public Car(){
}
public Car(String name,String color){
this.name = name;
this.color = color;
}
/**
* 定義兩個普通方法(行為)
*/
public void use(){
}
public void run(){
}
/**
* 屬性的get和set方法
* @return
*/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
水果類:
class Fruit{
// 定義屬性
private String name;
private int size;
/**
* 定義兩個構造方法
*/
public Fruit(){
}
public Fruit(String name,int size){
this.name = name;
this.size =size;
}
/**
* 定義兩個方法(行為)
*/
public void use(){
}
public void doFruit(){
}
/**
* 屬性的get和set方法
* @return
*/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getSize() {
return size;
}
public void setSize(int size) {
this.size = size;
}
}
可以看到水果和汽車這兩個類都有共同的部分,也就是一個類共同的部分,那就是屬性和方法,而Class就是來操作我們定義類的屬性和方法。
小試牛刀:通過Class這個類來獲取Fruit這個類中定義的方法;
public static void main(String[] args) {
Fruit fruit = new Fruit();
Class fruitClass = fruit.getClass();
Method[] fruitMethods = fruitClass.getMethods();
System.out.println("方法個數:" + fruitMethods.length);
for (Method method : fruitMethods) {
//得到返回類型
System.out.print("方法名稱和參數:" + method.getName() + "(");
//取得某個方法對應的參數類型數組
Class[] paramsType = method.getParameterTypes();
for (Class paramType : paramsType) {
System.out.print(paramType.getTypeName() + " ");
}
System.out.print(")");
Class returnType = method.getReturnType();
System.out.println("返回類型:" + returnType.getTypeName());
}
}
運行結果:
方法個數:15
方法名稱和參數:getName()返回類型:java.lang.String
方法名稱和參數:setName(java.lang.String )返回類型:void
方法名稱和參數:getSize()返回類型:int
方法名稱和參數:setSize(int )返回類型:void
方法名稱和參數:use()返回類型:void
方法名稱和參數:doFruit()返回類型:void
方法名稱和參數:wait()返回類型:void
方法名稱和參數:wait(long int )返回類型:void
方法名稱和參數:wait(long )返回類型:void
方法名稱和參數:equals(java.lang.Object )返回類型:boolean
方法名稱和參數:toString()返回類型:java.lang.String
方法名稱和參數:hashCode()返回類型:int
方法名稱和參數:getClass()返回類型:java.lang.Class
方法名稱和參數:notify()返回類型:void
方法名稱和參數:notifyAll()返回類型:void
這里可能有人疑惑了,Fruit類並沒有定義的方法為什么會出現,如wait(),equals()方法等。這里就有必要說一下java的繼承和反射機制。在繼承時,java規定每個類默認繼承Object這個類,上述這些並沒有在Fruit中定義的方法,都是Object中的方法,我們看一下Object這個類的源碼就會一清二楚:
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
public final native void wait(long timeout) throws InterruptedException;
public final void wait() throws InterruptedException {
wait(0);
}
而Class類中的getMethods()方法默認會獲取父類中的公有方法,也就是public修飾的方法。所以Object中的公共方法也出現了。
注: 要想獲得父類的所有方法(public、protected、default、private),可以使用apache commons包下的FieldUtils.getAllFields()可以獲取類和父類的所有(public、protected、default、private)屬性。
是不是感覺非常的強大 ,當然,使用Class來獲取一些類的方法和屬性的核心思想就是利用了Java反射特性。萬物皆反射,可見反射的強大之處,至於反射的原理,期待我的下一個博客。
2.常用方法的使用以及源碼分析
2.1構造方法
源碼如下:
private Class(ClassLoader loader) {
// Initialize final field for classLoader. The initialization value of non-null
// prevents future JIT optimizations from assuming this final field is null.
classLoader = loader;
}
可以看到Class類只有一個構造函數,並且是私有的。也就是說不能通過new來創建這個類的實例。官方文檔的解釋:私有構造函數,僅Java虛擬機創建Class對象。我想可能就是為了安全,具體原因不是很了解。如果有了解的話,可以在評論區內共同的交流。
Class是怎么獲取一個實例的。
那么既然這個class構造器私有化,那我們該如何去構造一個class實例呢,一般采用下面三種方式:
1.運用.class的方式來獲取Class實例。對於基本數據類型的封裝類,還可以采用.TYPE來獲取相對應的基本數據類型的Class實例,如下的示例。
// 普通類獲取Class的實例。接口,枚舉,注解,都可以通過這樣的方式進行獲得Class實例
Class fruitClass = Fruit.class;
// 基本類型和封裝類型獲得Class實例的方式,兩者等效的
Class intClass = int.class;
Class intClass1 = Integer.TYPE;
下面的表格兩邊等價:
boolean.class | Boolean.TYPE |
---|---|
char.class | Character.TYPE |
byte.class | Byte.TYPE |
short.class | Short.TYPE |
int.class | Integer.TYPE |
long.class | Long.TYPE |
float.class | Float.TYPE |
double.class | Double.TYPE |
void.class | Void.TYPE |
但是這種方式有一個不足就是對於未知的類,或者說不可見的類是不能獲取到其Class對象的。
2.利用對象.getClass()方法獲取該對象的Class實例;
這是利用了Object提供的一個方法getClass() 來獲取當着實例的Class對象,這種方式是開發中用的最多的方式,同樣,它也不能獲取到未知的類,比如說某個接口的實現類的Class對象。
Object類中的getClass()的源碼如下:
public final native Class<?> getClass();
源碼說明:
可以看到,這是一個native方法(一個Native Method就是一個java調用非java代碼的接口),並且不允許子類重寫,所以理論上所有類型的實例都具有同一個 getClass 方法。
使用:
Fruit fruit = new Fruit();
Class fruitClass = fruit.getClass();
3.使用Class類的靜態方法forName(),用類的名字獲取一個Class實例(static Class forName(String className) ),這種方式靈活性最高,根據類的字符串全名即可獲取Class實例,可以動態加載類,框架設計經常用到;
源碼如下:
/*
由於方法區 Class 類型信息由類加載器和類全限定名唯一確定,所以參數name必須是全限定名,
參數說明 name:class名,initialize是否加載static塊,loader 類加載器
*/
public static Class<?> forName(String name, boolean initialize,
ClassLoader loader)
throws ClassNotFoundException
{
Class<?> caller = null;
// 1.進行安全檢查
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
....
}
}
// 2.調用本地的方法
return forName0(name, initialize, loader, caller);
}
// 3.核心的方法
private static native Class<?> forName0(String name, boolean initialize,
ClassLoader loader,
Class<?> caller)
throws ClassNotFoundException;
/*
這個 forName是上述方法的重載,平時一般都使用這個 方法默認使用調用者的類加載器,將類的.class文件加載 到 jvm中
這里傳入的initialize為true,會去執行類中的static塊
*/
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
源碼說明已在注釋中說明,有些人會疑惑, static native Class<?> forName0()這個方法的實現。
這就要說到java的不完美的地方了,Java的不足除了體現在運行速度上要比傳統的C++慢許多之外,Java無法直接訪問到操作系統底層(如系統硬件等),為此Java使用native方法來擴展Java程序的功能。有關native的方法請移步這里。
基本使用:
Class fruitClass = Class.forName("cn.chen.test.util.lang.Fruit");
注: 這種方式必須使用類的全限定名,,這是因為由於方法區 Class 類型信息由類加載器和類全限定名唯一確定,否則會拋出ClassNotFoundException的異常。
2.2一般方法以及源碼分析:
Class類的一般的方法總共有六十多種,其實看到這么多方法咱也不要慫,這里面還有很多重載的方法,根據二八原則,我們平時用的也就那么幾個方法,所以這里只對以下幾個方法的使用和實現進行交流,其他的方法可以移步Java官方文檔:
2.2.1 獲得類的構造方法
這個方法主要是用來了解一個類的構造方法有哪些,包含那些參數,特別是在單例的模式下。一般包含的方法如下:
-
public Constructor[] getConstructors() :獲取類對象的所有可見的構造函數
-
public Constructor[] getDeclaredConstructors():獲取類對象的所有的構造函數
-
public Constructor getConstructor(Class... parameterTypes): 獲取指定的可見的構造函數,參數為:指定構造函數的參數類型數組,如果該構造函數不可見或不存在,會拋出 NoSuchMethodException 異常
-
public Constructor getDeclaredConstructor(Class... parameterTypes) :獲取指定的構造函數,參數為:指定構造函數的參數類型數組,無論構造函數可見性如何,均可獲取
基本使用:
Constructor[] constructors = fruitClass.getConstructors();
for (Constructor constructor : constructors) {
System.out.println("獲得共有的構造方法:"+constructor);
}
輸出結果:
獲得共有的構造方法:public cn.chen.test.util.lang.Fruit()
獲得共有的構造方法:public cn.chen.test.util.lang.Fruit(java.lang.String,int)
可以看到我們前面定義的來個構造方法,都被打印出來了。注意getConstructors()只能獲得被public修飾的構造方法,如果要獲得被(protected,default,private)修飾的構造方法,就要使用的getDeclaredConstructors()這個方法了。接下來,修改Fruit中的一個構造方法為private:
private Fruit(String name,int size){
this.name = name;
this.size =size;
}
使用getConstructors()和getDeclaredConstructors()着兩個方法進行測試:
Class fruitClass = Fruit.class;
Constructor[] constructors = fruitClass.getConstructors();
Constructor[] constructors1 = fruitClass.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println("獲得共有的構造方法:"+constructor);
}
System.out.println("=================================================");
for (Constructor constructor : constructors1) {
System.out.println("獲得所有的構造方法:"+constructor);
}
輸出結果:
獲得共有的構造方法:public cn.chen.test.util.lang.Fruit()
===================分隔線=============================
獲得所有的構造方法:public cn.chen.test.util.lang.Fruit()
獲得所有的構造方法:private cn.chen.test.util.lang.Fruit(java.lang.String,int)
可以看到兩者的區別。所以,反射在一定程度上破壞了java的封裝特性。畢竟人無完人,語言亦是一樣。
getConstructors()的源碼分析:
public Constructor<?>[] getConstructors() throws SecurityException {
// 1.檢查是否允許訪問。如果訪問被拒絕,則拋出SecurityException。
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
return copyConstructors(privateGetDeclaredConstructors(true));
}
private static <U> Constructor<U>[] copyConstructors(Constructor<U>[] arg) {
// 2.使用克隆,得到當前類的所有構造函數
Constructor<U>[] out = arg.clone();
// 3.使用ReflectionFactory構造一個對象,也是不使用構造方法構造對象的一種方式。
ReflectionFactory fact = getReflectionFactory();
// 4.遍歷,將構造函數進行拷貝返回,注意在調用fact.copyConstructor(out[i])這個方法的時候,還會進行安全檢查,用的就是下面的LangReflectAccess() 這個方法。
for (int i = 0; i < out.length; i++) {
out[i] = fact.copyConstructor(out[i]);
}
return out;
}
private static LangReflectAccess langReflectAccess() {
if (langReflectAccess == null) {
Modifier.isPublic(1);
}
return langReflectAccess;
}
通過打斷點調試,可以看到下面的信息:
代碼的調用邏輯在注釋里已進行說明。
2.2.2 獲得屬性
主要獲取類的屬性字段,了解這個類聲明了那些字段。
一般有四個方法:
- public Field[] getFields():獲取所有可見的字段信息,Field數組為類中聲明的每一個字段保存一個Field 實例
- public Field[] getDeclaredFields():獲取所有的字段信息
- public Field getField(String name) :通過字段名稱獲取字符信息,該字段必須可見,否則拋出異常
- public Field getDeclaredField(String name) :通過字段名稱獲取可見的字符信息
基本使用:
首先我們在Fruit的類中加入一個public修飾的屬性:
public double weight;
Class fruitClass = Fruit.class;
Field[] field2 = fruitClass.getFields();
for (Field field : field2) {
System.out.println("定義的公有屬性:"+field);
}
Field[] fields = fruitClass.getDeclaredFields();
for (Field field : fields) {
System.out.println("定義的所有屬性:"+field);
}
輸出結果:
定義的公有屬性:public double cn.chen.test.util.lang.Fruit.weight
========================分隔線============================
定義的所有屬性:private java.lang.String cn.chen.test.util.lang.Fruit.name
定義的所有屬性:private int cn.chen.test.util.lang.Fruit.size
定義的所有屬性:public double cn.chen.test.util.lang.Fruit.weight
源碼分析,就以getFileds()這個方法為例,涉及以下幾個方法:
public Field[] getFields() throws SecurityException {
// 1.檢查是否允許訪問。如果訪問被拒絕,則拋出SecurityException。
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
return copyFields(privateGetPublicFields(null));
}
private static Field[] copyFields(Field[] arg) {
// 2. 聲明一個Filed的數組,用來存儲類的字段
Field[] out = new Field[arg.length];
// 3.使用ReflectionFactory構造一個對象,也是不使用構造方法構造對象的一種方式。
ReflectionFactory fact = getReflectionFactory();
// 4.遍歷,將字段復制后返回。
for (int i = 0; i < arg.length; i++) {
out[i] = fact.copyField(arg[i]);
}
return out;
}
public Field copyField(Field var1) {
return langReflectAccess().copyField(var1);
}
// 再次檢查屬性的訪問權限
private static LangReflectAccess langReflectAccess() {
if (langReflectAccess == null) {
Modifier.isPublic(1);
}
return langReflectAccess;
}
2.2.3 獲得一般方法
就是獲取一個類中的方法,一般有以下幾個方法:
-
public Method[] getMethods(): 獲取所有可見的方法
-
public Method[] getDeclaredMethods() :獲取所有的方法,無論是否可見
-
public Method getMethod(String name, Class... parameterTypes)
參數說明:
- 通過方法名稱、參數類型獲取方法
- 如果你想訪問的方法不可見,會拋出異常
- 如果你想訪問的方法沒有參數,傳遞
null
作為參數類型數組,或者不傳值)
- public Method getDeclaredMethod(String name, Class... parameterTypes)
- 通過方法名稱、參數類型獲取方法
- 如果你想訪問的方法沒有參數,傳遞
null
作為參數類型數組,或者不傳值)
基本使用:
//在fruit中定義一個這樣的方法
private void eat(String describe){
System.out.println("通過getMethod()方法調用了eat()方法: "+describe);
}
調用這個方法:
Class fruitClass = Fruit.class;
Method method = fruitClass.getDeclaredMethod("eat",String.class);
method.setAccessible(true);
method.invoke(fruitClass.newInstance(),"我是該方法的參數值");
輸出結果:
通過getMethod()方法調用了eat()方法:我是該方法的參數值
分析getDeclaredMethod()涉及的源碼:
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
// 1.檢查方法的修飾符
checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
// 2.searchMethods()方法的第一個參數確定這個方法是不是私有方法,第二個參數我們定義的方法名,第三個參數就是傳入的方法的參數類型
Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
if (method == null) {
throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
}
return method;
}
// 這個方法就是通過傳入的方法名找到我們定義的方法,然后使用了Method的copy()方法返回一個Method的實例,我們通過操作mehtod這個實例就可以操作我們定義的方法。
private static Method searchMethods(Method[] methods,
String name,
Class<?>[] parameterTypes)
{
Method res = null;
String internedName = name.intern();
for (int i = 0; i < methods.length; i++) {
Method m = methods[i];
if (m.getName() == internedName
&& arrayContentsEq(parameterTypes, m.getParameterTypes())
&& (res == null
|| res.getReturnType().isAssignableFrom(m.getReturnType())))
res = m;
}
return (res == null ? res : getReflectionFactory().copyMethod(res));
}
public Method copyMethod(Method var1) {
return langReflectAccess().copyMethod(var1);
}
// 檢查屬性的訪問權限
private static LangReflectAccess langReflectAccess() {
if (langReflectAccess == null) {
Modifier.isPublic(1);
}
return langReflectAccess;
}
2.2.4 判斷類的類型的方法
這類型的方法顧名思義,就是來判斷這個類是什么類型,是接口,注解,枚舉,還是一般的類等等。部分方法如下表
boolean |
isAnnotation() 判斷是不是注解 |
---|---|
boolean |
isArray() 判斷是否為數組 |
boolean |
isEnum() 判斷是否為枚舉類型 |
boolean |
isInterface() 是否為接口類型 |
boolean |
isMemberClass() 當且僅當基礎類是成員類時,返回“true” |
boolean |
isPrimitive() 確定指定的“類”對象是否表示原始類型。 |
boolean |
isSynthetic() 如果這個類是合成類,則返回' true ';否則返回“false”。 |
基本用法:
// 定義一個接口:
interface Animal{
public void run();
}
判斷是不是一個接口:
Class AnimalClass = Animal.class;
boolean flag = AnimalClass.isInterface();
System.out.println(flag);
輸出結果:
true
源碼分析isInterface():
public native boolean isInterface();
這是一個native方法,大家都知道native方法是非Java語言實現的代碼,供Java程序調用的,因為Java程序是運行在JVM虛擬機上面的,要想訪問到比較底層的與操作系統相關的就沒辦法了,只能由靠近操作系統的語言來實現。
2.2.5 toString()方法
將對象轉換為字符串。字符串表示形式是字符串“類”或“接口”,后跟一個空格,然后是該類的全限定名。
基本使用:
// 這是前面定義的兩個類Fruit和Car,Car是一個接口
Class fruitClass = Fruit.class;
Class AnimalClass = Animal.class;
System.out.println(AnimalClass.toString());
System.out.println(fruitClass.toString());
輸出結果:
// 格式 字符串“類”或“接口”,后跟一個空格,然后是該類的全限定名
interface cn.chen.test.util.lang.Animal
class cn.chen.test.util.lang.Fruit
源碼如下:
public String toString() {
// 先是判斷是接口或者類,然后調用getName輸出類的全限定名
return (isInterface() ? "interface " : (isPrimitive() ? "" : "class "))
+ getName();
}
public native boolean isInterface();
public native boolean isPrimitive();
追本溯源,方能闊步前行。
參考資料
https://blog.csdn.net/x_panda/article/details/17120479