1. 動態語言
程序運行時,可以改變結構或變量類型。典型的語言:
- Python、ruby、javascript等
- 如下javascript代碼
function test(){ var s = "var a=3;var b=5;alert(a+b);"; eval(s); }
- C,C++,java不是動態語言,java可以稱之為“准動態語言”。但是java有一定的動態性,我們可以利用反射機制、字節碼操作獲得類似動態語言的特性
- java的動態性讓編程的時候更加靈活!
2. 反射機制reflection
- 指的是可以於運行時加載、探知、使用編譯其完全未知的類。
- 程序在運行狀態中,可以動態加載一個只有名稱的類,對於任意一個已加載的類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;
Class c = Class.forName(cn.stu.test.User);
- 加載完類之后,在堆內存中,就產生了一個Class類型的對象(一個類只有一個Class對象),這個對象就包含了完整的類的結構信息,我們可以通過這個對象看到類的結構。這個對象就像一面鏡子,透過這個鏡子看到類的結構,所有,我們形象的稱之為:反射
例1:
1 public class Demo01 { 2 public static void main(String[] args) { 3 String path = "cn.stu.ref.bean.User"; 4 5 try { 6 //方法一 7 Class clazz = Class.forName(path); 8 // 9 /** 10 * ================================== 11 * 對象時表示或封裝一些數據。一個類被加載后, 12 * JVM會創建一個對應該類的Class對象 13 * 類的整個結構信息會會放到對應的Class對象中。 14 * 這個Class對象就是一面鏡子一樣, 15 * 通過這面鏡子我們可以看到對應類的全部信息 16 * =================================== 17 */ 18 System.out.println(clazz.hashCode()); 19 //一個類只對應一個Class對象 20 Class clazz2 = Class.forName(path); 21 System.out.println(clazz2.hashCode()); 22 23 //方法二 24 Class strClass = String.class; 25 Class strClass2 = path.getClass(); 26 System.out.println(strClass==strClass2); 27 28 } catch (ClassNotFoundException e) { 29 e.printStackTrace(); 30 } 31 } 32 }
3. Class類介紹
當一個class被加載,或當加載器(class loader)的defineClass()被JVM調用,JVM便自動產生一個Class對象
Class類似Reflection的根源
針對任何你想動態加載、運行的類,唯有先獲得相應的Class對象
Class類的對象如何獲取?
- 運用getClass()
- 運用Class.forName() --常用
- 運用.class語法
例2:
1 public class Demo02 { 2 3 public static void main(String[] args) { 4 String path = "cn.stu.ref.bean.User"; 5 try { 6 Class clazz = Class.forName(path); 7 8 //獲取類的名稱 9 System.out.println(clazz.getName());//類名+包名 10 System.out.println(clazz.getSimpleName());//類名 11 12 //獲取屬性名 13 Field[] fields = clazz.getFields(); //只能獲得public的屬性 14 Field[] fields2 = clazz.getDeclaredFields();//獲得所有屬性 15 Field field = clazz.getDeclaredField("name");//獲取指定name屬性 16 17 //獲取方法名稱 18 Method[] method = clazz.getDeclaredMethods(); 19 Method m1 = clazz.getDeclaredMethod("getName", null); 20 //如果方法有參,則必須傳遞參數類型對應的class對象 21 Method m2 = clazz.getDeclaredMethod("setName", String.class); 22 23 //獲取構造器 24 Constructor[] constructors = clazz.getDeclaredConstructors(); 25 Constructor c = clazz.getDeclaredConstructor(int.class,int.class,String.class); 26 27 } catch (Exception e) { 28 // TODO Auto-generated catch block 29 e.printStackTrace(); 30 } 31 } 32 }
例3:
1 public class Demo03 { 2 public static void main(String[] args) { 3 String path = "cn.stu.ref.bean.User"; 4 5 try { 6 Class clazz = Class.forName(path); 7 8 //通過反射API調用構造方法,構造對象 9 User u = (User) clazz.newInstance();//其實是調用了User的無參構造方法 10 11 Constructor c = clazz.getDeclaredConstructor(int.class,int.class,String.class); 12 User u2 = (User) c.newInstance(1001,18,"張三"); 13 14 //通過反射API調用普通方法 15 User u3 = (User) clazz.newInstance(); 16 Method method = clazz.getDeclaredMethod("setName", String.class); 17 method.invoke(u3,"李四"); //u3.setName("李四") 18 19 //通過反射API操作屬性 20 User u4 = (User) clazz.newInstance(); 21 Field f = clazz.getDeclaredField("name"); 22 f.setAccessible(true);//不做安全檢查,直接訪問 23 f.set(u4, "二麻子"); //通過反射直接寫屬性 24 String name = u4.getName(); 25 String name2 = (String) f.get(u4); //通過反射直接讀屬性的值 26 } catch (Exception e) { 27 e.printStackTrace(); 28 } 29 } 30 }
4. 反射機制性能問題
- setAccessible
- 啟動和金庸訪問安全檢查的開關,值為true 則指示反射的對象在使用時應該取消java語言訪問檢查,值為false 則指示反射的對象應該實施java語言訪問檢查。並不是true就能訪問,false就不能訪問。
- 禁止安全檢查,可以提高反射的運行速度
- 可以考慮使用: cglib/javaassist字節碼操作
例4:
1 /** 2 * 通過跳過安全檢查,提高反射效率 3 * 三種執行方法的效率差異比較 4 */ 5 public class Demo04 { 6 7 public static void test01(){ 8 User u = new User(); 9 10 long startTime = System.currentTimeMillis(); 11 12 for (int i = 0; i < 1000000000L; i++) { 13 u.getName(); 14 } 15 16 long endTime = System.currentTimeMillis(); 17 System.out.println("普通方法調用,執行10億次,耗時:"+(endTime-startTime)+"ms"); 18 } 19 20 public static void test02() throws Exception{ 21 User u = new User(); 22 Class clazz = u.getClass(); 23 Method m = clazz.getDeclaredMethod("getName", null); 24 // m.setAccessible(true); 25 26 long startTime = System.currentTimeMillis(); 27 28 for (int i = 0; i < 1000000000L; i++) { 29 m.invoke(u, null); 30 } 31 32 long endTime = System.currentTimeMillis(); 33 System.out.println("反射動態方法調用,執行10億次,耗時:"+(endTime-startTime)+"ms"); 34 } 35 36 public static void test03() throws Exception{ 37 User u = new User(); 38 Class clazz = u.getClass(); 39 Method m = clazz.getDeclaredMethod("getName", null); 40 m.setAccessible(true); //不需要執行訪問安全檢查 41 42 long startTime = System.currentTimeMillis(); 43 44 for (int i = 0; i < 1000000000L; i++) { 45 m.invoke(u, null); 46 } 47 48 long endTime = System.currentTimeMillis(); 49 System.out.println("反射動態方法調用,跳過安全檢查,執行10億次,耗時:"+(endTime-startTime)+"ms"); 50 } 51 52 53 public static void main(String[] args) throws Exception { 54 test01(); 55 test02(); 56 test03(); 57 } 58 }
輸出結果:
普通方法調用,執行10億次,耗時:6665ms
反射動態方法調用,執行10億次,耗時:62947ms
反射動態方法調用,跳過安全檢查,執行10億次,耗時:11864ms
5. 反射操作泛型
- java采用泛型擦除的機制來引入泛型。java中的泛型僅僅是給編譯器javac使用的,確保數據的安全性和免去強制類型轉換的麻煩,但是,一旦編譯完成,所有的和泛型有關的類型全部擦除。
- 為了通過反射操操作這些類型以迎合實際開發的需要,java就新增了ParameterzedType,GenericArrayType,TypeVariable和WildcardType幾種類型來代表不能被詭異到Class類中的類型但是又和原始類型齊名的類型。
- ParmterizedType: 表示一種參數化的類型,比如Collection<String>
- GenericArrayType: 表示一種元素類型是參數化類型或者類型變量的數組類型
- TypeVariable:是各種類型變量的公共父接口
- WildcardType:代表一種通配符類型表達式