反射機制詳解

0x0一、類的加載流程

當程序要使用某個類時,若是該類還未被加載到內存中,則系統會經過加載,鏈接,初始化三步來實現對這個類進行初始化。java

①加載
就是指將class文件讀入內存,併爲之建立一個Class對象。
任何類被使用時系統都會創建一個Class對象。數組

②鏈接
驗證 是否有正確的內部結構,並和其餘類協調一致
準備 負責爲類的靜態成員分配內存,並設置默認初始化值 static{}語句塊
解析 將類的二進制數據中的符號引用替換爲直接引用code

③初始化類型
建立類的實例
訪問類的靜態變量,或者爲靜態變量賦值
調用類的靜態方法
使用反射方式來強制建立某個類或接口對應的java.lang.Class對象
初始化某個類的子類
直接使用java.exe命令來運行某個主類
.....對象

0x02.反射機制入門

經過上方閱讀,當咱們的程序在運行後,第一次使用某個類的時候,會將此類的class文件讀取到內存,併爲此類建立一個Class對象blog

類的加載時機

1. 建立類的實例。
2. 類的靜態變量,或者爲靜態變量賦值。
3. 類的靜態方法。
4. 使用反射方式來強制建立某個類或接口對應的java.lang.Class對象。
5. 初始化某個類的子類。
6. 直接使用java.exe命令來運行某個主類。

public class Test {
    public static void main(String[] args) throws Exception{
        // 類的加載時機
        //  1. 建立類的實例。
        //  Student stu = new Student();

        // 2. 類的靜態變量,或者爲靜態變量賦值。
        // Person.country = "中國";

        // 3. 類的靜態方法。
        // Person.method();

        // 4. 使用反射方式來強制建立某個類或接口對應的java.lang.Class對象。
        // Class<?> c = Class.forName("com.itheima.demo1_類的加載.Student");

        //  5. 初始化某個類的子類。
        // Zi zi = new Zi();

        // 6. 直接使用java.exe命令來運行某個主類。
    }
}

讀取到內存,併爲次類建立一個Class對象;這時候咱們反射的內容開始了,咱們第一步,就是經過反射,去獲取class對象接口

0x03. 獲取Class對象並建立對象

方式1: 經過`類名.class`得到
方式2:經過`對象名.getClass()`方法得到
方式3:經過Class類的靜態方法得到:` static Class forName("類全名")`
    * 每個類的Class對象都只有一個。

下面是得到到class對象,這是反射的三種方法,最多見的,是forName獲取class對象內存

/*
    反射獲取class對象的三種方式
 */
public class Demo3 {
    public static void main(String[] args) throws ClassNotFoundException {

        // 一、類名.class
        Class<Student> student1 = Student.class;
        System.out.println(student1);

        // 二、對象名.getClass
        Student student = new Student();
        Class<? extends Student> student2 = student.getClass();
        System.out.println(student2);

        // 三、 Class.forName("")
        Class<?> student3 = Class.forName("com.itheima_反射.Student");
        System.out.println(student3);
    }
}

這時候咱們須要使用 newInstance() 方法來建立對象

因爲返回值是 OBject類型的,因此咱們須要轉型get

public class Demo3 {
    public static void main(String[] args) throws Exception {
        // 一、獲取Class對象
        Class<?> student = Class.forName("com.itheima_反射.Student");
        // 二、實例化對象
        Student o = (Student) student.newInstance();

    }
}

而這一段代碼,能夠變爲it

public class Demo3 {
    public static void main(String[] args) throws Exception {
        // 等價於  Student student = new Student();
        Student student = (Student) Class.forName("com.itheima_反射.Student").newInstance();
    }
}

0x0四、操做構造方法

由於Student中是有構造方法的,而這邊newInstance是默認調用無參構造方法;io

反射之操做構造方法的目的
    * 得到Constructor對象來建立類的對象。

Constructor類概述
    * 類中的每個構造方法都是一個Constructor類的對象

1. Constructor getConstructor(Class... parameterTypes)
        * 根據參數類型得到對應的Constructor對象。
        * 只能得到public修飾的構造方法
 2. Constructor getDeclaredConstructor(Class... parameterTypes)
        * 根據參數類型得到對應的Constructor對象
    	* 能夠是public、protected、(默認)、private修飾符的構造方法。
 3. Constructor[] getConstructors()
        得到類中的全部構造方法對象,只能得到public的
 4. Constructor[] getDeclaredConstructors()
        得到類中的全部構造方法對象
    	能夠是public、protected、(默認)、private修飾符的構造方法。

1. T newInstance(Object... initargs)
 	根據指定的參數建立對象
2. void setAccessible(true)
   設置"暴力反射"——是否取消權限檢查,true取消權限檢查,false表示不取消
import java.lang.reflect.Constructor;

public class Demo {
    public static void main(String[] args) throws Exception {
        // 一、獲取class對象
        Class<?> student = Class.forName("org.test.反射01.Student");
        // 二、獲取Student類的帶有三個參數的構造方法,而且參數的類型順序爲:String,String,int
        Constructor<?> user = student.getConstructor(String.class,String.class, int.class);
        // 三、傳入參數,並使用 newInstance() 實例化對象,並向下轉型
        Student o = (Student)user.newInstance("吳迪", "男", 19);
        System.out.println(o);

    }
}

這是構造方法的public修飾的獲取,獲取private修飾的構造方法,須要先setAccessible(true),而後再執行

具體我就不細說了,畢竟用法都同樣

0x05. 操做成員方法

* Method getMethod(String name,Class...args);
      * 根據方法名和參數類型得到對應的成員方法對象,只能得到public的
* Method getDeclaredMethod(String name,Class...args);     掌握
       * 根據方法名和參數類型得到對應的成員方法對象,包括public、protected、(默認)、private的
* Method[] getMethods();
        * 得到類中的全部成員方法對象,返回數組,只能得到public修飾的且包含父類的
* Method[] getDeclaredMethods();    掌握
         * 得到類中的全部成員方法對象,返回數組,只得到本類的,包括public、protected、(默認)、private的

Method對象經常使用方法
*  Object invoke(Object obj, Object... args)
    * 調用指定對象obj的該方法
    * args:調用方法時傳遞的參數
*  void setAccessible(true)
    設置"暴力訪問"——是否取消權限檢查,true取消權限檢查,false表示不取消

咱們先看看調用沒有傳參的成員方法
Studeng.java文件:

public class Student {
    public void show1(){
        System.out.println("public 修飾的show1方法,無參數...");
    }

    public void show1(String str,int num){
        System.out.println("public 修飾的show1方法,2個參數...");
        System.out.println("str:"+str+",num:"+num);
    }

    public void show2(){
        System.out.println("public 修飾的show2方法...");
    }

    private void show3(){
        System.out.println("private 修飾的show3方法...");
    }
}

咱們這邊嘗試去調用show1方法

import java.lang.reflect.Method;

public class Demo {
    public static void main(String[] args) throws Exception {

        //獲取class對象
        Class<?> clzz = Class.forName("org.test.反射01.Student");
        //實例化對象(默認調用無參構造方法)
        Object o = clzz.newInstance();
        // 經過class對象,調用getMethod方法去獲取名爲show1()的成員方法,並無傳參
        Method showM = clzz.getMethod("show1");
        showM.invoke(o);


    }
}

那咱們再去調用另一個show1()方法

import java.lang.reflect.Method;

public class Demo {
    public static void main(String[] args) throws Exception {
        //獲取class對象
        Class<?> clzz = Class.forName("org.test.反射01.Student");
        //實例化對象(默認調用無參構造方法)
        Object o = clzz.newInstance();
        // 經過class對象,調用getMethod方法去獲取名爲show1()的成員方法,並無傳參
        Method showM = clzz.getMethod("show1",String.class,int.class);
        showM.invoke(o,"吳一凡",17);


    }
}

0x05. 操做成員變量

反射之操做成員變量的目的
    * 經過Field對象給對應的成員變量賦值和取值

Field類概述
    * 每個成員變量都是一個Field類的對象。

經過反射獲取類的成員變量

Class類中與Field相關的方法
* Field getField(String name);
    *  根據成員變量名得到對應Field對象,只能得到public修飾
* Field getDeclaredField(String name);
    *  根據成員變量名得到對應Field對象,包括public、protected、(默認)、private的
* Field[] getFields();
    * 得到全部的成員變量對應的Field對象,只能得到public的
* Field[] getDeclaredFields();
    * 得到全部的成員變量對應的Field對象,包括public、protected、(默認)、private的

經過反射訪問成員變量

Field對象經常使用方法
void  set(Object obj, Object value) 
void setInt(Object obj, int i) 	
void setLong(Object obj, long l)
void setBoolean(Object obj, boolean z) 
void setDouble(Object obj, double d) 

Object get(Object obj)  
int	getInt(Object obj) 
long getLong(Object obj) 
boolean getBoolean(Object ob)
double getDouble(Object obj) 

void setAccessible(true);暴力反射,設置爲能夠直接訪問私有類型的屬性。
Class getType(); 獲取屬性的類型,返回Class對象。

setXxx方法都是給對象obj的屬性設置使用,針對不一樣的類型選取不一樣的方法。

getXxx方法是獲取對象obj對應的屬性值的,針對不一樣的類型選取不一樣的方法。

package org.test.反射01;

import java.lang.reflect.Field;

public class Demo {
    public static void main(String[] args) throws Exception {
        //獲取class對象
        Class<?> clzz = Class.forName("org.test.反射01.Student");
        //實例化對象(默認調用無參構造方法)
        Object stu = clzz.newInstance();
        // 經過class對象,調用getDeclaredField,獲取被private修飾名爲sex的屬性
        Field sexF = clzz.getDeclaredField("sex");
        // 暴力反射
        sexF.setAccessible(true);
        //設置值
        sexF.set(stu,"李四");
        // 獲取
        System.out.println(sexF.get(stu));// 李四



    }
}

例子:本地命令執行

Runtime.getRuntime().exec("calc.exe");

怎麼經過反射機制調用呢?
首先咱們要了解運行流程

也就是說,這邊的Runtime.getRuntime(),其實就是Runtime runtime = new Runtime()
而後再runtime.exec()去執行命令

import java.lang.reflect.Method;

public class Demo {

    public static void main(String[] args) throws Exception {

        //獲取Runtime的class對象
        Class clazz = Class.forName("java.lang.Runtime");
        // 獲取exec的方法,並有一個參數
        Method execMethod = clazz.getMethod("exec", String.class);
        // 獲取getRuntime方法
        Method getRuntimeMethod = clazz.getMethod("getRuntime");
        /*
            執行 getRuntime() 方法,返回runtime對象
            
        */
        Object runtime = getRuntimeMethod.invoke(clazz);
        //最後執行exec方法
        execMethod.invoke(runtime, "calc.exe");
    }
}

再整合一下,就更短了

import java.lang.reflect.Method;

public class Demo {

    public static void main(String[] args) throws Exception {
//        Class clazz = Class.forName("java.lang.Runtime");
//        clazz.getMethod("exec", String.class).invoke(clazz.getMethod("getRuntime").invoke(clazz), "calc.exe");
    }
}

invoke 的做用是執行方法,它的第一個參數是:
若是這個方法是一個普通方法,那麼第一個參數是類對象
若是這個方法是一個靜態方法,那麼第一個參數是

相關文章
相關標籤/搜索