1. 程式人生 > >Java Class類及反射機制

Java Class類及反射機制

首先要分清楚Class類和關鍵詞class的不同,雖然Class類名稱上和class很相似,但兩者其實沒有什麼關聯。Class類也是一個Java類,它也可以例項化得到物件,與普通類不一樣的是Class類中儲存的是其它類的型別資訊。而這些型別資訊正是Java反射機制所需要的,Java的反射機制是由Class類和java.lang.reflect包下的Method、Field、Constructor等類支援的。下面就來了解下這幾個類。

1.Class類

類是程式的一部分,每個類都有一個Class物件,換而言之每編寫一個新類,就會產生一個Class物件(更準確的來說,是被儲存在一個同名的.class檔案中)。當程式中需要使用到這個類的時候(包括需要這個類的物件或引用這個類的靜態變數)就通過類載入器將類加到記憶體中來。
在進行類載入的時候,類載入器會先檢查這個類的Class物件是否已經載入,如果還未載入,則預設的類載入器就會根據類名查詢.class檔案(這裡不一定都是從檔案系統中獲取,可以從資料庫讀取位元組碼或者接受網路中傳輸過來的位元組碼)。這些位元組碼在被載入的時候,會檢查驗證保證它們沒有被破壞。一旦某個類的Class物件被載入記憶體,它就會被用來建立這個類的所有物件。下面來看下Class類一些操作。
1.1獲取Class物件的三種方式
總共有三種方式可以獲取一個類的Class物件:Class類的forName()方法、類字面常量、物件的getClass()方法。下面是一個示例,Student類是用來進行測試的物件。

package com.sankuai.lkl;
public class Student {

    public static final String test1 = "final_test";
    public static       String test2 = "static_test";

    static {
        System.out.println("init static");
    }

    private String name;
    private int    id;
    private String grade;
}

下面是一個測試方法:

public static void test1() throws Exception{
      //獲取Class物件的三種方式
        try {
            //使用Class.forName()方法載入的時候,需要類的全限定名
            Class<?> cc = Class.forName("com.sankuai.lkl.Student");
            System.out.println(cc);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        //使用類字面常量
Class<?> cc1 = Student.class; System.out.println(cc1); //通過物件的getClass() Student student = new Student(); Class<?> cc = student.getClass(); System.out.println(cc); }

真正測試的時候每次只會執行一個方法,下面是三種方式對應的輸出:
這裡寫圖片描述
這裡寫圖片描述
這裡寫圖片描述
可以看到第二次的輸出有點不同,沒有輸出static初始化塊中的值。這是因為使用類字面常量方式獲取Class物件的時候,類載入器雖然將類對應的class檔案載入到記憶體中並建立了Class物件,但是沒有給類進行初始化工作。這裡稍微介紹下類載入的流程,分為三步:

  1. 載入。這一步是類載入器執行的,負責查詢位元組碼(通常是在classpath所指定的路徑中查詢,但這不是必要的)。並從這些位元組碼中建立一個Class物件。
  2. 連結。在連結階段將驗證類中的位元組碼,併為類中的靜態變數分配儲存空間,並且如果必需的話,將解析這個類建立的對其他類的所有引用(靜態變數是引用並且直接初始化的情況)。
  3. 初始化。如果該類有父類,則先對其進行初始化,呼叫靜態初始化器和靜態初始化塊。

在使用類字面常量獲取Class物件的時候,初始化這一步被延遲到對類的靜態方法或靜態變數進行首次呼叫的時候(這裡有一個很有意思的情況,其實構造器也是隱式靜態的)。

1.2從Class物件中獲取類資訊
上面討論瞭如何獲取一個類的Class物件,那麼拿到這個Class物件之後,我們又能做些什麼呢。Class物件儲存了類的型別資訊,對於一個類來說比較重要的資訊有其中定義的屬性、方法、構造器,還有就是它擴充套件了哪些父類和介面等等。而這些方法Class物件中都是包含的,並且還提供了獲取的方法。下面是一些具體的例子,先給兩個測試用的類定義:

package com.sankuai.lkl.reflect;

public interface Person {

    void read();
}
package com.sankuai.lkl.reflect;
public class Student implements Person {

    private String id;
    public String name;
    private String age;

    //建構函式1
    public Student() {

    }
    //建構函式2
    public Student(String id) {
        this.id = id;
    }
    //建構函式3
    public Student(String id, String name) {
        this.id = id;
        this.name = name;
    }

    public String getId() {
        return id;
    }
     public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getAge() {
        return age;
    }
    public void setAge(String age) {
        this.age = age;
    }
    //靜態方法
    public static void update() {

    }
    @Override
    public void read() {

    }
}

可以看到Student類是繼承自Person類的。
首先來通過Class物件獲取Student的所有屬性:

public static void main(String[] args) {
        Class<?> cc = Student.class;

        //getFields返回的是申明為public的屬性,包括父類中定義
        Field[] fields = cc.getFields();
        System.out.println("cc.getFields()");
        for (Field field : fields) {
            System.out.println(field);
        }
        System.out.println("\n");

        //getDeclaredFields返回的是指定類定義的所有定義的屬性,不包括父類的。
        Field[] fields1 = cc.getDeclaredFields();
        System.out.println("cc.getDeclaredFields()");
        for (Field field : fields1) {
            System.out.println(field);
        }
    }

輸出如下:
cc.getFields()
public java.lang.String com.sankuai.lkl.reflect.Student.name
public static final java.lang.String com.sankuai.lkl.reflect.Person.test

cc.getDeclaredFields()
private java.lang.String com.sankuai.lkl.reflect.Student.id
public java.lang.String com.sankuai.lkl.reflect.Student.name
private java.lang.String com.sankuai.lkl.reflect.Student.age

除了上面兩個方法,還有getField(String name)和 getDeclaredField(String name)兩個可以通過名稱拿到指定屬性Field物件的方法,它們的區別同上。

獲取類所有的方法:

         Class<?> cc = Student.class;
        //獲取所有pulbic型別方法,包括父類中定義的(Object和Person類中)
        //被子類重寫過的方法,只運算元類的
        Method[] methods = cc.getMethods();
        System.out.println("cc.getMethods()");
        for (Method method : methods) {
            System.out.println(method);
        }
        System.out.println("\n");

        //獲取本類中所有方法
        Method[] methods1 = cc.getDeclaredMethods();
        System.out.println("cc.getDeclaredMethods()");
        for (Method method : methods1) {
            System.out.println(method);
        }
輸出如下:
cc.getMethods()
public java.lang.String com.sankuai.lkl.reflect.Student.getName()
public void com.sankuai.lkl.reflect.Student.setName(java.lang.String)
public java.lang.String com.sankuai.lkl.reflect.Student.getId()
public void com.sankuai.lkl.reflect.Student.read()
public static void com.sankuai.lkl.reflect.Student.update()
public void com.sankuai.lkl.reflect.Student.setId(java.lang.String)
public java.lang.String com.sankuai.lkl.reflect.Student.getAge()
public void com.sankuai.lkl.reflect.Student.setAge(java.lang.String)
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()


cc.getDeclaredMethods()
public java.lang.String com.sankuai.lkl.reflect.Student.getName()
public void com.sankuai.lkl.reflect.Student.setName(java.lang.String)
public java.lang.String com.sankuai.lkl.reflect.Student.getId()
public void com.sankuai.lkl.reflect.Student.read()
public static void com.sankuai.lkl.reflect.Student.update()
public void com.sankuai.lkl.reflect.Student.setId(java.lang.String)
public java.lang.String com.sankuai.lkl.reflect.Student.getAge()
public void com.sankuai.lkl.reflect.Student.setAge(java.lang.String)

除了上面的批量方法,也有通過名稱和引數來獲取單個Method物件的方法:
這裡寫圖片描述
分別是getMethod()和getDeclaredMethod(),這裡需要注意的是光通過名字是可能無法唯一確定一個方法的,所以這裡還需要傳入引數,兩個方法都是傳入引數的Class物件陣列,注意int.class和Integer.class分別對應到引數中的int和Integer型別,不能通用。

獲取類的所有構造器
在展示Class物件的方法之前,先將Person類進行改造:將其改造成抽象類,並增加兩個構造器。Student仍然是Person類的子類。

public abstract class Person {

    static String test = "test";

    private int age;

    public abstract void read();

    private Person() {
    }

    private Person(int age) {
        this.age = age;
    }
}

下面是具體方法和輸出結果

  Class<?> cc = Student.class;
        //獲取所有構造器
        Constructor<?>[] constructors = cc.getConstructors();
        System.out.println("cc.getConstructors()");
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor);
        }
        System.out.println("\n");

        Constructor<?>[] constructors1 = cc.getDeclaredConstructors();
        System.out.println("getDeclaredConstructors()");
        for (Constructor<?> constructor : constructors1)                 
        {
            System.out.println(constructor);
        }

輸出:
cc.getConstructors()
public com.sankuai.lkl.reflect.Student(java.lang.String,java.lang.String)
public com.sankuai.lkl.reflect.Student(java.lang.String)
public com.sankuai.lkl.reflect.Student()


getDeclaredConstructors()
public com.sankuai.lkl.reflect.Student(java.lang.String,java.lang.String)
public com.sankuai.lkl.reflect.Student(java.lang.String)
public com.sankuai.lkl.reflect.Student()

這次看起來倒是兩個方法表現一致,所有私有的構造器都沒查找出來。對於構造器來說也有根據名字和引數獲取類單個構造器Constructor物件的方法。

通過Class獲取類繼承的父類和實現的介面的Class物件:

  Class<?> cc = Student.class;
        //獲取該類實現了的所有介面
        Class<?>[] ccs = cc.getInterfaces();
        System.out.println("getInterfaces");
        for(Class<?> c : ccs){
            System.out.println(c);
        }

        System.out.println("\n");

        //獲取該類的直接父類,如果沒有顯示宣告的父類,則是Object
        Class<?> cc1 = cc.getSuperclass();
        System.out.println(cc1);

通過Class物件判斷當前類是不是介面並建立類的例項:

        Class<?> cc = Student.class;
        if (!cc.isInterface()) {
            //呼叫Class物件的newInstance建立物件,要求類必須具有無參構造器
            //並且創建出來的物件是Object型別,必須強制轉型
            Student student = (Student) cc.newInstance();
            System.out.println(cc);
        }

 輸出:
 com.sankuai.lkl.reflect.Student@19501026

通過上面幾個方法的介紹,大致上明白了Class類的作用,下面看下它是如何和反射來結合的。

2.Java反射機制

通過Class物件可以拿到類的型別資訊,方法、屬性、構造器;但是如果只能拿到這些資訊,沒有辦法對其進行操作其實意義也不大。Java的反射機制正是提供了對上面獲取的Method、Field、Constructor等進行操作的能力。其實這些類本身就屬於java.lang.reflect包下的一部分,都實現了Member介面。下面來看下如何對它們進行操作。

2.1通過Field改變物件屬性

public static void test1() throws Exception{
      try{
          Class<?> cc = Class.forName("com.sankuai.lkl.reflect.Student");
          Object object =  cc.newInstance();

          Field field = cc.getDeclaredField("id");
          //id是private型別的,如果沒有下面的設定,會丟擲IllegalAccessException
          field.setAccessible(true);
          field.set(object,"12313");
          System.out.println(((Student)object).getId());
      }catch (ClassNotFoundException e){
          e.printStackTrace();
      }
    }
    //輸出:12313

上面的id屬性是私有的,但是通過將其對應Field的accessible設定為true,就可以對其進行修改了,這其實破壞了Java的封裝。

2.2通過Method來呼叫物件的方法

 public static void test1() throws Exception{
      try{
          Class<?> cc = Class.forName("com.sankuai.lkl.reflect.Student");
          Object object =  cc.newInstance();
          Method method = cc.getDeclaredMethod("setName",String.class);
          method.invoke(object,"testName");
          Method method1 = cc.getDeclaredMethod("getName");
          //將getName()方法設定成private,但將accessible設定為true之後,仍然可以方法
          method1.setAccessible(true);
          System.out.println(method1.invoke(object));
      }catch (ClassNotFoundException e){
          e.printStackTrace();
      }
    }
   //輸出:testName

上面展示了通過Class物件獲得一個類例項,然後通過Method來操作這個類例項方法的過程。需要注意的是呼叫Class物件的getDeclaredMethod方法時,如果是想獲得一個有引數的方法的Method物件,那麼是需要傳入引數型別的Class物件的,這裡對順序是有嚴格要求的並且int.class和Integer.class是不一樣的,對其它基本型別也是同樣的情況。並且和Field一樣,只要將accessible設定為true,就可以實現對私有方法的呼叫。

2.3通過Constructor來構造物件

 public static void test1() throws Exception {
        try {
            Class<?> cc = Class.forName("com.sankuai.lkl.reflect.Student");
            Constructor<?> constructor = cc.getDeclaredConstructor();
            System.out.println(constructor.newInstance());
            Constructor<?> constructor1 = cc.getConstructor(int.class, String.class);
            constructor1.setAccessible(true);
            Student student = (Student) constructor1.newInstance(1, "2321");
            System.out.println(student.getId() + " " + student.getName());

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

輸出:
com.sankuai.lkl.reflect.Student@24e5ddd0
1 2321

看起來構造器呼叫的形式和方法有些類似,不同的只是不再需要指定名字。並且對於私有構造器,也可以通過設定accessible為true來進行訪問。

總的來說反射機制允許程式在執行時透過Reflection APIs取得任何一個已知名稱的class的內部資訊,包括其modifiers(諸如public, static 等)、superclass(例如Object)、實現之interfaces(例如Cloneable),也包括fields和methods的所有資訊,並可於執行時改變fields內容或喚起methods。
Java反射機制容許程式在執行時載入、探知、使用編譯期間完全未知的classes。

相關推薦

Java Class反射機制

首先要分清楚Class類和關鍵詞class的不同,雖然Class類名稱上和class很相似,但兩者其實沒有什麼關聯。Class類也是一個Java類,它也可以例項化得到物件,與普通類不一樣的是Class類中儲存的是其它類的型別資訊。而這些型別資訊正是Java反射機

Java Class反射機制學習筆記(一)

類的載入 當一個程式使用某一個類時,如果該類還未被載入到記憶體中,則系統會通過載入、連線、初始化三個步驟來實現對這個類的初始化。 載入 就是指將該類的 .class檔案讀入記憶體中,併為之建立一個

深入理解Java加載機制反射

指定 請求 image vm虛擬機 常量池 使用 元素 靜態 創建 一、Java類加載機制 1.概述 Class文件由類裝載器裝載後,在JVM中將形成一份描述Class結構的元信息對象,通過該元信息對象可以獲知Class的結構信息:如構造函數,屬性和方法等,J

Java型別資訊(Class物件)與反射機制

RRTI的概念以及Class物件作用 RTTI(Run-Time Type Identification)執行時型別識別,對於這個詞一直是 C++ 中的概念,至於Java中出現RTTI的說法則是源於《Thinking in Java》一書,其作用是在執行時識別一個物件的型別

Java型別資訊(Class物件)與反射機制-1

RRTI的概念以及Class物件作用 RTTI(Run-Time Type Identification)執行時型別識別,對於這個詞一直是 C++ 中的概念,至於Java中出現RTTI的說法則是源於《Thinking in Java》一書,其作用是在執行時識別一個物件的型別

java虛擬機器基礎看java反射機制

java虛擬機器相關基礎     任何一種語言編寫的程式,執行在不同的系統上,最終都需要被編譯成為機器可以識別的機器碼(也就是01010…1這種二進位制資料)。對於java語言而言,虛擬機器起到了機器語言與該語言自身的橋樑作用,虛擬機器可以識別字節碼,針對不同的

深入理解Java型別資訊(Class物件)與反射機制

關聯文章: 本篇主要是深入對Java中的Class物件進行分析,這對後續深入理解反射技術非常重要,主要內容如下: 深入理解Class物件 RRTI的概念以及Class物件作用 認識Class物件之前,先來了解一個概念,RTTI(Run-Time

Java基礎之深入理解Class物件與反射機制

深入理解Class物件 RRIT及Class物件的概念 RRIT(Run-Time Type Identification)執行時型別識別。在《Thinking in Java》一書第十四章中有提到,它的功能是在執行時識別物件的型別和類資訊。有兩種主要方式:“傳統的”RTTI(它假定我們在編譯時

java.lang.reflect】反射機制應用詳解

最近也是面試的時候問道一個問題,如何將一個java物件轉換為json字串,一聽到的時候沒有任何思路,之前也有接觸過fastjson,知道就是用這個jar包來處理的,但是具體如何執行原理並不瞭解,導致面試說不出來,面試官提到fastjson其實就是利用反射來獲取這個物件對應的資

JAVA jdbc ResultSet 通過反射機制轉換為實體Bean

最近專案又有一部分用到jdbc了。。。都快忘光了。。。複習了一下,在這記錄一下。肯定有有問題的地方,歡迎指正。 轉實體類工具 /** * 公司名稱: * 專案名稱: * 版本號 : 1.0 * 建立時間:2017/3/24 16:19

Java學習筆記】66:認識Java中的Reflection(反射)機制,獲取的屬性和方法

反射部分一直欠著,現在學框架和Servlet必須要學一下了。最近學習Struts2框架和Servlet時候,很多地方直接給出類名就可以去使用了,如在web.xml中配置Filter時: <filter> <filter-name&

java Class-載入的三種方法 以及的初始化的六種方式 終止執行的方式

1在命令列啟動虛擬機器jvm進行載入,2用class.forname()方法進行動態載入;3用ClassLoader.loadClass()進行動態載入;區別:用二方法載入時,還會對類進行解釋,執行其中的static語句塊;          用三方法時候,static語句塊

Java基礎筆記 – 通過反射機制修改中的私有屬性的值

//建立一個類的物件 PrivateField privateField = new PrivateField(); //獲取物件的Class Class<?> classType = privateField.getClass(); //獲取指定名字的私有域 Field field = cla

Java加載機制

自定義 會有 啟動 數字 rgs 層次 loader 虛擬機 存儲結構 一、什麽是類的加載 類的加載指的是將類的.class文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,然後在堆區創建一個java.lang.Class對象,用來封裝類在方法區內

jvm系列學習之--Java加載機制

任務 字節 修飾符 https 完整 自定義類加載器 字節流 由於 字節碼 轉載:https://segmentfault.com/a/1190000004597758 本文主要講述Java類的加載機制,主要包括類加載器、加載過程、初始化時機。 一、類加載器 1、Clas

Java基礎-加載機制與自定義Java加載器(ClassLoader)

定義類 方式 blog 之前 www 筆記 通過 反射 加載機制          Java基礎-類加載機制與自定義類Java類加載器(ClassLoader)                                     作者:尹正傑 版權聲明:原創作品,謝絕轉

Java Class檔案結構

平臺無關性 Java是與平臺無關的語言,這得益於Java原始碼編譯後生成的儲存位元組碼的檔案,即Class檔案,以及Java虛擬機器的實現。不僅使用Java編譯器可以把Java程式碼編譯成儲存位元組碼的Class檔案,使用JRuby等其他語言的編譯器也可以把程式程式碼編譯成Class檔案,虛擬機

java間轉換

1.子類轉父類,無需強轉,只能呼叫父類與子類中同時擁有的變數: class MyBase{ int a=5; } public class zhuan1 extends MyBase{ int a=3; int b=8; public static void main(String[]

Java重點基礎:反射機制

一、什麼是反射? Java反射說的是在執行狀態中,對於任何一個類,我們都能夠知道這個類有哪些方法和屬性。對於任何一個物件,我們都能夠對它的方法和屬性進行呼叫。我們把這種動態獲取物件資訊和呼叫物件方法的功能稱之為反射機制。 二、反射的三種方式 這裡需要跟大家說一下,所謂反射其實是獲取類的位

Java - Class物件、反射、動態代理學習

         Java是一個動態連結語言。Java中的類在需要時才會被載入,這個類我們可以視為一個Class物件(xxx.class)。管理這些Class物件的類則就是Class類。         &