Java——動態性、反射機制、類加載、動態編譯、腳本引擎、字節碼操做.....

Java動態性有:反射機制,動態編譯/代理,字節碼操做。常見的是反射和字節碼操做。javascript


Java讓咱們在運行時識別對象和類的信息,主要有2種方式:一種是傳統的RTTI,它假定咱們在編譯時已經知道了全部的類型信息;另外一種是反射機制,它容許咱們在運行時發現和使用類的信息。java


類的生命週期python

image.png

類加載初始化階段,必須對類進行初始化的狀況:數組

一、使用new關鍵字實例化對象時、讀取或者設置一個類的靜態字段(除final常量)以及調用靜態方法的時候。瀏覽器

二、使用反射包(java.lang.reflect)的方法對類進行反射調用時,若是類尚未被初始化,則需先進行初始化,這點對反射很重要。服務器

三、當初始化一個類的時候,若是其父類還沒進行初始化則需先觸發其父類的初始化。網絡

四、當Java虛擬機啓動時,用戶須要指定一個要執行的主類(包含main方法的類),虛擬機會先初始化這個主類框架

五、當使用JDK 1.7 的動態語言支持時,若是一個java.lang.invoke.MethodHandle 實例最後解析結果爲REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,而且這個方法句柄對應類沒有初始化時。ide

不會被初始化的狀況:函數

    當訪問靜態域,只有真正聲明這個域的類菜會被初始化。

        經過子類引用父類的靜態變量,不會致使子類初始化。

    經過數組定義引用類,不會觸發此類初始化

    引用常量不會觸發此類的初始化。


Class對象

Class類的對象做用是運行時提供或得到某個對象的類型信息。

  理解RTTI在Java中的工做原理,首先須要知道類型信息在運行時是如何表示的,這是由Class對象來完成的,它包含了與類有關的信息。Class對象就是用來建立全部「常規」對象的,Java使用Class對象來執行RTTI,即便你正在執行的是相似類型轉換這樣的操做。


  每一個類都會產生一個對應的Class對象,也就是保存在.class文件。

好比建立一個Shapes類,編譯Shapes類後就會建立其包含Shapes類相關類型信息的Class對象,並保存在Shapes.class字節碼文件中。

全部類都是在對其第一次使用時,動態加載到JVM的,當程序建立一個對類的靜態成員的引用時,就會加載這個類。Class對象僅在須要的時候纔會加載,static初始化是在類加載時進行的。


類加載器首先會檢查這個類的Class對象是否已被加載過,若是還沒有加載,默認的類加載器就會根據類名查找對應的.class文件。


  想在運行時使用類型信息,必須獲取對象的Class對象的引用,使用功能Class.forName(「Base」)能夠實現該目的,或者使用base.class。

注:使用.class來建立Class對象的引用時,不會自動初始化該Class對象,使用forName()會自動初始化該Class對象。爲了使用類而作的準備工做通常有如下3個步驟:

加載:由類加載器完成,找到對應的字節碼,建立一個Class對象

連接:驗證類中的字節碼,爲靜態域分配空間

初始化:若是該類有超類,則對其初始化,執行靜態初始化器和靜態初始化塊

獲取Class類的四種方式


1.調用運行時類自己的.class屬性

Class clazz = String.class;


2.經過運行時類的對象獲取

Person p = new Person();

Class clazz = p.getClass();


3.經過Class的靜態方法獲取:體現反射的動態性

Class clazz = Class.forName("com.util.xxx");


4.經過類的加載器

ClassLoader classLoader = this.getClass().getClassLoader();

Class clazz = classLoader.loadClass("com.util.xxx");

這是一個實例方法,須要一個ClassLoader對象來調用該方法,該方法將Class文件加載到內存時,並不會執行類的初始化,直到這個類第一次使用時才進行初始化.該方法由於須要獲得一個ClassLoader對象,因此能夠根據須要指定使用哪一個類加載器.


反射

     Class類和java.lang.reflect類庫一塊兒對反射進行了支持,該類庫包含Field、Method和Constructor類,這些類的對象由JVM在啓動時建立,以表示未知類裏對應的成員。這就就可使用Contructor建立新的對象,用get()和set()方法獲取和修改類中與Field對象關聯的字段,用invoke()方法調用與Method對象關聯的方法。另外,還能夠調用getFields()、getMethods()和getConstructors()等許多便利的方法,以返回表示字段、方法、以及構造器對象的數組,這樣,對象信息能夠在運行時被徹底肯定下來,而在編譯時不須要知道關於類的任何事情。


  經過反射與一個未知類型的對象打交道時,JVM只是簡單地檢查這個對象,看它屬於哪一個特定的類。所以,那個類的.class對於JVM來講必須是可獲取的,要麼在本地機器上,要麼從網絡獲取。因此對於RTTI和反射之間的真正區別只在於:

RTTI:編譯器在編譯時打開和檢查.class文件

反射:運行時打開和檢查.class文件


instanceof 關鍵字與isInstance方法


        if(obj instanceof Animal){

            Animal animal= (Animal) obj;

        }

//isInstance方法則是Class類中的一個Native方法

        if(Animal.class.isInstance(obj)){

            Animal animal= (Animal) obj;

        }


參考:https://blog.csdn.net/javazejian/article/details/70768369


動態編譯

就是運行時動態生成java代碼, JAVA 6.0引入了動態編譯機制。

• 動態編譯的應用場景

好比瀏覽器端編寫java代碼,上傳服務器編譯和運行。

服務器動態加載某些類文件進行編譯。

• 動態編譯的兩種作法:

經過Runtime調用javac,啓動新的進程去操做,這是一種模擬操做。

Runtime run = Runtime.getRuntime();

Process process = run.exec("javac -cp d:/myjava/ HelloWorld.java");

經過JavaCompiler動態編譯

public static int compileFile(String sourceFile){

// 動態編譯

JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

int result = compiler.run(null, null, null,sourceFile);

System.out.println(result==0?" 編譯成功 ":" 編譯失敗 ");

return result;

}

第一個參數: 爲java編譯器提供參數

第二個參數: 獲得 Java 編譯器的輸出信息

第三個參數: 接收編譯器的 錯誤信息

第四個參數: 可變參數(是一個String數組)能傳入一個或多個 Java 源文件

返回值: 0表示編譯成功,非0表示編譯失敗


動態編譯運行好的類:

一、經過Runtime.getRuntime() 運行啓動新的進程運行

Runtime run = Runtime.getRuntime();

Process process = run.exec("java -cp d:/myjava HelloWorld");

// Process process = run.exec("java -cp "+dir+" "+classFile);

二、經過反射運行編譯好的類

public static void runJavaClassByReflect(String dir,String classFile) throws Exception{

try {

URL[] urls = new URL[] {new URL("file:/"+dir)};

URLClassLoader loader = new URLClassLoader(urls);

Class c = loader.loadClass(classFile);

//調用加載類的main方法

c.getMethod("main",String[].class).invoke(null, (Object)new String[]{});

} catch (Exception e) {

e.printStackTrace();

}

}


腳本引擎

 JAVA腳本引擎是從JDK6.0以後添加的新功能。

腳本引擎介紹:

使得 Java 應用程序能夠經過一套固定的接口與各類腳本引擎交互,從而達到在 Java 平臺上調用各類腳本語言的目的。

Java 腳本 API 是連通 Java 平臺和腳本語言的橋樑。

能夠把一些複雜異變的業務邏輯交給腳本語言處理。

得到腳本引擎對象


ScriptEngineManager sem = new ScriptEngineManager();

ScriptEngine engine = sem.getEngineByName("javascript");


Java 腳本 API 爲開發者提供了以下功能:

獲取腳本程序輸入,經過腳本引擎運行腳本並返回運行結果,這是最核心的接口。

Java可使用各類不一樣的實現,從而通用的調用js、python等腳本。

– 使用Js腳本引擎:Rhino

https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino

Rhino 是一種使用 Java 語言編寫的 JavaScript 的開源實現,如今被集成進入JDK 6.0。

– 經過腳本引擎的運行上下文在腳本和 Java 平臺間交換數據。

– 經過 Java 應用程序調用腳本函數。


字節碼操做

就是在運行時對字節碼操做,可讓咱們實現以下功能:

動態生成新的類

動態改變某個類的結構(添加、刪除、修改  新的屬性和方法)


字節碼操做的優點:比反射開銷小,性能高

常見的字節碼類庫以下;

-BECL :是java classworking普遍使用的一種框架,能夠深刻理解JVM彙編語言進行類的操做細節。

基於JVM底層指令操做,比較難學。哈哈


-ASM :輕量級的java字節碼操做框架類庫,直接涉及JVM底層操做和指令


-CGLIB  :是基於ASM的的實現,強大性能高


-Javassist :開源框架,較簡單。

支持類庫和bytecode來操做字節碼,性能相對BECL,ASM性能低下,跟CGLIB差很少,不少開源框架都在使用它,常見。

相關文章
相關標籤/搜索