1. 程式人生 > >看了Java的Class的原始碼,我自閉了

看了Java的Class的原始碼,我自閉了

# java原始碼之Class ​ 原始碼的重要性不言而喻,雖然枯燥,但是也有拍案叫絕。這是我的原始碼系列第二彈,後續還會一直更新,歡迎交流。String原始碼可以看我的[Java原始碼之String](https://www.cnblogs.com/chentang/p/13067765.html),如有不足,希望指正。 ## 1.class這個類是什麼 Class的本質也是一個類,只不過它是將我們定義類的共同的部分進行抽象,比如我們常定義的類都含有構造方法,類變數,函式,而Class這個類就是來操作這些屬性和方法的。當然我們常定義的類包含的型別都可以通過Class間接的來操作。而類的型別包含一般的類,介面,列舉型別,註解型別等等。這麼說可能有點太理論,我們看下面這個例子: 我們將生活中的一類事物抽象為一個類的時候,往往是因為他們具有相同的共性和不同的個性。定義一個類的作用就是將相同的共性抽離出來。一般的類都包含屬性和方法(行為),下面我們定義水果和汽車這兩個大類: ​ ![](https://img2020.cnblogs.com/blog/1842096/202006/1842096-20200620191538758-775685622.png) ![](https://img2020.cnblogs.com/blog/1842096/202006/1842096-20200620191557762-1388786455.png) 程式碼如下: 汽車類: ```java 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; } } ``` 水果類: ```java 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就是來操作我們定義類的屬性和方法。 ​ ![](https://img2020.cnblogs.com/blog/1842096/202006/1842096-20200620191641553-176815306.png) ![](https://img2020.cnblogs.com/blog/1842096/202006/1842096-20200620191657636-925521892.png) ​小試牛刀:通過Class這個類來獲取Fruit這個類中定義的方法; ```java 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()); } } ``` 執行結果: ```java 方法個數: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這個類的原始碼就會一清二楚: ```java 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構造方法 原始碼如下: ```java 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例項,如下的示例。 ```java // 普通類獲取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()的原始碼如下: ```java public final native Class getClass(); ``` 原始碼說明: 可以看到,這是一個native方法(一個Native Method就是一個java呼叫非java程式碼的介面),並且不允許子類重寫,所以理論上所有型別的例項都具有同一個 getClass 方法。 使用: ```java Fruit fruit = new Fruit(); Class fruitClass = fruit.getClass(); ``` 3.使用Class類的**靜態方法forName()**,用類的名字獲取一個Class例項(static Class forName(String className) ),這種方式靈活性最高,根據類的字串全名即可獲取Class例項,可以動態載入類,框架設計經常用到; 原始碼如下: ```java /* 由於方法區 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的方法請[移步這裡](https://blog.csdn.net/x_panda/article/details/17120479)。 基本使用: ```java Class fruitClass = Class.forName("cn.chen.test.util.lang.Fruit"); ``` **注**: 這種方式必須使用類的全限定名,,這是因為由於方法區 Class 型別資訊由類載入器和類全限定名唯一確定,否則會丟擲ClassNotFoundException的異常。 ### 2.2一般方法以及原始碼分析: Class類的一般的方法總共有六十多種,其實看到這麼多方法咱也不要慫,這裡面還有很多過載的方法,根據二八原則,我們平時用的也就那麼幾個方法,所以這裡只對以下幾個方法的使用和實現進行交流,其他的方法可以移步Java[官方文件](https://docs.oracle.com/en/java/javase/14/docs/api/index.html): ![](https://img2020.cnblogs.com/blog/1842096/202006/1842096-20200620191808594-434042365.png) #### 2.2.1 獲得類的構造方法 這個方法主要是用來了解一個類的構造方法有哪些,包含那些引數,特別是在單例的模式下。一般包含的方法如下: - **public Constructor[] getConstructors()** :獲取類物件的所有可見的建構函式 - **public Constructor[] getDeclaredConstructors():**獲取類物件的所有的建構函式 - **public Constructor getConstructor(Class... parameterTypes)**: 獲取指定的可見的建構函式,引數為:指定建構函式的引數型別陣列,如果該建構函式不可見或不存在,會丟擲 NoSuchMethodException 異常 - **public Constructor getDeclaredConstructor(Class... parameterTypes)** :獲取指定的建構函式,引數為:指定建構函式的引數型別陣列,無論建構函式可見性如何,均可獲取 基本使用: ```java Constructor[] constructors = fruitClass.getConstructors(); for (Constructor constructor : constructors) { System.out.println("獲得共有的構造方法:"+constructor); } ``` 輸出結果: ```java 獲得共有的構造方法: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: ```java private Fruit(String name,int size){ this.name = name; this.size =size; } ``` 使用getConstructors()和getDeclaredConstructors()著兩個方法進行測試: ```java 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); } ``` 輸出結果: ```java 獲得共有的構造方法: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()的原始碼分析: ```java public Constructor[] getConstructors() throws SecurityException { // 1.檢查是否允許訪問。如果訪問被拒絕,則丟擲SecurityException。 checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true); return copyConstructors(privateGetDeclaredConstructors(true)); } private static Constructor[] copyConstructors(Constructor[] arg) { // 2.使用克隆,得到當前類的所有建構函式 Constructor[] 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; } ``` 通過打斷點除錯,可以看到下面的資訊: ![](https://img2020.cnblogs.com/blog/1842096/202006/1842096-20200620191829363-802397336.png) 程式碼的呼叫邏輯在註釋裡已進行說明。 #### 2.2.2 獲得屬性 主要獲取類的屬性欄位,瞭解這個類聲明瞭那些欄位。 一般有四個方法: - **public Field[] getFields()**:獲取所有可見的欄位資訊,Field陣列為類中宣告的每一個欄位儲存一個Field 例項 - **public Field[] getDeclaredFields()**:獲取所有的欄位資訊 - **public Field getField(String name)** :通過欄位名稱獲取字元資訊,**該欄位必須可見,否則丟擲異常** - **public Field getDeclaredField(String name)** :通過欄位名稱獲取可見的字元資訊 基本使用: 首先我們在Fruit的類中加入一個public修飾的屬性: ```java public double weight; ``` ```java 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); } ``` 輸出結果: ```java 定義的公有屬性: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()這個方法為例,涉及以下幾個方法: ```java 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)** 引數說明: 1. 通過**方法名稱、引數型別**獲取方法 2. 如果你想訪問的方法不可見,會丟擲異常 3. 如果你想訪問的方法沒有引數,傳遞 `null`作為引數型別陣列,或者不傳值) - **public Method getDeclaredMethod(String name, Class... parameterTypes)** 1. 通過**方法名稱、引數型別**獲取方法 2. 如果你想訪問的方法沒有引數,傳遞 `null`作為引數型別陣列,或者不傳值) 基本使用: ```java //在fruit中定義一個這樣的方法 private void eat(String describe){ System.out.println("通過getMethod()方法呼叫了eat()方法: "+describe); } ``` 呼叫這個方法: ```java Class fruitClass = Fruit.class; Method method = fruitClass.getDeclaredMethod("eat",String.class); method.setAccessible(true); method.invoke(fruitClass.newInstance(),"我是該方法的引數值"); ``` 輸出結果: ```java 通過getMethod()方法呼叫了eat()方法:我是該方法的引數值 ``` 分析getDeclaredMethod()涉及的原始碼: ```java 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”。 | 基本用法: ```java // 定義一個介面: interface Animal{ public void run(); } ``` 判斷是不是一個介面: ```java Class AnimalClass = Animal.class; boolean flag = AnimalClass.isInterface(); System.out.println(flag); ``` 輸出結果: ``` true ``` 原始碼分析isInterface(): ```java public native boolean isInterface(); ``` 這是一個native方法,大家都知道native方法是非Java語言實現的程式碼,供Java程式呼叫的,因為Java程式是執行在JVM虛擬機器上面的,要想訪問到比較底層的與作業系統相關的就沒辦法了,只能由靠近作業系統的語言來實現。 #### 2.2.5 toString()方法 將物件轉換為字串。字串表示形式是字串“類”或“介面”,後跟一個空格,然後是該類的全限定名。 基本使用: ```java // 這是前面定義的兩個類Fruit和Car,Car是一個介面 Class fruitClass = Fruit.class; Class AnimalClass = Animal.class; System.out.println(AnimalClass.toString()); System.out.println(fruitClass.toString()); ``` 輸出結果: ```java // 格式 字串“類”或“介面”,後跟一個空格,然後是該類的全限定名 interface cn.chen.test.util.lang.Animal class cn.chen.test.util.lang.Fruit ``` 原始碼如下: ```java 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 ​ https://juejin.im/post/5d4450fbe51d4561ce5a1be1 ​ [JavaSE的官方文件](https://docs.oracle.com/en/java/javase/14/docs/api/index.html)