Java反射

0.Java內存模型

        首先咱們瞭解一下JVM,什麼是JVM,就是Java的虛擬機。這個JVM的最大特色就是可使你的程序移植到其餘平臺上也能夠運行使用,你能夠簡單的理解成一個進程,程序,只不過他的做用是用來跑你的代碼的。上圖是java的內存模型,咱們主要關注的是方法區,Java堆和Java棧。接下來說講JVM的運行過程:java

假如你寫了一段代碼:Object obj=new Object();數據結構

而後把他運行起來!(真正的代碼不止這一段,這裏只是舉個例子)jvm

首先JVM會啓動,你的代碼會編譯成一個xxx.class文件,而後被類加載器加載進jvm的內存中,而後類Object加載到方法區中,建立了Object類的class對象到堆中,可是請注意這個對象不是new出來的對象,而是類的類型對象,每一個類只有一個class對象,做爲方法區類的數據結構的接口。jvm建立對象前,會先檢查類是否加載,尋找類對應的class對象,若加載好,則爲你的對象分配內存,以後就初始化,也就是執行代碼:new Object()。this

在jvm內存模型中有個叫類裝載子系統,接下來說講這個系統中的類加載器吧!spa

1.類加載器

    A.類的加載
        當程序要使用某個類時,若是該類還未被加載到內存中,則系統會經過加載,鏈接(驗證,準備,解析),初始化三步來實現對這個類進行初始化。對象

     a 加載 
         就是指將編譯後的class文件讀入內存,併爲之建立一個Class對象。
         任何類被使用時系統都會創建一個Class對象
    b 鏈接
        驗證:是否有正確的內部結構,並和其餘類協調一致
        準備:負責爲類的靜態成員分配內存,並設置默認初始化值
        解析:將類的二進制數據中的符號引用替換爲直接引用(在方法中的賦值或者四則運算直接替換成最終值,好比a=1替換爲1)
    c 初始化 
         簡單而言就是new 對象,初始化成員變量等等
     注:簡單的說就是:把.class文件加載到內存裏,並把這個.class文件封裝成一個Class類型的對象。
 B.類的加載時機
    在如下的狀況,會把這個類加載進來
     a. 建立類的實例
     b. 類的靜態變量,或者爲靜態變量賦值
     c. 使用到類的靜態方法
     d. 使用反射方式來強制建立某個類或接口對應的java.lang.Class對象
     e. 初始化某個類的子類
     f. 直接使用java.exe命令來運行某個主類
        
 C: 類加載器的種類(3種)
     a. Bootstrap ClassLoader 根類加載器
         也被稱爲引導類加載器,負責Java核心類的加載
         好比System,String等。在JDK中JRE的lib目錄下rt.jar文件中繼承

     b. Extension ClassLoader 擴展類加載器
        負責JRE的擴展目錄中jar包的加載。
        在JDK中JRE的lib目錄下ext目錄接口

    c. System ClassLoader 系統類加載器
        負責在JVM啓動時加載來自java命令的class文件,以及classpath環境變量所指定的jar包和類路徑。遊戲

2.反射

 A. 反射定義
     a. JAVA反射機制是在運行狀態中,
            對於任意一個類,都可以知道這個類的全部屬性和方法
            對於任意一個對象,都可以調用它的任意一個方法和屬性
        這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。進程

     b.反射技術
        條件:運行狀態
        已知:一個類或一個對象(根本是已知.class文件)
        結果:獲得這個類或對象的全部方法和屬性

     注: 要想解剖一個類,必須先要獲取到該類的字節碼文件對象。而解剖使用的就是Class類中的方法.因此先要獲取到每個字節碼文件對應的Class類型的對象。

B. Class類
     a. Class類及Class對象的瞭解
        要想解剖一個類,必須先了解Class對象。
        閱讀API的Class類得知,Class 沒有公共構造方法。Class 對象是在加載類時由 Java 虛擬機以及經過調用類加載器中的 defineClass 方法自動構造的。
     b. 獲得Class對象
        1. 有三個方法(假設事先寫好了一個類Person)
            方式一: 經過Object類中的getClass()方法
                Person person = new Person();
                Class c1 = person.getClass();
            方式二: 經過 類名.class 獲取到字節碼文件對象(任意數據類型都具有一個class靜態屬性,看上去要比第一種方式簡單)。
                Class c2 = Person.class;
            方式三: 經過Class類中的方法(將類名做爲字符串傳遞給Class類中的靜態方法forName便可)。
                Class c3 = Class.forName("Person");//注意引號內是類的全路徑或者配置文件
            注:第三種和前兩種的區別是:
                    前兩種你必須明確Person類型.
                    後面是指定這種類型的字符串就行.這種擴展更強.我不須要知道你的類.我只提供字符串,按照配置文件加載就能夠了。另外要注意,獲得class對象的操做中可能會有Exception,在main方法中throws Exception出來就能夠了。

獲取一個類的class文件對象代碼部分:

package cn.example.demo1;
/*
 *  獲取一個類的class文件對象的三種方式
 *   1. 對象獲取
 *   2. 類名獲取
 *   3. Class類的靜態方法獲取
 */
public class ReflectDemo {
    public static void main(String[] args)throws ClassNotFoundException {
        //1. 對象獲取
        Person p = new Person();
        //調用Person類的父類的方法 getClass
        Class c = p.getClass();
        System.out.println(c);
        
        //2. 類名獲取
        //每一個類型,包括基本和引用,都會賦予這個類型一個靜態的屬性,屬性名字class
        Class c1 = Person.class;
        System.out.println(c1);
        
        //3. Class類的靜態方法獲取 forName(字符串的類名)包名.類名
        Class c2 = Class.forName("cn.example.demo1.Person");
        System.out.println(c2);
    }
}

下面是Person類的代碼

    package cn.example.demo1; 
    public class Person {
        public String name;
        private int age;
    
        /*static{
            System.out.println("靜態代碼塊");
        }*/
        
        public Person(){
        }
        
        public Person(String name,int age){
            this.name = name;
            this.age = age;
        }
        
        private Person(int age,String name){
            this.name = name;
            this.age = age;
        }
        
        public void eat(){
            System.out.println("人吃飯");
        }
    
        public void sleep(String s, int a,double d){
            System.out.println("人在睡覺"+s+"....."+a+"....."+d);
        }
        private void playGame(){
            System.out.println("人在打遊戲");
        }
    
        public String toString() {
            return "Person [name=" + name + ", age=" + age + "]";
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
        
    }   

1.反射構造方法

        在反射機制中,把類中的成員(構造方法、成員方法、成員變量)都封裝成了對應的類進行表示。其中,構造方法使用類Constructor表示。可經過Class類中提供的方法獲取構造方法:

  返回一個構造方法

      public Constructor<T> getConstructor(Class<?>... parameterTypes)

        獲取public修飾, 指定參數類型所對應的構造方法

      public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)

        獲取指定參數類型所對應的構造方法(包含私有的)

  返回多個構造方法

      public Constructor<?>[] getConstructors()

        獲取全部的public 修飾的構造方法

      public Constructor<?>[] getDeclaredConstructors()

        獲取全部的構造方法(包含私有的)

獲取構造方法的代碼部分

package cn.example.demo1;

import java.lang.reflect.Constructor;

/*
 *  經過反射獲取class文件中的構造方法,運行構造方法
 *  運行構造方法,建立對象
 *    獲取class文件對象
 *    從class文件對象中,獲取須要的成員
 *    
 *  Constructor 描述構造方法對象類
 */
public class ReflectDemo1 {
    public static void main(String[] args) throws Exception {
    
        Class c = Class.forName("cn.example.demo1.Person");
        //使用class文件對象,獲取類中的構造方法
        //  Constructor[]  getConstructors() 獲取class文件對象中的全部公共的構造方法
        /*Constructor[] cons = c.getConstructors();
        for(Constructor con : cons){
            System.out.println(con);
        }*/
        //獲取指定的構造方法,空參數的構造方法
        Constructor con =  c.getConstructor();
        //運行空參數構造方法,Constructor類方法 newInstance()運行獲取到的構造方法
        Object obj = con.newInstance();
        System.out.println(obj.toString());
    }
}

3.反射成員變量

在反射機制中,把類中的成員變量使用類Field表示。可經過Class類中提供的方法獲取成員變量:

  返回一個成員變量

      public Field getField(String name)

        獲取指定的 public修飾的變量

      public Field getDeclaredField(String name)

         獲取指定的任意變量

  返回多個成員變量

      public Field[] getFields()

        獲取全部public 修飾的變量

      public Field[] getDeclaredFields()

        獲取全部的 變量 (包含私有)

獲取成員變量的​​​​​​​代碼部分:

package cn.example.demo1;

import java.lang.reflect.Field;

/*
 *  反射獲取成員變量,並修改值
 *  Person類中的成員String name
 */
public class ReflectDemo5 {
    public static void main(String[] args) throws Exception{
        Class c = Class.forName("cn.itcast.demo1.Person");
        Object obj = c.newInstance();
        //獲取成員變量 Class類的方法 getFields() class文件中的全部公共的成員變量
        //返回值是Field[]    Field類描述成員變量對象的類
        /*Field[] fields = c.getFields();
        for(Field f : fields){
            System.out.println(f);
        }*/
        
        //獲取指定的成員變量 String name
        //Class類的方法  Field getField(傳遞字符串類型的變量名) 獲取指定的成員變量
        Field field = c.getField("name");
       
        //Field類的方法 void set(Object obj, Object value) ,修改爲員變量的值
        //Object obj 必須有對象的支持,  Object value 修改後的值
        field.set(obj,"王五");
        System.out.println(obj);
        
    }
}

4.反射成員方法

在反射機制中,把類中的成員方法使用類Method表示。可經過Class類中提供的方法獲取成員方法:

  返回獲取一個方法:

      public Method getMethod(String name, Class<?>... parameterTypes)

             獲取public 修飾的方法

      public Method getDeclaredMethod(String name, Class<?>... parameterTypes)

             獲取任意的方法,包含私有的

        參數1: name 要查找的方法名稱; 參數2: parameterTypes 該方法的參數類型

  返回獲取多個方法:

      public Method[] getMethods()

        獲取本類與父類中全部public 修飾的方法

      public Method[] getDeclaredMethods()

        獲取本類中全部的方法(包含私有的)

  獲取成員方法的代碼演示:

package cn.example.demo1;

import java.lang.reflect.Method;

/*
 *  反射獲取成員方法並運行
 *  public void eat(){}
 */
public class ReflectDemo6 {
    public static void main(String[] args) throws Exception{
        Class c = Class.forName("cn.itcast.demo1.Person");
        Object obj = c.newInstance();
        //獲取class對象中的成員方法
        // Method[] getMethods()獲取的是class文件中的全部公共成員方法,包括繼承的
        // Method類是描述成員方法的對象
        /*Method[] methods = c.getMethods();
        for(Method m : methods){
            System.out.println(m);
        }*/
        
        //獲取指定的方法eat運行
        // Method getMethod(String methodName,Class...c)
        // methodName獲取的方法名  c 方法的參數列表
        Method method = c.getMethod("eat");
        //使用Method類中的方法,運行獲取到的方法eat
        //Object invoke(Object obj, Object...o)
        method.invoke(obj);
    }
}
 

5.反射配置文件運行類中的方法

  經過反射配置文件,運行配置文件中指定類的對應方法

讀取Peoperties.txt文件中的數據,經過反射技術,來完成Person對象的建立

Peoperties.txt文件內容:

#className=cn.example.demo3.Student
#methodName=study
className=cn.example.demo3.Person
methodName=eat
#className=cn.example.demo3.Worker
#methodName=job

反射配置文件運行類中的方法​​​​​​​代碼部分:

package cn.example.demo3;

import java.io.FileReader;
import java.lang.reflect.Method;
import java.util.Properties;

/*  *  調用Person方法,調用Student方法,調用Worker方法  *  類不清楚,方法也不清楚  *  經過配置文件實現此功能  *    運行的類名和方法名字,以鍵值對的形式,寫在文本中  *    運行哪一個類,讀取配置文件便可  *  實現步驟:  *    1. 準備配置文件,鍵值對  *    2. IO流讀取配置文件  Reader  *    3. 文件中的鍵值對存儲到集合中 Properties  *        集合保存的鍵值對,就是類名和方法名  *    4. 反射獲取指定類的class文件對象  *    5. class文件對象,獲取指定的方法  *    6. 運行方法  */ public class Test {     public static void main(String[] args) throws Exception{         //IO流讀取配置文件         FileReader r = new FileReader("config.properties");         //建立集合對象         Properties pro = new Properties();         //調用集合方法load,傳遞流對象         pro.load(r);         r.close();         //經過鍵獲取值         String className = pro.getProperty("className");         String methodName = pro.getProperty("methodName");         //反射獲取指定類的class文件對象         Class c = Class.forName(className);         Object obj = c.newInstance();         //獲取指定的方法名         Method method = c.getMethod(methodName);         method.invoke(obj);     } }  

相關文章
相關標籤/搜索