java反射底層實現原理


1.     什么是反射?

1.1.  問題1

1.對象有編譯類型和運行類型

           Object obj = new java.util.Date();

  編譯類型:Object

  運行類型(其實就是obj對象真實的類型):java.util.Date

  需求:根據對象obj調用Date類中的一個方法,toLocaleString,如何來做?

    obj.toLocaleString()代碼在編譯階段去編譯類型Object中檢查是否有該方法,若沒有,編譯失敗.

 

解決方案1:強制轉換obj為Date類型,前提:必須知道對象的真實類型是什么?

      Date d = (Date)obj;

      d.toLocaleString();//YES

 

如果我不知道obj的真實類型,那又如何去調用toLolcaeString方法. 如何去做?

解決方案2: 使用反射

 

1.2.  問題2

對象使用類描述的,但是Java中一些皆對象,其實所有的類,底層都有一個字節碼對象,用什么來描述這一個個類底層的字節碼對象

 

解決方案 : 使用反射

 

 

 

1.3.  反射

反射:(reflection):在運行時期,動態地去獲取類中的信息(類的信息,方法信息,構造器信息,字段等信息進行操作);

 

一個類中包含的信息有: 構造器,字段,方法

 

如何使用反射描述這些相關的信息

 

Class : 描述類

Method : 描述方法

Constructor :描述構造器

Field :描述字段

 

 

2.     獲取類的Class實例三種方式

在反射操作某一個類之前,應該先獲取這個類的字節碼實例

 

獲取字節碼實例有三種方式

 

注意 :同一個類在JVM的字節碼實例只有一份

 

1. 類名.class 

2. 類的對象.getClass()

3. Class.forName(“類的全限定名”)

(1)     全限定名 = 包名 + 類名

public class User {

    @Test

    public void testName() throws Exception {

         //1.使用類名.class 獲取類的字節碼實例

         Class<User>  clz1 = User.class;

         System.out.println(clz1.toString());

        

         //2.對象.getClass()

         User user = new User();

         Class<?> clz2 =  user.getClass();

         System.out.println(clz2);

        

         //3.Class.forName("全限定名"):用的最多

         Class<?> clz3 = Class.forName("cn.sxt.reflect.User");

         System.out.println(clz3);

    }  

}

 

3.     獲取九大內置類的字節碼實例

對於對象來說,可以直接使用對象.getClass()或者Class.forName(className); 類名.class都可以獲取Class實例.

 

但是我們的基本數據類型,就沒有類的權限定名,也沒有getClass方法.

 

問題: 那么如何使用Class類來表示基本數據類型的Class實例?

 

八大基本數據類型和 void關鍵字都是有 字節碼實例的

byte,short,int,long,char,float,double,boolean ,void關鍵字

 

答 : 數據類型/void.class 即可

 

每個基本數據類型都是包裝類型 如 :int ----Integer包裝類型

注意: 基本數據類型和包裝數據類型底層的字節碼實例是不相同

//獲取8大基本數據類型和void的字節碼實例

//byte,short,int,long,char,float,double,boolean ,void關鍵字

public class BaiscDataTypeClassTest {

    @Test

    public void testName() throws Exception {

         //1.獲取byte的字節碼實例

         Class<?> byteClz = byte.class;

         System.out.println(byteClz);

         //....

         //獲取void關鍵字的字節碼實例

         Class<?> voidClz =void.class;

         System.out.println(voidClz);

        

        

         //所有的基本數據類型都有包裝類型

         //int---Integer

         //int 和Integer 的字節碼是絕對不相等的

         Class<?> intClz1 = int.class;

         Class<?> intClz2 = Integer.class;

         System.out.println(intClz1);

         System.out.println(intClz2);

         System.out.println(intClz1 == intClz2);   

    }

}

 

4.     獲取數組類型的字節碼實例

表示數組的Class實例:

   String[] sArr1 = {"A","C"};

   Class clz = String[].class;//此時clz表示就是一個String類型的一位數組類型

 

所有具有相同元素類型和維數的數組才共享同一份字節碼(Class對象);

   注意:和數組中的元素沒有一點關系.

package cn.sxt.reflect._01.getclass;

 

import static org.junit.Assert.*;

 

import org.junit.Test;

 

//獲取數組類型的字節碼實例

public class ArrayClassTest {

    @Test

    public void testName() throws Exception {

         //定義數組

         int[] arr = {1,2,3};

         Class<int[]> clz1 = (Class<int[]>) arr.getClass();

         System.out.println(clz1);

         Class<int[]>  clz2= int[].class;

         System.out.println(clz2);

        

        

         int[] arr2 = {2,3,4};

         Class<int[]> clz3 = (Class<int[]>) arr2.getClass();

         System.out.println(clz3);

        

        

         System.out.println(clz1 == clz2);//true

         System.out.println(clz1 == clz3);//true

    }

}

 

 

5.     構造函數-Constructor

5.1.  獲取構造函數

類的構函數有 有參數構造函數,無參構造函數,公共構造函數,非公共構造函數,根據不同的構造函數 Class提供了幾種獲取不同構造函數的方法

 

 Constructor<?>[]

getConstructors()
獲取所有的公共構造函數

 Constructor<?>[]

getDeclaredConstructors()
獲取所有的構造函數,和訪問權限無關

 Constructor<T>

getConstructor(Class<?>... parameterTypes)
獲取指定的公共構造函數

parameterTypes : 如果構造函數有參數,傳遞的是參數的字節碼實例

 Constructor<T>

getDeclaredConstructor(Class<?>... parameterTypes)
獲取和訪問權限無關的指定的構造函數

 

//通過字節碼實例獲取構造器

public class ConstructorTest {

    @Test

    public void testName() throws Exception {

        

         //1.獲取Student的字節碼實例

         Class<?>  stuClz = Student.class;

        

         //2.獲取所有的公共構造函數

         Constructor<?>[] cts1 = stuClz.getConstructors();

         for (Constructor<?> ct : cts1) {

             System.out.println(ct);

         }

         System.out.println("----------------------");

         //3.獲取所有的構造函數包括私有的

         Constructor<?>[] cts2 = stuClz.getDeclaredConstructors();

         for (Constructor<?> ct : cts2) {

             System.out.println(ct);

         }

         System.out.println("----------------------");

        

         //4.獲取指定的構造函數(clz.getConstructor(...))只能獲取公共的構造函數

         Constructor<?> ct1 = stuClz.getConstructor();

         System.out.println(ct1);

        

         Constructor<?> ct2 =stuClz.getConstructor(String.class);

         System.out.println(ct2);

         //4.獲取指定的構造函數(clz.getDeclaredConstructor(...))獲取的構造函數和權限沒有關系

         Constructor<?> ct3=stuClz.getDeclaredConstructor(String.class,int.class);

         System.out.println(ct3);

    }

}

 

 

5.2.  調用構造函數創建對象

調用構造器,創建對象

 

Constructor<T>類:表示類中構造器的類型,Constructor的實例就是某一個類中的某一個構造器

常用方法:

public T newInstance(Object... initargs):如調用帶參數的構造器,只能使用該方式.

    參數:initargs:表示調用構造器的實際參數

    返回:返回創建的實例,T表示Class所表示類的類型

如果:一個類中的構造器可以直接訪問,同時沒有參數.,那么可以直接使用Class類中的newInstance方法創建對象.

 public Object newInstance():相當於new 類名();

調用私有的構造器:

 

5.2.1.  Constructor 創建對象的方法

 T

newInstance(Object... initargs)
          使用此 Constructor 對象表示的構造方法來創建該構造方法的聲明類的新實例,並用指定的初始化參數初始化該實例。

 

5.2.2.  Class類中創建對象的方法

如果使用Class直接創建對象,必須保證類中有一個無參數公共構造函數

 T

newInstance()
創建此 Class 對象所表示的類的一個新實例。

 

 

5.2.3.  設置忽略訪問權限

默認執行私有構造函數報如下錯誤

 

 

如果是私有構造方法,反射默認是無法直接執行的,找到父類中AccessibleObject的方法,設置為true,即可忽略訪問權限

 

 

 

 void

setAccessible(boolean flag)
如果執行私有方法,設置忽略訪問權限 為 true即可

 

 

 

//使用構造器創建對象

public class NewInstanceTest {

    @Test

    public void testName() throws Exception {

      

       //1.獲取Student的字節碼實例

       Class<?> clz = Class.forName("cn.sxt.reflect.Student");

      

       //1.1如果類有無參數公共構造函數,直接可以使用類的字節碼實例就創建對象

       Student stu0 = (Student) clz.newInstance();

      

      

       //2.獲取一個參數的構造函數

       Constructor<Student> ct1 = (Constructor<Student>) clz.getConstructor(String.class);

       //2.1.創建對象

       Student stu1 = ct1.newInstance("東方不敗");

      

       //3.獲取私有構造函數並創建對象

       Constructor<Student> ct2 =  (Constructor<Student>) clz.getDeclaredConstructor(String.class,int.class);

       //3.1設置權限可以創建對象

       ct2.setAccessible(true);

       //3.2創建對象

       Student stu2 = ct2.newInstance("西門吹雪",50);

    }

}

 

 

 

6.     操作方法-Method

一個類創建對象以后,一般就要執行對象的方法等等,使用反射操作對象

 

首先要獲取方法,再去執行

 

6.1.  獲取方法和方法的執行

一個類中的方法有很多,無參,有參,靜態,可變參數私有方法,等等,針對不同的方法處理,提供了不同的獲取方案

 

6.1.1.  使用Class 獲取對應的方法

 Method[]

getMethods()
獲取所有的公共方法,包括父類的公共方法

 Method[]

getDeclaredMethods()
獲取所有本類的方法,包括本類的私有方法

 Method

getDeclaredMethod(String name, Class<?>... parameterTypes)
獲取指定方法名稱的方法,和訪問權限無關

 

Name : 指定的方法名稱

parameterTypes : 方法參數的類型

 

 

6.1.2.  Method執行方法

方法獲取以后就需要執行。Method對象中提供方法執行的功能

 Object

invoke(Object obj, Object... args)
執行方法

Obj :如果是對象方法,傳指定的對象,如果是類方法,傳 null

Args: 方法的參數

 

如果方法有返回結果,可以接收

 

6.1.3.  設置忽略訪問權限

如果是私有方法,反射默認是無法直接執行的,找到父類中AccessibleObject的方法,設置為true,即可忽略訪問權限

 

 void

setAccessible(boolean flag)
如果執行私有方法,設置忽略訪問權限 為 true即可

 

 

6.2.  方法操作的代碼

6.2.1.  Student類

package cn.sxt.reflect._03method;

 

import java.util.Arrays;

 

public class Person {

   

    public void hell1() {

         System.out.println("我是無參數無返回值方法");

    }

   

    public String hello2(String name) {

 

         return "你好 :"+name;

    }

   

    private String hello3(String name,intage) {

         return "我是 :"+name+",今年 :"+age;

    }

   

   

   

    public static void  staticMethod(String name) {

         System.out.println("我是靜態方法 :"+name);

    }

   

   

   

    public static void method1(int[] intArr) {

         System.out.println(Arrays.toString(intArr));

    }

   

    public static void method2(String[] strArr) {

         System.out.println(Arrays.toString(strArr));

    }

   

}

 

6.2.2.  測試類

package cn.sxt.reflect._03method;

 

import static org.junit.Assert.*;

 

import java.lang.reflect.Method;

 

import org.junit.Test;

 

//獲取Person類的方法

public class GetMethodTest {

 

    @Test

    public void testName() throws Exception {

         // 1.獲取Person字節碼實例

         Class<Person> clz = Person.class;

         // 2.創建對象

         Person p = clz.newInstance();

 

         // 3.獲取方法(使用反射),獲取所有公共方法,包含父類的公共方法

         Method[] methods1 = clz.getMethods();

         for (Method method : methods1) {

             System.out.println(method);

         }

         System.out.println("------------------------------");

         // 4.獲取自己類中的所有方法(包括私有)

         Method[] methods2 = clz.getDeclaredMethods();

         for (Method method : methods2) {

             System.out.println(method);

         }

         System.out.println("------------------------------");

         // 4.獲取單個指定名稱的方法

         Method method = clz.getMethod("hello2", String.class);

         System.out.println(method);

 

         // 4.1執行方法

         Object res = method.invoke(p, "陸小鳳");

         System.out.println(res);

 

         // 5.獲取私有的方法

         Method hello3 = clz.getDeclaredMethod("hello3", String.class, int.class);

         System.out.println(hello3);

 

         // 5.1設置忽略訪問權限

         hello3.setAccessible(true);

 

         Object res1 = hello3.invoke(p, "葉孤城", 30);

         System.out.println(res1);

 

         // 6.獲取靜態方法

         Method staticMethod = clz.getMethod("staticMethod", String.class);

 

         // 6.1執行靜態方法

         staticMethod.invoke(null, "花滿樓");

 

         // 7.獲取有整數數組參數的方法

         Method method1 = clz.getMethod("method1", int[].class);

         method1.invoke(null, new Object[] {new int[] { 1, 2, 3, 4 }});

 

         // 8.獲取有整數數組參數的方法

         /*

          * 如果反射傳遞的參數是引用類型,底層有一個拆箱的功能,會將數組的元素拆成一個個參數傳遞過來

          * 解決方案: 將數組外面在包裝一層數組,如果拆箱一次,得到還是一個數組

          */

         Method method2 = clz.getMethod("method2", String[].class);

         method2.invoke(null,new Object[] {new String[] {"AA","BB","CC"}});

    }

}

6.2.3.  可變參數的方法執行

如果方法中有可變參數(數組),如果反射傳遞的可變參數是引用類型,底層有一個拆箱的功能,會將數組的元素拆成一個個參數傳遞過來

 

解決方案: 將數組外面在包裝一層數組,如果拆箱一次,得到還是一個數組

package cn.sxt.reflect._03method;

 

import java.util.Arrays;

 

public class Person {

   

    public static void method1(int... intArr) {

         System.out.println(Arrays.toString(intArr));

    }

   

    public static void method2(String...strArr) {

         System.out.println(Arrays.toString(strArr));

    }

}

 

         // 7.獲取有整數數組參數的方法

         Method method1 = clz.getMethod("method1", int[].class);

         method1.invoke(null, new Object[] {new int[] { 1, 2, 3, 4 }});

 

         // 8.獲取有整數數組參數的方法

         /*

          * 如果反射傳遞的參數是引用類型,底層有一個拆箱的功能,會將數組的元素拆成一個個參數傳遞過來

          * 解決方案: 將數組外面在包裝一層數組,如果拆箱一次,得到還是一個數組

          */

         Method method2 = clz.getMethod("method2", String[].class);

         method2.invoke(null,new Object[] {new String[] {"AA","BB","CC"}});

 

7.     操作字段(成員變量)-Field

類中的字段有各種數據類型和各種訪問權限,針對這些情況,反射操作有對應的方法來獲取和處理

 

7.1.  Class中獲取字段方法

 Field[]

getFields()

獲取當前Class所表示類中所有的public的字段,包括繼承的字段.

 Field[]

getDeclaredFields()

獲取當前Class所表示類中所有的字段,不包括繼承的字段.

 Field

getField(String name)

獲取當前Class所表示類中

 Field

getDeclaredField(String name)

:獲取當前Class所表示類中該fieldName名字的字段,不包括繼承的字段.

 

7.2.  Field操作設置值/獲取值

給某個類中的字段設置值和獲取值:

1,找到被操作字段所在類的字節碼

2,獲取到該被操作的字段對象

3,設置值/獲取值

Field類常用方法:

 void setXX(Object obj, XX value) :為基本類型字段設置值,XX表示基本數據類型

 void set(Object obj, Object value) :表示為引用類型字段設置值

  參數:

      obj:    表示字段底層所屬對象,若該字段是static的,該值應該設為null

      value:  表示將要設置的值

-------------------------------------------------------------------------------------

 XX getXX(Object obj) :獲取基本類型字段的值,XX表示基本數據類型

 Object get(Object obj) :表示獲取引用類型字段的值

   參數:

      obj:    表示字段底層所屬對象,若該字段是static的,該值應該設為null

   返回:返回該字段的值.

void

set(Object obj, Object value)

設置引用類型的值,非基本數據類型

Obj: 要設置值得對象

Value : 要設置的值

 

 

package cn.sxt.reflect._04Field;

 

import static org.junit.Assert.*;

 

import java.lang.reflect.Field;

 

import org.junit.Test;

 

public class FieldTest {

   

    @Test

    public void testName() throws Exception {

         //1.獲取People字節碼

         Class<People> clz = People.class;

        

         People p = clz.newInstance();

        

         //2.獲取所有公共字段

         Field[] fields1 = clz.getFields();

         for (Field field : fields1) {

             System.out.println(field);

         }

         System.out.println("---------------------");

         //3.獲取所有字段,和訪問權限無關

         Field[] fields2 = clz.getDeclaredFields();

         for (Field field : fields2) {

             System.out.println(field);

         }

         System.out.println("---------------------");

         //3.獲取指定的公共字段

         Field emialField = clz.getField("emial");

         System.out.println(emialField);

        

         //為字段設置值

         emialField.set(p, "zhagnsan@qq.com");

         System.out.println(p);

         //4.獲取指定所有的字段,和訪問權限無關

         Field nameFiled = clz.getDeclaredField("name");

         System.out.println(nameFiled);

         //設置忽略訪問權限

         nameFiled.setAccessible(true);

         nameFiled.set(p, "張三");

         System.out.println(p);

        

        

         //5 獲取age字段

         Field ageFile = clz.getDeclaredField("age");

         ageFile.setAccessible(true);

         //設置忽略訪問權限

         ageFile.setInt(p, 18);

         System.out.println(p);

        

    }

}

 

8.     Class的其他API方法

//Class字節碼的其他api

    @Test

    public void testName() throws Exception {

        

         //1.獲取UserDaoImpl的字節碼實例

         Class<?> clz = Class.forName("cn.sxt.reflect._05otherapi.UserDaoImpl");

        

         //2.獲取所有的接口

         Class<?>[] interfaces = clz.getInterfaces();

         for (Class<?> intface : interfaces) {

             System.out.println(intface);

         }

        

         //3.獲取全限定名

         System.out.println(clz.getName());

        

         //4.獲取簡單類名

         System.out.println(clz.getSimpleName());

         //5.獲取包

         System.out.println(clz.getPackage().getName());

    }

 

 

9.     JavaBean

問題: 什么是javaBean?

答: JavaBean就是一個個Java類,在java中符合JavaBean特點類才叫做JavaBean

 

9.1.  JavaBean的三個特點

  1. JavaBean類的修飾符必須是public,也就是一個JavaBean必須是一個對應一個類文件
  2. JavaBean必須有無參數公共構造方法(以便於反射直接通過直接通過字節碼實例創建對象)
  3. 3.    JavaBean中的成員變量/字段必須有get/set方法提供對應的屬性

 

 

9.1.1.  JavaBean中的屬性

Java類有成員變量,成員變量絕對不是屬性,JavaBean中的屬性是有get/set方法確定的

 

9.1.1.1.     Get方法確定屬性

public String getName() {

       return name;

}

屬性確定規則 : get方法去掉 get前綴 ,剩余部分 首字母小寫

 

屬性名稱 : name

 

9.1.1.2.     Set方法確定屬性

public void setName(String name) {

       this.name = name;

}

屬性確定規則 : set方法去掉 set前綴 ,剩余部分 首字母小寫

 

屬性名稱 : name

 

如果一個成員變量get/set方法都有確定的屬性就只有一個

 

問題 :get/set方法確定確定的屬性一般不就和成員變量一樣啊,為什么要有屬性了

 

答: 一般情況下,Eclipse工具有自動生成get/set方法的功能,確定的JavaBean的屬性只是恰好和成員變量相同,但是成員變量不是屬性

如下特殊性情況,屬性和成員變量名稱不同

//Filed

private String firstName;

//Filed

private String lastName;

   

//屬性 : fullName

public String getFullName() {

return this.firstName + this.lastName;

}

 

 

 

10. 小結

  1. 1.  理解反射概念?反射能干啥?

(1)     反射: 在jvm運行階段,動態的獲取類的信息(字節碼實例,構造器,方法,字段),動態進行對象的創建,方法執行,字段操作。

2. 反射的常用類

(1)     Class :所有類的字節碼實例的描述

(2)     Constructor :構造器

(3)     Method :方法

(4)     Field :字段

3. JDBC+反射 代碼封裝

(1)     通用 crud 方法

①   思路清晰

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM