1. 程式人生 > >Java 基礎篇之反射

Java 基礎篇之反射

反射

使用反射獲取程式執行時的物件和類的真實資訊。

獲取 Class 物件

每個類被載入之後,系統會為該類生成一個對應的 Class 物件,通過該 Class 物件可以訪問到 JVM 中的這個類。

  • 使用 Class 類的 forName(String clazzName) 靜態方法。字串引數的值是某個類的全限定類名,必須包含完整的包名

  • 呼叫某個類的 class 屬性

  • 呼叫某個物件的 getClass() 方法。該方法是 java.lang.Object 類中的一個方法,所有的 Java 物件都可以呼叫,返回該物件所屬類對應的 Class 物件

獲取 Class 物件中資訊

Class 類提供了大量的例項方法來獲取該 class 物件所對應的類的詳細資訊。更多請參考 API。

import java.lang.reflect.*;
import java.lang.annotation.*;

public class ClassTest {
    private ClassTest() {
    }

    public ClassTest(String name) {
        System.out.println("執行有引數的構造器");
    }

    public void info() {
        System.out.println("執行無引數的info方法");
    }

    public void info(String str) {
        System.out.println("執行有引數的info方法" + ",其 str 引數值: " + str);
    }

    class Inner {
    }

    public static void main(String[] args) throws Exception {
        Class<ClassTest> clazz = ClassTest.class;
      
        // 獲取 clazz 物件所對應類的全部構造器
        Constructor<?>[] ctros = clazz.getDeclaredConstructors();
        System.out.println("ClassTest 的全部構造器如下: ");
        for (Constructor c : ctros) {
            System.out.println(c);
        }
      
        // 獲取 clazz 物件所對應類的全部 public 構造器
        Constructor<?>[] publicCtors = clazz.getConstructors();
        System.out.println("ClassTest的全部public構造器如下:");
        for (Constructor c : publicCtors) {
            System.out.println(c);
        }
      
        // 獲取 clazz 物件所對應類的全部 public 方法
        Method[] mtds = clazz.getMethods();
        System.out.println("ClassTest 的全部 public 方法如下: ");
        for (Method md : mtds) {
            System.out.println(md);
        }
      
        // 獲取 clazz 物件所對應類的指定方法
        System.out.println("ClassTest 裡帶一個字串引數的 info 方法為:" + clazz.getMethod("info", String.class));
      
        // 獲取 clazz 物件所對應類的全部註解
        Annotation[] anns = clazz.getAnnotations();
        System.out.println("ClassTest 的全部 Annotation 如下: ");
        for (Annotation an : anns) {
            System.out.println(an);
        }
      
        // 獲取 clazz 物件所對應類的全部內部類
        Class<?>[] inners = clazz.getDeclaredClasses();
        System.out.println("ClassTest 的全部內部類如下: ");
        for (Class c : inners) {
            System.out.println(c);
        }
      
        // 使用 Class.forName() 方法載入 ClassTest 的 Inner 內部類
        Class inClazz = Class.forName("ClassTest$Inner");
      
        // 訪問該類所在的外部類
        System.out.println("inClazz 對應類的外部類為: " + inClazz.getDeclaringClass());

        System.out.println("ClassTest 的包為:" + clazz.getPackage());
        System.out.println("ClassTest 的父類為:" + clazz.getSuperclass());

    }
}

應用

Class 物件可以獲得對應類的方法(由 Method 表示)、構造器(由 Constructor 表示)、成員變數(由 Field 物件表示),且這個三個類都實現了 java.lang.reflect.Member 介面。程式可以通過 Method 物件來執行對應的方法,通過 Constructor 物件來呼叫對應的構造器建立例項,通過 Field 物件直接訪問並修改物件的成員變數值。

建立物件

  • 使用 Class 物件的 newInstance() 方法來建立 Class 物件對應類的例項。要求該 Class 物件的對應類有預設構造器

  • 先使用 Class 物件獲取指定的 Constructor 物件,再呼叫 Constructor 物件的 newInstance() 方法來建立該 Class 物件對應類的例項。這種方式可以選擇使用指定的構造器來建立例項

方式一

實現了一個簡單的物件池,該物件池會根據配置檔案讀取 key-value 對,然後建立這些物件並放入 HashMap 中

import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class ObjectPoolFactory {
    private Map<String, Object> objectPool = new HashMap<>();

    private Object createObject(String clazzName) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        Class<?> clazz = Class.forName(clazzName);
        // 使用 Class 物件對應的類的預設構造器
        return clazz.newInstance();
    }

    public void initPool(String fileName) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        try (
                FileInputStream fis = new FileInputStream(fileName)
        ) {
            Properties props = new Properties();
            props.load(fis);
            for ( String name: props.stringPropertyNames()) {
                objectPool.put(name, createObject(props.getProperty(name)));
            }
        } catch (IOException ex) {
            System.out.println("讀取" + fileName + "異常");
        }
    }

    public Object getObject(String name) {
        return objectPool.get(name);
    }

    public static void main(String[] args) throws Exception{
        ObjectPoolFactory pf = new ObjectPoolFactory();
        pf.initPool("obj.txt");
        System.out.println(pf.getObject("a"));
        System.out.println(pf.getObject("b"));
    }
}
/*
obj.txt 內容:

a=java.util.Date
b=javax.swing.JFrame
*/
方式二
import java.lang.reflect.Constructor;

public class CreateJFrame {
    public static void main(String[] args) throws Exception {
        Class<?> jframeClazz = Class.forName("javax.swing.JFrame");
        // 選擇使用指定的構造器
        Constructor ctor = jframeClazz.getConstructor(String.class);
        Object obj = ctor.newInstance("測試視窗");
        System.out.println(obj);
    }
}

呼叫方法

每個 Method 物件對應一個方法,獲得 Method 物件後,就可以通過該 Method 來呼叫它對應的方法。

Method 包含一個 invoke() 方法,該方法的簽名如下:

  • Object invoke(Object obj, Object... args):該方法中的 obj 是執行方法的主調(即類的例項物件),後面的 args 是執行該方法的實參

下面是對之前的物件工廠池進行增強,允許在配置檔案中增加配置物件的成員變數值,物件池工廠會讀取該物件配置的成員變數值,並利用該物件對應的 setter 方法設定成員變數的值:

import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class ExtendedObjectPoolFactory {
    // 定義一個物件池,前面是物件名,後面是實際的物件
    private Map<String, Object> objectPool = new HashMap<>();
    private Properties config = new Properties();
    // 從指定檔案中初始化 Properties 物件
    public void init(String fileName) {
        try (
                FileInputStream fis = new FileInputStream(fileName);
        ) {
            config.load(fis);
        } catch (IOException ex) {
            System.out.println("讀取" + fileName + "異常");
        }
    }
    // 定義建立物件的方法
    private Object createObject(String clazzName) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        // 根據字串來獲取對應的 Class 物件
        Class<?> clazz = Class.forName(clazzName);
        // 使用 clazz 對應類的預設構造器建立例項
        return clazz.newInstance();
    }

    // 初始化物件池
    public void initPool() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        for (String name : config.stringPropertyNames()) {
            if (!name.contains("%")) {
                objectPool.put(name, createObject(config.getProperty(name)));
            }
        }
    }

    // 根據屬性檔案來呼叫指定物件的 setter 方法
    public void initProperty() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        for (String name : config.stringPropertyNames()) {
            if (name.contains("%")) {
                String[] objAndProp = name.split("%");
                Object target = getObject(objAndProp[0]);
                String mtdName = "set" + objAndProp[1].substring(1);
                // 通過 target 的 getClass() 獲取它的實現類所對應的 Class 物件
                Class<?> targetClass = target.getClass();
                // 獲取希望呼叫的 setter 方法
                Method mtd = targetClass.getMethod(mtdName, String.class);
                // 通過 Method 的 invoke 方法執行 setter 方法
                mtd.invoke(target, config.getProperty(name));
            }
        }
    }

    public Object getObject(String name) {
        // 從 objectPool 中取出指定 name 對應的物件
        return objectPool.get(name);
    }

    public static void main(String[] args) throws Exception {
        ExtendedObjectPoolFactory epf = new ExtendedObjectPoolFactory();
        epf.init("extObj.txt");
        epf.initPool();
        epf.initProperty();
        System.out.println(epf.getObject("a"));
    }
}

/* extObj.txt 內容

a=java.util.Date
b=javax.swing.JFrame
# set the title of a
a%title=Test Title
*/

PS:當通過 Method 的 invoke() 方法來呼叫對應的方法時,Java 會要求程式必須有呼叫該方法的許可權。如果需要呼叫某個物件的 private 方法,則可以先呼叫 Method 物件的如下方法:

  • setAccessible(boolean flag):值為 true,表示該 Method 在使用時取消訪問許可權檢查

訪問成員變數值

Filed 提供如下兩組方法來讀取或設定成員變數值:

  • getXxx(Object obj):獲取 obj 物件的該成員變數的值。此處的 Xxx 對應 8 中基本型別。如果成員變數的型別是引用型別,則直接使用 get

  • setXxx(Object obj, Xxx val):將 obj 物件的成員變數值設為 val 值。此處的 Xxx 對應 8 中基本型別。如果成員變數的型別是引用型別,則直接使用 set

public class Person {
    private String name;
    private int age;

    public String toString() {
        return "Person[name:" + name + ", age:" + age + "]";
    }
}

import java.lang.reflect.Field;

public class FieldTest {
    public static void main(String[] args) throws Exception {
        Person p = new Person();
        Class<Person> personClazz = Person.class;
        Field nameField = personClazz.getDeclaredField("name");
        nameField.setAccessible(true);
        nameField.set(p, "crazy");
        Field ageField = personClazz.getDeclaredField("age");
        ageField.setAccessible(true);
        ageField.setInt(p, 30);
        System.out.println(p);
    }
}

泛型在反射中的應用

在反射中使用泛型,反射生成的物件就不需要進行強制型別轉換。

import java.util.Date;

public class CrazyitObjectFactory {
    public static <T> T getinstance(Class<T> cls) {
        try {
            return cls.newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static void main(String[] args) {
        // 獲取例項後無需進行型別轉換
        Date d = CrazyitObjectFactory.getinstance(Date.class);
    }
}

歡迎關注我的公眾號

相關推薦

Java 基礎反射

反射 使用反射獲取程式執行時的物件和類的真實資訊。 獲取 Class 物件 每個類被載入之後,系統會為該類生成一個對應的 Class 物件,通過該 Class 物件可以訪問到 JVM 中的這個類。 使用 Class 類的 forName(String clazzName) 靜態方法。字串引數的值是某個類

java基礎nio與aio

sre 非阻塞 只有一個 accept ava 使用步驟 截取 city writable 1.同步和異步 同步:指一個任務運行完以後接著運行下一個任務 異步:接到一個任務後開啟一個新的線程運行此任務,基本不占用原線程時間 2.阻塞與非阻塞 阻塞:多個線程同時訪問一份數據時

Java基礎加強反射

驅動 pri tostring reflect get api tcl 所有 hide 1.什麽是反射? 反射其實就是動態的加載類,我們在寫JDBC的時候加載驅動Class.forName("xxx")時就涉及到了反射。 反射機制是在運行狀態中,對於任意一個類,都能夠知

Java基礎常量、變數、運算子

資料型別 : Java中的基本型別功能簡單,不具備物件的特性,為了使基本型別具備物件的特性,所以出現了包裝類,就可以像操作物件一樣操作基本型別資料。 基本型別對應的包裝類 基本型別 byte int short long float double boolean char 包裝型別

Java基礎順序、選擇、迴圈結構

程式流程  java程式語句執行的順序包括4種基本控制結構:順序結構、選擇結構、迴圈結構、異常處理邏輯結構。 順序結構  Java程式中,語句執行的基本順序按各語句出現的位置先後順序執行,即為順序結構。 例1:順序結構:已知三角形三邊,求三角形面積: pub

Java基礎總結反射

Java反射機制:是在執行狀態中,對於任意一個類,都能知道這個類的所有屬性和方法;對於任何一個物件都能夠呼叫它的任何一個方法說著屬性;這種動態獲取的資訊以及動態呼叫的物件的方法的功能成為Java語言的反射機制。 那麼,如果想解刨一個類的話,首先就要獲取到該類的位元組碼檔案物件:獲取方法有如下三種

java基礎-----------抽象類 、最終類、介面

一、抽象類: 我們把一類事物抽象出來,類中只宣告方法,不實現,這就是抽象類存在的意義; 抽象類: 含有抽象方法的類,被abstract 關鍵字修飾; 抽象方法:只有方法宣告沒有方法實體,被abstract關鍵字修飾; 注意要點: 1、抽象類沒有例項物件,只能通過別的類繼承實現抽象方法

Java基礎環境搭建

伺服器環境:Centos jdk安裝包:.tar.gz 第一步:在官網下載JDK安裝包 第二步:解壓縮安裝包:tar -xzvf jdk-8u131-linux-x64.tar.gz -C /usr/local/java ps:壓縮到/usr/local/java目錄

java基礎GC

概述 java和C++有著一堵 記憶體動態分配 和 垃圾收集技術 圍成的“高牆”,外面的人想進去,裡面的人想出來。 java垃圾回收 GC(Garbage Collection) 的歷史比java還要久遠,1960年誕生的Lisp語言當時就在考慮三個問題:

java基礎基礎

靜態程式碼塊執行一次,隨著類載入而載入 public class Test { static HashMap hashMap = new HashMap(){ // map優雅的寫法 { put("1","2"

java基礎5-------一維陣列的拷貝

作業: 1、{1,2,3,4,5,6} 將奇數放在偶數前面 大小順序不要求 public static int[] sortArray(int[] a) { int odd = 0;// 奇數下標 int even

Java 基礎程式設計基礎

基本資料型別 java 是強型別語言,在 java 中儲存的資料都是有型別的,而且必須在編譯時就確定其型別。 基本資料型別變數儲存的是資料本身,而引用型別變數存的是資料的空間地址。 基本型別轉換 自動型別轉換 把一個表數範圍小的數值或變數直接賦給另一個表數範圍大的變數時,系統將會進行自動型別轉換,否則需要

Java 基礎異常

異常 異常層次 Error:Java 執行時系統的內部錯誤和資源耗盡錯誤。應用程式不應該丟擲這種型別的物件。如果出現了這樣的內部錯誤,除了通告給使用者,並盡力使程式安全地終止之外,再也無能為力了。 Exception RuntimeException:由程式錯誤導致的異常 其他異常:程式本身沒有問題,

Java 基礎集合

List 集合 List 集合中元素有序、可重複,集合中每個元素都有其對應的索引順序。 List 判斷兩個物件相等,只要通過 equals 方法比較返回 true 即可。 看個例子: public class A { public boolean equals(Object obj) {

Java基礎學習筆記二十三 Java核心語法反射

負責 目錄 boolean tostring 筆記 str 編譯 三種 進制 類加載器 類的加載 當程序要使用某個類時,如果該類還未被加載到內存中,則系統會通過加載,鏈接,初始化三步來實現對這個類進行初始化。 加載就是指將class文件讀入內存,並為之創建一個Clas

java基礎學習小知識點

java基礎學習之小知識點 1.程式碼塊 分類 2.super和this的注意點 3.過載(overload)和重寫(override) 4.final修飾符 final指的是**最終的**。 注意點:

贏在面試Java基礎

1、一個".java"原始檔中是否可以包括多個類(不是內部類)?有什麼限制? 可以有多個類,但只能有一個public的類,並且public的類名必須與檔名相一致。 2、Java有沒有goto? java中的保留字,現在沒有在java中使用。 3、說說&

Java基礎加強多執行緒 - 執行緒建立與終止、互斥、通訊、本地變數

執行緒建立與終止 執行緒建立 Thread類與 Runnable 介面的關係 public interface Runnable { public abstract void run(); } public class Thread implements Run

黑馬程式設計師—【Java基礎語言基礎———for巢狀迴圈練習題

------- android培訓、java培訓、期待與您交流! ---------    上篇分享和總結了Java語言基礎程式流程控制和函式; 那麼接下來我繼續為大家分享for巢狀迴圈2道練習題。 /** * 第1題:用控制檯程式倒著輸出九九乘法表;輸出結果按下圖所示

1.偏頭痛楊的中高階java後端面試題攻略系列java基礎(持續更新)

前戲即使不跳槽也建議每隔一段時間出去面試幾次,面試的目的不在於拿offer入職,而是在於你知道你自己目前的水平是幾斤幾兩,是否有被市場所淘汰的危機。終身學習,居安思危,保持危機意識。每個人的時間與精力都是有限的,有限的時間做有限的事情,不要什麼都學,做減法,要有自己的強項,知