天天看點

Java反射機制知識點反射機制

本文章參照:魔樂java視訊教程講解,感謝

1、認識反射

反射之中包含一個“反”的概念,是以就必須先從“正”開始解釋,一般而言,當使用者使用一個類的時候,應該先知道這個類,而後通過這個類産生執行個體化對象,但是“反”指的是通過對象找到類

package cn.test;

class Person{};  

// 定義一個Person類

public

class ReflectDemo {

    public

static void main(String[] args) {

       System.out.println(Person.class.getName());  

// 得到類名

    }

}

列印

cn.test.Person

以上代碼使用一個getClass( )方法,而後就可以得到對象所在的“包”類名稱,就屬于“反了”,但是在這個“反”的操作之中有一個getClass( )就作為發起一切反射操作的開端。

1.1、取得類的執行個體化對象

Person的父類是Object類,而上面所使用的getClass()就是Object類中所定義的方法

l  Class對象:public final <?> getClass()

傳回此 <code>Object</code> 的運作時類。傳回的 <code>Class</code> 對象是由所表示類的 <code>static synchronized</code> 方法鎖定的對象,反射中的所有泛型都定義為?,傳回值都是Object,而這個getClass( )方法傳回的對象是Class類的對象,是以這個Class就是所有反射操作的源頭。這個類最為重要,而如果想要取得這個類的執行個體化對象,Java中定義了三種方式:

1)        通過Object的getClass( )方法

2)        通過“類名.class”方式

3)        使用Class類内部定義的一個static方法,如下描述

public static &lt;?&gt;

forName( className) throws

傳回與帶有給定字元串名的類或接口相關聯的 Class 對象。調用此方法等效于:

  Class.forName(className, true, currentLoader)

其中 currentLoader 表示目前類的定義類加載器。

例如,以下代碼片段傳回命名為 java.lang.Thread 的類的運作時 Class 描述符。

   Class t = Class.forName("java.lang.Thread")

調用 forName("X") 将導緻命名為 X 的類被初始化。

參數:

className - 所需類的完全限定名。

initialize - 是否必須初始化類

loader - 用于加載類的類加載器

傳回:

具有指定名的類的 Class 對象。

抛出:

- 如果連結失敗

- 如果此方法所激發的初始化失敗

- 如果無法定位該類

       /* 方式一:使用Object類的 getClass()方法,基本不用

*/

       Person p = new Person();

       System.out.println(p.getClass().getName());

       // 或

       Class&lt;?&gt; c = p.getClass();

       System.out.println(c.getName());

       /* 方式二:

使用類.Class

方式,在日後學習Hibernate開發時候使用 */

       Class&lt;?&gt; cls = Person.class;   

// 取得 Class對象

       System.out.println(cls.getName());

       System.out.println(Person.class.getName());

       /*方式三:使用Class類内部定義的一個static方法,主要使用

       try {

           Class&lt;?&gt; cl = Class.forName("cn.test.Person");

           System.out.println(cl.getName());

       } catch (Exception e) {

           e.printStackTrace();

       }

1.2、通過反射執行個體化對象

費了這個大的周章,去到了Class類的對象又有什麼用呢?對于對象的執行個體化操作之前一直依靠構造方法和關鍵字New來完成,可是有了Class類對象之後現在又提供了另一種對象執行個體化方法

l  通過反射執行個體化對象:使用newInstance()方法

public newInstance()  throws

,  

建立此 Class 對象所表示的類的一個新執行個體。如同用一個帶有一個空參數清單的 new 表達式執行個體化該類。如果該類尚未初始化,則初始化這個類。

/*

 * 定義一個Person類

 */

class Person{

    public String toString() {

       return

"Person Class Instance";

}; 

static void main(String[]

args) throws Exception {

       Class&lt;?&gt; cls = Class.forName("cn.test.Person");

       Person p = (Person)cls.newInstance(); 

// 執行個體化對象,相當于new關鍵字,然後向下轉型

       System.out.println(p);

列印:

Person Class Instance

可是我們發現,對于對象的執行個體化操作,除了使用關鍵字new之外又多了一個反射機制操作,而對于這個操作要比之前使用的new複雜一些,可是這有什麼用呢?

1.3、反射有什麼用呢?

對于程式開發模式之前一直強調:盡量減少耦合,而減少耦合最好的做法就是使用接口,但是就算使用了接口也逃不出關鍵字new,是以實際上new就是造成耦合的關鍵元兇。

範例:回顧一下之前所編寫的工廠設計模式

interface Fruit{ 

// 水果接口類

void eat();

class Apple

implements Fruit{  

// 蘋果類 

    @Override

void eat() {

       System.out.println("吃蘋果");

class Factory{   

// 工廠類

static Fruit getInstance(String className){

       if("apple".equals(className)){

           return

new Apple();

null;

static void main(String[] args)

throws Exception {

       Fruit f = Factory.getInstance("apple");

       f.eat();

吃蘋果

以上為之前所編寫的最簡單的工廠設計模式,但是在這個弓藏設計模式之中有一個最大的問題,如果現在接口的子類增加了,那麼工廠類肯定需要修改,這就是它所面臨的最大問題,而這個最大問題造成的關鍵字病因就是new,那麼如果說不使用關鍵字new,變為了反射機制呢?

反射機制執行個體化對象的時候實際上隻需要“包類”就可以了,于是根據此操作,修改工廠設計模式。

       Fruit f = null;

           f = (Fruit)Class.forName(className).newInstance();

       return f;

       Fruit f = Factory.getInstance("cn.test.Apple");

發現,這個時候即使增加了接口子類,工廠類照樣完成執行個體化操作,這才是真正的工廠類,可以應對于所有的變化,如果單獨開發角度而已,開發者關系不大,但是對于日後學習的一些架構技術這個就是它實作的命脈,在實作的程式開發上,如果發現操作過程中需要傳遞一個完整的“包類”名稱的時候幾乎都是反射機制作用

2、反射的深入應用

以上知識利用Class類作為反射執行個體化對象的基本應用,但是對于一個執行個體化對象而言,它需要調用類之中的構造方法、普通方法、屬性等,而這些操作都可以通過反射機制完成。

2.1、Class類提供的相關接口

         那麼在得到對應類的Class對象對應後,我們就可以通過該Class對象得到它所對應的類的一些資訊,比如該類的構造函數、成員(屬性)、方法(函數); 

Class類提供的相關接口介紹:(注:在表中,Class對象對應的類,姑且稱為目标類)

接口

傳回類型

接口功能實作

getPackage()

Package

得到目标類的包名對應的Package對象

getCanonicalName()

String

得到目标類的全名(包名+類名)

getName()

同getCanonicalName()

getClassLoader()

ClassLoader

得到加載目标類的ClassLoader對象

getClasses()

Class&lt;?&gt;[]

得到目标類中的所有的public内部類以及public内部接口所對應的Class對象

getDeclaredClasses()

同getClasses(),但不局限于public修飾,隻要是目标類中聲明的内部類和接口均可

getConstructors()

Constructor&lt;?&gt;[]

得到目标類的所有public構造函數對應的Constructor對象

getDeclaredConstructors()

同getConstructors(),但不局限于public修飾,隻要是目标類中聲明的構造函數均可

getField(String arg)

Field

得到目标類中指定的某個public屬性對應的Field對象

getDeclaredField(String arg)

同getField,但不局限于public修飾,隻要是目标類中聲明的屬性均可

getFields()

Field[]

得到目标類中所有的public屬性對應的Field對象

getDeclaredFields()

同getFields(),但不局限于public修飾的屬性

getMethod(String arg0, Class&lt;?&gt;... arg1)

method

得到目标類中指定的某個public方法對應的Method對象

getDeclaredMethod(String arg0, Class&lt;?&gt;... arg1)

Method

同getMethod,但不局限于public修飾的方法

getMethods()

Method[]

得到目标類中所有的public方法對應的Method對象

getDeclaredMethods()

同getMethods(),但不局限于public修飾的方法

getEnclosingClass()

Class

得到目标類所在的外圍類的Class對象

getGenericInterfaces()

Type[]

得到目标類實作的接口對應的Type對象

getGenericSuperclass()

Type

得到目标類繼承的父類所對應的Type對象

getInterfaces()

得到目标類實作的接口所對應的Class對象

getSuperclass()

得到目标類繼承的父類所對應的Class對象

isMemberClass()

boolean

目标類是否為成員類

cisAnonymousClass()

目标類是否為匿名類

2.2、調用所有構造方法

import java.lang.reflect.Constructor;

 * 定義一個Person内部類,

包含3個構造函數

    public Person(){};

    public Person(String name){};

    public Person(String name,

int age){};

class ReflectDemo2 {

       Constructor&lt;?&gt; cons[] = cls.getConstructors();

       for (int i = 0; i &lt; cons.length; i++) {

           System.out.println(cons[i]);

public cn.test.Person()

public cn.test.Person(java.lang.String)

public cn.test.Person(java.lang.String,int)

驗證:一個簡單的Java類必須存在一個無參構造函數。

範例: 無參構造函數

沒有無參構造函數

    String name;

    public Person(String name){

       this.name = name;

    };

    public String toString(){

"Person name = "+name;

       Person p = (Person)cls.newInstance();

Exception in thread "main" java.lang.InstantiationException: cn.test.Person

    at java.lang.Class.newInstance0(Class.java:340)

    at java.lang.Class.newInstance(Class.java:308)

    at cn.test.ReflectDemo2.main(ReflectDemo2.java:21)

因為以上的方式使用反射執行個體化對象時需要的是類之中要提供無參構造方法,但是及軟沒有無參構造方法,那麼就必須明确找到一個構造方法,而後使用Constructor類之中的新方法執行個體化對象

public &lt;&gt;

getConstructor(&lt;?&gt;... parameterTypes)

                              throws ,

傳回一個 Constructor 對象,它反映此 Class 對象所表示的類的指定公共構造方法。parameterTypes 參數是 Class 對象的一個數組,這些 Class 對象按聲明順序辨別構造方法的形參類型。 如果此 Class 對象表示非靜态上下文中聲明的内部類,則形參類型作為第一個參數包括顯示封閉的執行個體。

要反映的構造方法是此 Class 對象所表示的類的公共構造方法,其形參類型與 parameterTypes 所指定的參數類型相比對。

parameterTypes - 參數數組

與指定的 parameterTypes 相比對的公共構造方法的 Constructor 對象

- 如果找不到比對的方法。

- 如果存在安全管理器

s,并滿足下列任一條件:

·         調用 拒絕通路構造方法

·         調用者的類加載器不同于也不是目前類的類加載器的一個祖先,并且對 的調用拒絕通路該類的包

    int

age;

int age){

       this.age = age;

"Person name = "+name+" ,age="+age;

       Class&lt;?&gt; cls = Class.forName("cn.test.Person"); 

// 取得Class對象

       // 取得指定參數類型的構造方法

       Constructor&lt;?&gt;  cons = cls.getConstructor(String.class,int.class);

       Object obj = cons.newInstance("PrettyGril",20); 

// 為構造方法傳遞參數

       System.out.println(obj);

Person name = PrettyGril ,age=20

很明顯,調用無參構造方法執行個體化對象要比調用有參構造更加簡單,友善,是以在日後所有開發中,凡是有簡單Java類出現的地方,都一定要有無參構造方法

2.3、調用普通方法

1)  取得全部方法

           public []

getMethods() throws

2)        取得指定方法

getMethod( name,

&lt;?&gt;... parameterTypes)

                           throws

,

import java.lang.reflect.Method;

 * 定義一個Person内部類

class Person {

    public String getName() {

name;

void setName(String name) {

};

       Method met[] = cls.getMethods();  

// 取得全部方法

       for (int i = 0; i &lt; met.length; i++) {

           System.out.println(met[i]);

public java.lang.String cn.test.Person.getName()

public void cn.test.Person.setName(java.lang.String)

public final void java.lang.Object.wait() throws java.lang.InterruptedException

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 native int java.lang.Object.hashCode()

public final native java.lang.Class java.lang.Object.getClass()

public boolean java.lang.Object.equals(java.lang.Object)

public java.lang.String java.lang.Object.toString()

public final native void java.lang.Object.notify()

public final native void java.lang.Object.notifyAll()

對于取得了Method類對象之後還有一個最大的功能,就是可以利用反射調用類中的方法;

l  調用方法:使用Method類中的Invoke方法

invoke( obj,

... args)

              throws ,,

        之前調用類中方法時候都是“對象.方法”,但是現在有了反射之後,可以直接利用Object類調用指定子類的操作方法(同時解釋一下,為什麼setter、getter方法的命名要求如此嚴格)

範例:利用反射調用Person類之中的setName()、getName()方法

       Object obj = cls.newInstance();

       String attribute = "Name";

       Method setMet = cls.getMethod("set"+attribute, String.class);

       Method getMet = cls.getMethod("get"+attribute);

       setMet.invoke(obj, "PrettyGirl");

       System.out.println(getMet.invoke(obj));

PrettyGirl

在日後的所有架構技術開發之中,簡單Java類都是如此應用的,是以必須按照标準進行

3、 調用成員變量

類之中最後一個組成部分就是成員(Field,也稱屬性)

l  取得本類的全部成員:

                                         public [] getDeclaredFields() throws

l  取得指定的成員:

        public getDeclaredField( name)

                throws,

import java.lang.reflect.Field;

       Field fie[] = cls.getDeclaredFields();

// 獲得全部屬性

       for (int i = 0; i &lt; fie.length; i++) {

           System.out.println(fie[i]);

java.lang.String cn.test.Person.name

在Field中有2個操作方法

(1)設定屬性内容(類似于:對象.屬性 = 内容)

                    public void

set( obj,

 value)

                         throws

(2)取得屬性内容(類似于:對象.屬性)

                    public

get( obj)

                         throws

    private String

       Field fie = cls.getDeclaredField("name");

// 獲得屬性

       fie.set(obj, "PrettyGril");

// 指派

       System.out.println(fie.get(obj));

Exception in thread "main" java.lang.IllegalAccessException: Class cn.test.ReflectDemo2 can not access a member of class cn.test.Person with modifiers

"private"

    at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:65)

    at java.lang.reflect.Field.doSecurityCheck(Field.java:960)

    at java.lang.reflect.Field.getFieldAccessor(Field.java:896)

    at java.lang.reflect.Field.set(Field.java:657)

    at cn.test.ReflectDemo2.main(ReflectDemo2.java:17)

因為設定的name通路權限是私有的,是以不能進行指派,可是從類的開發要求而言,一直都強調類之中屬性必須封裝,是以現在調用之前要想辦法解除封裝,隻需一句代碼: fie.setAccessible(true); 

// 解除封裝

       fie.setAccessible(true);

PrettyGril

雖然反射機制運作直接操作類之中的屬性,可是不會有任何一種程式直接操作屬性,都會通過setter、getter方法調用。