1. 程式人生 > >深入理解java反射機制

深入理解java反射機制

轉載自:https://blog.csdn.net/u012585964/article/details/52011138

一,java的核心機制

        java有兩種核心機制:java虛擬機器(JavaVirtual Machine)與垃圾收集機制(Garbage collection):

  • Java虛擬機器:是執行所有Java程式的抽象計算機,是Java語言的執行環境,在其上面執行Java程式碼編譯後的位元組碼程式,java虛擬機器實現了平臺無關性。
  • Java垃圾回收(Garbage Collection):自動釋放不用物件記憶體空間,在java程式執行過程中自動進行,垃圾收集機制可大大縮短程式設計時間,保護程式的完整性,是Java語言安全性策略的一個重要部份。

二,java虛擬機器及其結構

        java垃圾回收不需要程式設計師手動操作,我們經常需要關注的是java虛擬機器,java虛擬機器承載著程式從原始碼到執行的全部工作。 
Java虛擬機器是可執行Java程式碼的假想計算機,有自己想象中的硬體,如處理器、堆疊、暫存器等,還具有相應的指令系統,可以執行 Java 的位元組碼程式。Java語言的一個非常重要的特點就是與平臺的無關性。而使用Java虛擬機器是實現這一特點的關鍵。Java語言使用模式Java虛擬機器遮蔽了與具體平臺相關的資訊,使得Java語言編譯程式只需生成在Java虛擬機器上執行的目的碼(位元組碼),就可以在多種平臺上不加修改地執行。Java虛擬機器在執行位元組碼時,把位元組碼解釋成具體平臺上的機器指令執行。 
對於 JVM 的基本結構,我們可以從下圖可以大致瞭解: 
這裡寫圖片描述

三,程式的執行過程

        從原始檔建立到程式執行,Java程式要經過兩大步驟:編譯,執行;1、原始檔由編譯器編譯成位元組碼(ByteCode) 2、位元組碼由java虛擬機器解釋執行。

  •  第一步(編譯): 建立完原始檔之後,程式會被編譯器編譯為.class檔案。Java編譯一個類時,如果這個類所依賴的類還沒有被編譯,編譯器就會先編譯這個被依賴的類,然後引用,否則直接引用。。編譯後的位元組碼檔案格式主要分為兩部分:常量池和方法位元組碼。
  •  第二步(執行):java類執行的過程大概可分為兩個過程:1、類的載入  2、執行。

四,類的載入

類載入過程

java程式經過編譯後形成*.class檔案。通過類載入器將位元組碼(*.class)載入入JVM的記憶體中。JVM將類載入過程分成載入,連線,初始化三個階段,其中連線階段又可分為驗證,準備,解析三個階段。

JVM 的類載入是通過 ClassLoader 及其子類來完成的,類的層次關係和載入順序可以由下圖來描述:

這裡寫圖片描述 
1)Bootstrap ClassLoader啟動類載入器

        負責載入$JAVA_HOME中jre/lib/裡所有的 class(JDK 代表 JDK 的安裝目錄,下同),或被-Xbootclasspath引數指定的路徑中的,並且能被虛擬機器識別的類庫(如 rt.jar,所有的java.*開頭的類均被 Bootstrap ClassLoader 載入)。啟動類載入器由 C++ 實現,不是 ClassLoader 子類。無法被 Java 程式直接引用的。

2)Extension ClassLoader擴充套件類載入器

         該載入器由sun.misc.LauncherExtClassLoader實現,負責載入Java平臺中擴充套件功能的一些jar包,包括JAVA_HOME中jre/lib/.jar或-Djava.ext.dirs指定目錄下的 jar 包。即JDK\jre\lib\ext目錄中,或者由 java.ext.dirs 系統變數指定的路徑中的所有類庫(如javax.開頭的類),開發者可以直接使用擴充套件類載入器

3)App ClassLoader應用程式類載入器

         該類載入器由 sun.misc.Launcher$AppClassLoader 來實現,負責記載 classpath 中指定的 jar 包及目錄中 class,開發者可以直接使用該類載入器,如果應用程式中沒有自定義過自己的類載入器,一般情況下這個就是程式中預設的類載入器。

        啟動類載入器:它使用 C++ 實現(這裡僅限於 Hotspot,也就是 JDK1.5 之後預設的虛擬機器,有很多其他的虛擬機器是用 Java 語言實現的),是虛擬機器自身的一部分。 

       所有其他的類載入器:這些類載入器都由 Java 語言實現,獨立於虛擬機器之外,並且全部繼承自抽象類 java.lang.ClassLoader,這些類載入器需要由啟動類載入器載入到記憶體中之後才能去載入其他的類。 
        應用程式都是由這三種類載入器互相配合進行載入的,我們還可以加入自定義的類載入器。

載入

載入是類載入過程的第一個階段,在載入階段,虛擬機器需要完成以下三件事情:

  • 通過一個類的全限定名來獲取其定義的二進位制位元組流。
  • 將這個位元組流所代表的靜態儲存結構轉化為方法區的執行時資料結構。
  • 在 Java 堆中生成一個代表這個類的 java.lang.Class 物件,作為對方法區中這些資料的訪問入口。

        注意,這裡第 1 條中的二進位制位元組流並不只是單純地從 Class 檔案中獲取,比如它還可以從 Jar 包中獲取、從網路中獲取(最典型的應用便是 Applet)、由其他檔案生成(JSP 應用)等。

        相對於類載入的其他階段而言,載入階段(準確地說,是載入階段獲取類的二進位制位元組流的動作)是可控性最強的階段,因為開發人員既可以使用系統提供的類載入器來完成載入,也可以自定義自己的類載入器來完成載入。

     JVM主要在程式第一次主動使用類的時候,才會去載入該類。也就是說,JVM並不是在一開始就把一個程式就所有的類都載入到記憶體中,而是到用的時候才把它載入進來,而且只加載一次。

        載入過程中會先檢查類是否被已載入,檢查順序是自底向上,從 Custom ClassLoader 到 BootStrap ClassLoader 逐層檢查,只要某個 Classloader 已載入就視為已載入此類,保證此類只所有 ClassLoade r載入一次。而載入的順序是自頂向下,也就是由上層來逐層嘗試載入此類。 
        這幾種類載入器的層次關係如下圖所示:

這裡寫圖片描述

        這種層次關係稱為類載入器的雙親委派模型。雙親委派模型的工作流程是:

 如果一個類載入器收到了類載入的請求,它首先不會自己去嘗試載入這個類,而是把請求委託給父載入器去完成,依次向上,因此,所有的類載入請求最終都應該被傳遞到頂層的啟動類載入器中,只有當父載入器在它的搜尋範圍中沒有找到所需的類時,即無法完成該載入,子載入器才會嘗試自己去載入該類。

驗證

        驗證的目的是為了確保 Class 檔案中的位元組流包含的資訊符合當前虛擬機器的要求,而且不會危害虛擬機器自身的安全。不同的虛擬機器對類驗證的實現可能會有所不同,但大致都會完成以下四個階段的驗證:檔案格式的驗證、元資料的驗證、位元組碼驗證和符號引用驗證。

準備

        準備階段是正式為類變數分配記憶體並設定類變數初始值的階段,這些記憶體都將在方法區中分配。對於該階段有以下幾點需要注意:

  • 這時候進行記憶體分配的僅包括類變數(static),而不包括例項變數,例項變數會在物件例項化時隨著物件一塊分配在 Java 堆中。 
  •  這裡所設定的初始值通常情況下是資料型別預設的零值(如 0、0L、null、false 等),而不是被在 Java 程式碼中被顯式地賦予的值。

解析

        解析階段是虛擬機器將常量池中的符號引用轉化為直接引用的過程。

        解析動作主要針對類或介面、欄位、類方法、介面方法四類符號引用進行,分別對應於常量池中的 CONSTANT_Class_info、CONSTANT_Fieldref_info、CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info 四種常量型別。

  •  類或介面的解析:判斷所要轉化成的直接引用是對陣列型別,還是普通的物件型別的引用,從而進行不同的解析。
  • 欄位解析:對欄位進行解析時,會先在本類中查詢是否包含有簡單名稱和欄位描述符都與目標相匹配的欄位,如果有,則查詢結束;如果沒有,則會按照繼承關係從上往下遞迴搜尋該類所實現的各個介面和它們的父介面,還沒有,則按照繼承關係從上往下遞迴搜尋其父類,直至查詢結束

初始化

類初始化是類載入過程的最後一個階段,到初始化階段,才真正開始執行類中的 Java 程式程式碼。虛擬機器規範嚴格規定了有且只有四種情況必須立即對類進行初始化:

  • 遇到 new、getstatic、putstatic、invokestatic 這四條位元組碼指令時,如果類還沒有進行過初始化,則需要先觸發其初始化。生成這四條指令最常見的 Java 程式碼場景是:使用 new 關鍵字例項化物件時、讀取或設定一個類的靜態欄位(static)時(被 static 修飾又被 final 修飾的,已在編譯期把結果放入常量池的靜態欄位除外)、以及呼叫一個類的靜態方法時。
  • 使用 Java.lang.refect 包的方法對類進行反射呼叫時,如果類還沒有進行過初始化,則需要先觸發其初始化。
  • 當初始化一個類的時候,如果發現其父類還沒有進行初始化,則需要先觸發其父類的初始化。
  • 當虛擬機器啟動時,使用者需要指定一個要執行的主類,虛擬機器會先執行該主類。

虛擬機器規定只有這四種情況才會觸發類的初始化,稱為對一個類進行主動引用,除此之外所有引用類的方式都不會觸發其初始化,稱為被動引用。

五,靜態載入和動態載入

        Java初始化一個類的時候可以用new 操作符來初始化,也可通過Class.forName的方式來得到一個Class型別的例項,然後通過這個Class型別的例項的newInstance來初始化.我們把前者叫做JAVA的靜態載入,把後者叫做動態載入.。

        有時候我們說某個語言具有很強的動態性,有時候我們會區分動態和靜態的不同技術與作法。我們朗朗上口動態繫結(dynamic binding)、動態連結(dynamic linking)、動態載入(dynamic loading)等。然而“動態”一詞其實沒有絕對而普遍適用的嚴格定義,有時候甚至像面向物件當初被匯入程式設計領域一樣,一人一把號,各吹各的調。 
        一般而言,開發者社群說到動態語言,大致認同的一個定義是:“程式執行時,允許改變程式結構或變數型別,這種語言稱為動態語言”。從這個觀點看,Perl,Python,Ruby是動態語言,C++,Java,C#不是動態語言。 

        儘管在這樣的定義與分類下Java不是動態語言,它卻有著一個非常突出的動態相關機制:Reflection。Java程式可以載入一個執行時才得知名稱的class,獲悉其完整構造(但不包括methods定義),並生成其物件實體、或對其fields設值、或喚起其methods。

  • 靜態載入的時候如果在執行環境中找不到要初始化的類,丟擲的是NoClassDefFoundError,它在JAVA的異常體系中是一個Error.
  • 動態態載入的時候如果在執行環境中找不到要初始化的類,丟擲的是ClassNotFoundException,它在JAVA的異常體系中是一個checked異常,在寫程式碼的時候就需要catch.

六,反射

        JAVA反射機制:

在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性;這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為java語言的反射機制。

        Java反射機制主要提供了以下功能:

在執行時判斷任意一個物件所屬的類;在執行時構造任意一個類的物件;在執行時判斷任意一個類所具有的成員變數和方法;在執行時呼叫任意一個物件的方法;生成動態代理。

        Java有個Object 類,是所有Java 類的繼承根源,其內聲明瞭數個應該在所有Java 類中被改寫的方法:hashCode()、equals()、clone()、toString()、getClass()等。其中getClass()返回一個Class 物件。

Class 類十分特殊。它和一般類一樣繼承自Object,其實體用以表達Java程式執行時的classes和interfaces,也用來表達enum、array、primitive Java types(boolean, byte, char, short, int, long, float, double)以及關鍵詞void。當一個class被載入,或當載入器(class loader)的defineClass()被JVM呼叫,JVM 便自動產生一個Class 物件。

        如果您想借由“修改Java標準庫原始碼”來觀察Class 物件的實際生成時機(例如在Class的constructor內新增一個println()),這樣是行不通的!因為Class並沒有public constructor。

        Class是Reflection故事起源。針對任何您想探勘的類,唯有先為它產生一個Class 物件,接下來才能經由後者喚起為數十多個的Reflection APIs。Reflection機制允許程式在正在執行的過程中,利用Reflection APIs取得任何已知名稱的類的內部資訊,包括:package、 type parameters、 superclass、 implemented interfaces、 inner classes、 outer classes、 fields、 constructors、 methods、 modifiers等,並可以在執行的過程中,動態生成instances、變更fields內容或喚起methods。

本段來自於百度百科 JAVA反射機制

從Class中獲取資訊

Class類提供了大量的例項方法來獲取該Class物件所對應的詳細資訊,Class類大致包含如下方法,其中每個方法都包含多個過載版本,因此我們只是做簡單的介紹,詳細請參考JDK文件

獲取類內資訊

獲取內容    方法簽名
構造器 Constructor<T> getConstructor(Class<?>... parameterTypes)
包含的方法   Method getMethod(String name, Class<?>... parameterTypes)
包含的屬性   Field getField(String name)
包含的Annotation   <A extends Annotation> A getAnnotation(Class<A> annotationClass)
內部類 Class<?>[] getDeclaredClasses()
外部類 Class<?> getDeclaringClass()
所實現的介面  Class<?>[] getInterfaces()
修飾符 int getModifiers()
所在包 Package getPackage()
類名  String getName()
簡稱  String getSimpleName()

一些判斷類本身資訊的方法

判斷內容    方法簽名
註解型別?   boolean isAnnotation()
使用了該Annotation修飾?   boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
匿名類?    boolean isAnonymousClass()
陣列? boolean isArray()
列舉? boolean isEnum()
原始型別?   boolean isPrimitive()
介面? boolean isInterface()
obj是否是該Class的例項 boolean isInstance(Object obj)

1,獲取class 
這裡寫圖片描述

這張圖忘記從哪儲存的了

主有三種獲得class的途徑,使用時要注意區別

  • 型別.class  如: String.class使用類名加“.class”的方式即會返回與該類對應的Class物件。這個方法可以直接獲得與指定類關聯的Class物件,而並不需要有該類的物件存在。
  • Class.forName("類名");該方法可以根據字串引數所指定的類名獲取與該類關聯的Class物件。如果該類還沒有被裝入,該方法會將該類裝入JVM。forName方法的引數是類的完 整限定名(即包含包名)。通常用於在程式執行時根據類名動態的載入該類並獲得與之對應的Class物件。
  • obj.getClass();所有Java物件都具備這個方法,該方法用於返回呼叫該方法的物件的所屬類關聯的Class物件

2、獲取構造方法

Class類提供了四個public方法,用於獲取某個類的構造方法:

Constructor getConstructor(Class[] params)根據建構函式的引數,返回一個具體的具有public屬性的建構函式

Constructor getConstructors()     返回所有具有public屬性的建構函式陣列

Constructor getDeclaredConstructor(Class[] params)     根據建構函式的引數,返回一個具體的建構函式(不分public和非public屬性)

Constructor getDeclaredConstructors()    返回該類中所有的建構函式陣列(不分public和非public屬性)
/* 1 反射出無參的構造方法並得到物件
  * 注意:
  * 1 在Class.forName()中應該傳入含有包名的類全名
  * 2 newInstance()方法的本質是呼叫類的無參Public構造方法
  */
  String className1="cn.testreflect.Worker";
  Class clazz1=Class.forName(className1);
  Object object1=clazz1.newInstance();
  System.out.println("object1.toString()="+object1.toString());
  /**
  * 2 反射出帶引數的構造方法並得到物件
  */
  String className2="cn.testreflect.Worker";
  Class clazz2=Class.forName(className2);
  Constructor constructor1=clazz2.getConstructor(int.class,String.class);
  Object object2=constructor1.newInstance(18,"小明");
  System.out.println("object2.toString()="+object2.toString());

3、獲取類的成員方法

與獲取構造方法的方式相同,存在四種獲取成員方法的方式。 

Method getMethod(String name, Class[] params) 根據方法名和引數,返回一個具體的具有public屬性的方法

Method[] getMethods() 返回所有具有public屬性的方法陣列

Method getDeclaredMethod(String name, Class[] params)  根據方法名和引數,返回一個具體的方法(不分public和非public屬性)

Method[] getDeclaredMethods() 返回該類中的所有的方法陣列(不分public和非public屬性)
 /* 呼叫物件的帶引數的方法
  */
  String className5="cn.testreflect.Worker";
  Class clazz5=Class.forName(className5);
  Method method=clazz5.getMethod("printMessage", 
       String.class,int.class,int.class);
  Object object5=clazz5.newInstance();
  method.invoke(object5, "周星星",50,9527);
  } catch (Exception e) {
  System.out.println(e.toString());
  }

4、獲取類的成員變數(成員屬性)

存在四種獲取成員屬性的方法

Field getField(String name)  根據變數名,返回一個具體的具有public屬性的成員變數

Field[] getFields()  返回具有public屬性的成員變數的陣列

Field getDeclaredField(String name) 根據變數名,返回一個成員變數(不分public和非public屬性)

Field[] getDelcaredFields() 返回所有成員變數組成的陣列(不分public和非public屬性)
/* 1 獲取類的私有欄位
  * 注意:
  * 獲取共有欄位應呼叫clazz3.getField(name)方法
  */
  String className3="cn.testreflect.Worker";
  Class clazz3=Class.forName(className3);
  Field ageField1=clazz3.getDeclaredField("age");
  System.out.println("ageField1="+ageField1);
  /**
  * 2 獲取和更改某個物件的私有欄位
  * 即模擬get()和set()方法
  */
  String className4="cn.testreflect.Worker";
  Class clazz4=Class.forName(className4);
  Field ageField2=clazz4.getDeclaredField("age");
  Object object4=constructor1.newInstance(18,"小明");
  //取消訪問私有欄位的合法性檢查
  ageField2.setAccessible(true);
  //獲取物件的私有欄位
  Object ageObject4=ageField2.get(object4);
  System.out.println("ageObject4="+ageObject4);
  //再更改物件的私有欄位的值
  ageField2.set(object4, 9527);
  //重新獲得
  Object ageObject5=ageField2.get(object4);
  System.out.println("ageObject5="+ageObject5);


相關推薦

深入理解java反射機制

轉載自:https://blog.csdn.net/u012585964/article/details/52011138一,java的核心機制        java有兩種核心機制:java虛擬機器(JavaVirtual Machine)與垃圾收集機制(Garbage c

深入理解java反射機制中Method類中的invoke()方法

1.先說明Method類中的幾個重要的屬性 1)Method型別的root屬性: 可以理解為每一個 java方法都有唯一的一個Method物件,這個物件就是root,我們可以利用反射建立java方法的眾多的Method類的物件,這些物件指向root,可以理解為root的映象

理解Java反射機制

ext 就是 概念 alt target private 一個 反射機制 語言   看了知乎老頑童周伯通的回復和深入解析java反射關於反射的講解,終於對反射的概念有了新的認識。不再畫蛇添足了,轉載一下精華部分。 一、為什麽要用到反射? 假如你寫了一段代碼:Objec

深入理解Java反射

override nts 準備工作 class red declare hand 類型信息 屬於   要想理解反射的原理,首先要了解什麽是類型信息。Java讓我們在運行時識別對象和類的信息,主要有2種方式:一種是傳統的RTTI,它假定我們在編譯時已經知道了所有的類型信息;另

深入理解java反射原理

  反射是java的一個特性,這一特性也使得它給了廣大的第三方框架和開發過者很大的想像空間。   通過反射,java可以動態的載入未知的外部配置物件,臨時生成位元組碼進行載入使用,從而使程式碼更靈活!可以極大地提高應用的擴充套件性!   但是,除了停留在使用其華麗功能,我們還可以去看看其實現! 主要看兩

深入理解 Java 反射:Class (反射的入口)

什麼是 Reflection 反射,為什麼要用它 Java 強型別語言,但是我們在執行時有了解、修改資訊的需求,包括類資訊、成員資訊以及陣列資訊。 Java 中 Reflection 和 Introspection 區別? 說起反射,還有一個相似的概念 ‘Introspec

深入理解Java回收機制

參考:深入理解Java虛擬機器 一、垃圾回收機制的意義 Java語言中一個顯著的特點就是引入了垃圾回收機制,使c++程式設計師最頭疼的記憶體管理的問題迎刃而解,它使得Java程式設計師在編寫程式的時候不再需要考慮記憶體管理。由於有個垃圾回收機制,Java中的物件不

深入探尋Java反射機制 (Class)

Classes 通過反射機制我們可以在執行時探尋類的內部結構,並獲取以下資訊 Class NameClass Modifiers (public, protected, synchronized等)Package InfoSuper ClassImplemented In

深入探尋Java反射機制

通過Java的反射(Reflection)機制,即使在編譯時不知道class name和method name等資訊,也可以在執行時獲取class、interface、fields和methods等相關資訊,還可以建立新的例項、呼叫方法以及獲取/設定屬性值。 本文將介

Java核心技術再理解——Java反射機制

在Java中反射是一種十分強大的機制,可以傳遞class,可以取得一個類的所有資訊,動態的生成一個類。甚至可以取得一個類的父類。同樣反射這一機制也是Java高階開發所必須掌握的。 能夠分析類能力的程式被稱為反射。本文將談到java.lang.Class,以及j

深入理解Java:類加載機制反射

指定 請求 image vm虛擬機 常量池 使用 元素 靜態 創建 一、Java類加載機制 1.概述 Class文件由類裝載器裝載後,在JVM中將形成一份描述Class結構的元信息對象,通過該元信息對象可以獲知Class的結構信息:如構造函數,屬性和方法等,J

深入理解Java類型信息(Class對象)與反射機制

成員變量 字段 機制 () 程序 轉換 默認 數據 統一   深入理解Class對象    RRTI的概念以及Class對象作用    認識Class對象之前,先來了解一個概念,RTTI(Run-Time Type Identification)運行時類型識別,對於這個詞一

深入理解Java型別資訊(Class物件)與反射機制

關聯文章: 本篇主要是深入對Java中的Class物件進行分析,這對後續深入理解反射技術非常重要,主要內容如下: 深入理解Class物件 RRTI的概念以及Class物件作用 認識Class物件之前,先來了解一個概念,RTTI(Run-Time

深入理解java反射機制(轉載)

今天將從以下4方面來系統的學習一下java的反射機制: java反射是什麼 java反射(Reflection)底層實現原理 java反射的簡單演示 java反射的應用場景 1,java反射是什麼 首先大家應該先了解兩個概念,編譯

Java基礎13——深入理解java中的反射機制

java中的反射機制 什麼是反射?   反射(Reflection)是Java 程式開發語言的特徵之一,它允許執行中的 Java 程式獲取自身的資訊,並且可以操作類或物件的內部屬性。   簡而言之,通過反射,我們可以在執行時獲得程式或程式集中每一個型別的成員和成員的資訊。    程式

深入理解Java:類載入機制反射

 說明:本文乃學習整理參考而來. 一、Java類載入機制 1.概述        Class檔案由類裝載器裝載後,在JVM中將形成一份描述Class結構的元資訊物件,通過該元資訊物件可以獲知Class的結構資訊:如建構函式,屬性和方法等,Java允許使用者藉由這個Class相關的元資訊物件間接呼

夯實Java基礎系列12:深入理解Java中的反射機制

本系列文章將整理到我在GitHub上的《Java面試指南》倉庫,更多精彩內容請到我的倉庫裡檢視 https://github.com/h2pl/Java-Tutorial 喜歡的話麻煩點下Star哈 文章首發於我的個人部落格: www.how2playlife.com 列舉(enum)型別是Java

深入理解Java虛擬機- 學習筆記 - 虛擬機類加載機制

支持 pub eth 獲取 事件 必須 string 沒有 字節碼 虛擬機把描述類的數據從Class文件加載道內存,並對數據進行校驗,轉換解析和初始化,最終形成可以被虛擬機直接使用的Java類型,這就是虛擬機的類加載機制。在Java裏,類型的加載、連接和初始化過程都是在程序

Java反射機制深入詳解

const 運行時 設計 應用程序 類加載器 分配 import 程序 為什麽 一.概念   反射就是把Java的各種成分映射成相應的Java類。   Class類的構造方法是private,由JVM創建。   反射是java語言的一個特性,它允程序在運行時(註意不是編譯的