Java:反射機制學習筆記

1、反射機制

一、概述

反射機制:將類的各個組成部分封裝成其餘對象,在運行狀態中,能夠動態獲取類信息和調用類對象的方法。程序員

二、優缺點

  • 優勢
    • 能夠在程序運行過程當中,操做類的組成部分。
    • 能夠解耦,提升程序的可擴展性。
  • 缺點
    • 產生一系列的解釋操做,性能較差。

三、類加載的過程

java語言有着」一處編譯,到處運行「的優良特色,可以很好地適應不一樣的平臺,過程大體以下:數組

  • 咱們經過javac命令將.java文件編譯,會在磁盤上生成不面向平臺的字節碼文件.class
  • 類加載器經過一系列的操做,如加載、鏈接和初始化以後,將.class文件加載進內存中。
  • 將.class字節碼文件表明的靜態存儲結構轉化爲方法區的運行時數據結構。
  • 類加載時還會再內存中建立一個java.lang.Class的對象,該對象包含類的信息,做爲程序從方法區訪問各類數據的接口。

2、獲取Class對象的三種方式

這個Class對象包含類的全部信息,也就是說,咱們得到了這個Class類的對象,就能夠訪問到JVM中的這個類。那麼,如何獲取呢?主要有如下三種方式。安全

一、Class.forName("全類名")

經過Class的靜態方法forName("全類名"),返回和全類名對應類的Class對象。數據結構

//Class.forName("全類名");
    Class cls1 = Class.forName("com.my.base.Person");
    System.out.println(cls1);
  • 多用於配置文件,將類名定義在配置文件中,即可以動態讀取文件,加載類。
  • 全類名指:完整包名.類名,若是找不到,會拋出ClassNotFoundException的異常。

二、類名.class

經過類名的class屬性獲取,返回該類對應的Class對象。性能

//類名.class;
    Class cls2 = Person.class;
    System.out.println(cls2);
  • 該方式在程序編譯階段就能夠檢查須要訪問的Class對象是否存在,相對來講更加安全。
  • 該方式無需調用方法,程序性能方面相對較好。

三、對象.getClass()

getClass()方法在Object中定義,返回該對象所屬類對應得Class對象。code

//對象.getClass();
    Person p = new Person();
    Class cls3 = p.getClass();
    System.out.println(cls3);

同一個字節碼文件.class在一次程序運行過程當中,只會被加載一次,不論經過哪一種方式獲取的Class對象都是同一個。視頻

// == 比較三個對象,都是true

    System.out.println(cls1 == cls2);
    System.out.println(cls1 == cls3);
    System.out.println(cls3 == cls2);

3、反射相關的方法

Class對象包含着類的全部信息,而這些信息被封裝在java.lang.reflect包下,成了一個個的類,不徹底統計以下:對象

Method Field Annotation Constructor Package Modifier Parameter
方法類 字段類 註解類 構造器類 包類 修飾符類 方法參數類

Class類中提供了相應的方法,獲取這些信息,因爲方法衆多,具體的還須要參看JDK官方文檔。接口

我在這裏總結幾點通用的:

  • 調用對應的getXxx一般是得到對應public修飾的單個信息,getDeclaredXxx則不考慮修飾符,找不到,則往超類找。

  • 調用對應的getXxxs一般得到對應的public修飾信息的數組,getDeclaredXxxs則不考慮修飾符,找不到,則往超類找。

  • 上面二者遇到參數的狀況不一樣,若是自己用於獲取public修飾信息的方法強行去獲取達不到的權限,則會拋出異常:
    • 對於Field而言,括號內是: "參數名"
    • 對於Method而言,括號內是: "方法名",參數類型對應的類
    • 對於Constructor而言,括號內是:"參數對應的類"
  • Field類中有獲取值的方法:Object get(Object obj),也有設置值的方法:void set(Object obj, Object value)
  • Method類能夠調用方法:Object invoke(Object obj, Object... args)
  • Constructor類能夠建立實例:T newInstance(Object... initargs),若是類中有無參構造器,能夠直接利用Class類中的newInstance()方法建立實例。

  • 在反射中,沒有什麼是私有的,若是有,那就使用xxx.setAccessible(true)暴力破解。
  • 判斷修飾符時,getModifiers()返回的時int值,是各個修飾符表示數的和,能夠用位運算判斷,(xx.getModifiers()&Modifier.STATIC)!=0表示存在static修飾符。

ps:確定有遺漏的API,到時候用的時候,翻翻API就完事了。

4、Demo×2

一、嘗試本身寫一個clone()方法

package com.my.reflect.practice;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

/**
 * @auther Summerday
 */

@SuppressWarnings("unchecked")
public class ImplementClone {
    public Object clone(Object o) throws Exception{
        //獲取對象的實際類型
        Class<Object> clz = (Class<Object>) o.getClass();

        //獲取全部構造方法
        Constructor<Object>[] cs = (Constructor<Object>[]) clz.getDeclaredConstructors();
        //任取一個構造方法
        Constructor<Object> c = cs[0];

        //防止取出構造方法爲私有
        c.setAccessible(true);

        //該構造方法參數有or無?

        //獲取參數類型
        Class[] ps = c.getParameterTypes();
        //存儲參數的數組
        Object[] os = new Object[ps.length];

        for(int i = 0;i<ps.length;i++){
            //判斷是否爲基本類型
            if(ps[i].isPrimitive()){
                if(ps[i] == boolean.class)
                    os[i] = false;
                else if(ps[i] == char.class)
                    os[i] = '\u0000';
                else
                    os[i] = 0;
            }else{
                os[i] = null;
            }
        }
        //執行構造方法建立對象
        Object obj = c.newInstance(os);

        //獲取屬性數組
        Field[] fs = clz.getDeclaredFields();

        for (Field f : fs){

            //若是final修飾則返回,沒法修改
            if((f.getModifiers()&Modifier.FINAL)!=0){
                continue;
            }
            //暴力破解
            f.setAccessible(true);

            //取出原屬性值
            Object value = f.get(o);

            //將取出的屬性值賦值給新對象的屬性
            f.set(obj, value);
            
        }
        return obj;
    }
}

二、利用配置文件動態加載

package com.my.reflect.practice;

/**
 * @auther Summerday
 */

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

/**
 *
 *     參考黑馬程序員教學視頻
 *
 * 實現:一、配置文件  二、反射
 *
 * 1、將須要建立的對象的全類名和須要執行的方法定義在配置文件中
 *
 * 2、在程序中加載讀取配置文件
 *
 * 3、使用反射來加載類文件進內存
 *
 * 4、建立對象
 *
 * 5、執行方法
 *
 */
public class ReflectTest {

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

        //一、加載配置文件

        //1.1 建立Properties對象
        Properties pro = new Properties();

        //1.2 加載配置文件,轉換爲一個集合

        //1.2.1獲取class目錄下的配置文件

        //建立類加載器
        ClassLoader classLoader = ReflectTest.class.getClassLoader();

        InputStream resourceAsStream = classLoader.getResourceAsStream("pro.properties");
        pro.load(resourceAsStream);

        //二、獲取配置文件中定義的數據
        String className = pro.getProperty("className");
        String methodName = pro.getProperty("methodName");

        //三、加載該類進內存
        Class cls = Class.forName(className);

        //四、建立對象
        Object obj = cls.newInstance();

        //五、獲取方法
        Method method = cls.getMethod(methodName);

        //六、執行方法
        method.invoke(obj);

    }
}

之後會有愈來愈多真實的場景須要用到反射這項靈魂技術,總之,好好看,好好學。

相關文章
相關標籤/搜索