天天看點

Java反射機制 詳解

一、什麼是反射機制

JAVA反射機制是在運作狀态中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法和屬性;這種動态擷取的資訊以及動态調用對象的方法的功能稱為java語言的反射機制。

“程式運作時,允許改變程式結構或變量類型,這種語言稱為動态語言”。Java不是動态語言。但是Java有着一個非常突出的動态相關機制:Reflection,用在Java身上指的是我們可以于運作時加載、探知、使用編譯期間完全未知的classes。換句話說,Java程式可以加載一個運作時才得知名稱的class,獲悉其完整構造(但不包括methods定義),并生成其對象實體、或對其fields設值、或喚起其methods。

二、初識Java反射機制

反射之中包含了一個“反”的概念,要解釋反射就必須先從“正”開始解釋,一般而言,一定是先有類再産生執行個體化對象。如下:

package com.wz.reflectdemo;

import java.util.Date;//先有類

public class ReflectTest {

    public static void main(String[] args) {
        Date date = new Date();//再産對象
        System.out.println(date);
    }
}
           

而所謂的“反”,是通過對象找到類。在Object類裡面提供有一個方法,

取得class對象:

public final Class<?> getClass()
           

注:反射之中的所有泛型都定義為?,傳回值都是Object。

執行個體如下:

package com.wz.reflectdemo;

import java.util.Date;//先有類

public class ReflectTest {

    public static void main(String[] args) {
        Date date = new Date();//再産對象
        System.out.println(date.getClass());
    }
}
           

執行結果:

我們發現,調用getClass()後,得到了類的完整名稱。也就找到了對象的出處。

三、Class類對象執行個體化

java.lang.Class是一個類,它和一般類一樣繼承自Objec。這個類是反射操作的源頭,即所有的反射都要從此類開始進行,這個類有三種執行個體化方式:

方式一:調用Object類的getClass()方法(很少用到):

package com.wz.reflectdemo;

import java.util.Date;

public class ReflectTest {

    public static void main(String[] args) {
        Date date = new Date();
        Class<?> cls = date.getClass();
        System.out.println(cls);
    }
}
           

運作結果:

方式二:使用“類.class”取得:

package com.wz.reflectdemo;

import java.util.Date;

public class ReflectTest {

    public static void main(String[] args) {
        //Date date = new Date();
        Class<?> cls = Date.class;
        System.out.println(cls);
    }
}
           

運作結果:

注意:先前取得Class類對象之前需要執行個體化,但此時并沒有進行執行個體化。

方式三:調用Class類提供的一個方法:

public static Class<?> forName(String className) throws ClassNotFoundException
           

執行個體如下:

package com.wz.reflectdemo;

//import java.util.Date;

public class ReflectTest {

    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("java.util.Date");
        System.out.println(cls);
    }
}
           

運作結果:

此時,無需使用import語句導入一個明确的類,而類名稱是采用字元串的形式進行描述的。

四、反射執行個體化對象

一般情況下,對象的執行個體化操作需要依靠構造方法和關鍵字new完成。可是有了Class類對象之後,可以利用反射來實作對象的執行個體化。

通過反射執行個體化對象:

public T newInstance() throws InstantiationException, IllegalAccessException
           

執行個體:

package com.wz.reflectdemo;

class Book{

    public Book(){
        System.out.println("Book類的無參構造方法");
    }

    @Override
    public String toString() {

        return "This a book !";
    }
}

public class TestDemo {

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

        Class<?> cls = Class.forName("com.wz.reflectdemo.Book");
        Object obj = cls.newInstance();//相當于使用new調用無參構造執行個體化對象

        Book book = (Book)obj;
        System.out.println(book);
    }

}
           

運作結果:

Book類的無參構造方法
This a book !
           

如上,有了反射之後,進行執行個體化的操作不再隻有依靠關鍵字new來完成了。但這個操作要比之前使用的new複雜一些,并且并不表示用new來進行執行個體化被完全取代了。為什麼呢?

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

先看一個簡單的工廠設計模式:

package com.wz.reflectdemo;


interface Fruit{
    public void eat();
}

class Apple implements Fruit{
    @Override
    public void eat(){
        System.out.println("eat apple");
    }
}

class Factory{
    public static Fruit getInstance(String className){
        if("apple".equals(className)){
            return new Apple();
        }
        return null;
    }
}

public class FactoryDemo{
    public static void main(String[] args){
        Fruit f = Factory.getInstance("apple");
        f.eat();
    }
}
           

運作結果:

eat apple
           

以上是一個簡單的工廠設計模式,但是在這個工廠設計模式之中有一個問題:如果增加了Fruit接口子類,那麼就需要修改工廠類。

增加了Fruit接口子類Orange :

class Orange implements Fruit {
    public void eat() {
        System.out.println("eat orange");
    };
}
           

需要修改工廠類:

class Factory{
    public static Fruit getInstance(String className){
        if("apple".equals(className)){
            return new Apple();
        }else if("orange".equals(className)){
            return new Orange();
        }
        return null;

    }
}
           

問題來了,每增加一個接口子類,就需要去修改工廠類,那麼若随時可能增加多個子類呢?那麼就要一直對工廠類進行修改!

根本原因:工廠類中的對象都是通過關鍵字new直接執行個體化的。那麼如果說現在不使用關鍵字new了,變為了反射機制呢?

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

package com.wz.reflectdemo;


interface Fruit{
    public void eat();
}

class Apple implements Fruit{
    @Override
    public void eat(){
        System.out.println("eat apple");
    }
}

class Orange implements Fruit{
    @Override
    public void eat(){
        System.out.println("eat orange");
    }
}

class Factory{
    public static Fruit getInstance(String className){
        /*if("apple".equals(className)){
            return new Apple();
        }else if("orange".equals(className)){
            return new Orange();
        }
        return null;*/
        Fruit f = null;
        try {
            f = (Fruit)Class.forName(className).newInstance();

        } catch (Exception e) {
            e.printStackTrace();
        }
        return f;

    }
}

public class FactoryDemo{
    public static void main(String[] args){
        /*Fruit f = Factory.getInstance("apple");
        f.eat();

        Fruit f1 = Factory.getInstance("orange");
        f1.eat();*/

        Fruit f1= Factory.getInstance("com.wz.reflectdemo.Apple");
        f1.eat();

        Fruit f2 = Factory.getInstance("com.wz.reflectdemo.Orange");
        f2.eat();

    }
}
           

運作結果:

eat apple
eat orange
           

這個時候即使增加了接口的子類,工廠類照樣可以完成對象的執行個體化操作,這個才是真正的工廠類,可以應對于所有的變化。這就完成了解耦合的目的,而且擴充性非常強。

五、反射調用構造方法

之前我們通過反射執行個體化對象都是這麼寫的:

Class<?> cls = Class.forName(“*****className*****”);
Object  obj =  cls.newInstance();
           

這隻能調用預設的無參構造方法,那麼,問題來了:若類中不提供無參構造方法呢?怎麼解決?

看一個範例:

先寫一個Book類:

package com.wz.reflectdemo;

public class Book {

    private String title;
    private double price;

    public Book(String title, double price) {
        this.title = title;
        this.price = price;
    }

    @Override
    public String toString() {

        return "圖書名稱:"+this.title + " ,價格:"+this.price;
    }

}
           

然後執行個體化對象:

package com.wz.reflectdemo;

public class ReflectTest {

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

        Class<?> cls = Class.forName("com.wz.reflectdemo.Book");
        Object obj = cls.newInstance();//相當于使用new調用無參構造執行個體化對象

        Book book = (Book)obj;
        System.out.println(book);

    }
}
           

執行結果:

Exception in thread "main" java.lang.InstantiationException: com.wz.reflectdemo.Book
    at java.lang.Class.newInstance(Unknown Source)
    at com.wz.reflectdemo.ReflectTest.main(ReflectTest.java:)
Caused by: java.lang.NoSuchMethodException: com.wz.reflectdemo.Book.<init>()
    at java.lang.Class.getConstructor0(Unknown Source)
    ...  more
           

由此可見,由于此時Book類沒有提供無參構造方法,而cls.newInstance()的時候又調用了無參構造方法,是以無法進行對象執行個體化。那麼,怎麼解決?隻能明确的調用有參構造方法。

在Class類裡面,提供了方法來取得構造:

(1)取得全部構造:

(2)取得一個指定參數順序的構造:

以上兩個方法傳回的都是”java.lang.reflect.Constructor”類的對象。在這個類中提供有一個明确傳遞有參構造内容的執行個體化對象方法:

public T newInstance(Object... initargs) throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
           

改寫上面範例的執行個體化對象方法,明确調用有參構造方法:

package com.wz.reflectdemo;

import java.lang.reflect.Constructor;

public class ReflectTest {

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

        Class<?> cls = Class.forName("com.wz.reflectdemo.Book");
        /*Object obj = cls.newInstance();//相當于使用new調用無參構造執行個體化對象
        Book book = (Book)obj;
        System.out.println(book);*/

        Constructor<?> con = cls.getConstructor(String.class,double.class);
        Object obj = con.newInstance("Java開發",);//執行個體化對象

        System.out.println(obj);

    }
}
           

執行結果:

圖書名稱:Java開發 ,價格:79.8
           

很明顯,調用無參構造方法執行個體化對象要比調用有參構造的更加簡單、友善。so,簡單的Java開發中不過提供有多少個構造方法,請至少保留有無參構造。

六、反射調用普通方法

我們都知道:類中的普通方法隻有在一個類産生執行個體化對象之後才可以調用。

先看一個例子。我們先定義一個類:

package com.wz.reflectdemo;

public class Book {

    private String title;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

}
           

這個類有無參構造方法,所有執行個體化對象的時候可以直接利用Class類提供的newInstance()方法。

在Class類裡面提供以下取得類在Method的操作:

(1)取得一個類中的全部方法:

(2)取得指定方法:

public Method getMethod(String name, Class<?>... parameterTypes) throws
NoSuchMethodException, SecurityException
           

以上的方法傳回的都是”java.lang.reflect.Method”類的對象,在這個類中有一個調用方法:

public Object invoke(Object obj, Object... args) throws IllegalAccessException,
IllegalArgumentException, InvocationTargetException
           

我們接着上面的例子來看反射調用方法:

package com.wz.reflectdemo;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class ReflectTest {

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

        Class<?> cls = Class.forName("com.wz.reflectdemo.Book");
        Object obj = cls.newInstance();

        Method setMet = cls.getMethod("setTitle", String.class);
        setMet.invoke(obj, "Java開發");//等價于Book類的setTitle("Java開發")

        Method getMet = cls.getMethod("getTitle");
        System.out.println(getMet.invoke(obj));//等價于Book類的getTitle()
    }
}
           

執行結果:

Java開發
           

此時,我們完全看不見具體的操作類型,也就是說,利用反射可以實作任意類的指定方法的調用。

七、反射調用成員

我們都知道,類中的屬性一定要在本類執行個體化對象之後才可以配置設定記憶體空間。在Class類裡面提供有取得成員的方法:

(1)取得全部成員:

public Field[] getDeclaredFields() throws SecurityException
           

(2)取得指定成員:

public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException
           

這兩個方法的傳回值類型是”java.lang.reflect.Field”類的對象。在這個類裡面有兩個重要的方法:

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

public Object get(Object obj)
throws IllegalArgumentException, IllegalAccessException
           

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

public void set(Object obj, Object value)
throws IllegalArgumentException, IllegalAccessException
           

接着看一個例子:

先定義一個類:

package com.wz.reflectdemo;

public class Book {

    private String title;

}
           

這個類隻定義了一個私有屬性,按照之前的做法,它一定無法被外部所使用。但是,可以反射調用:

package com.wz.reflectdemo;

import java.lang.reflect.Field;

public class ReflectTest {

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

        Class<?> cls = Class.forName("com.wz.reflectdemo.Book");
        Object obj = cls.newInstance();

        Field titleField = cls.getDeclaredField("title");
        titleField.setAccessible(true);//解除封裝
        titleField.set(obj, "Java開發");//相當于Book類對象.title = "Java開發"
        System.out.println(titleField.get(obj));//相當于Book類對象的.title
    }
}
           

執行結果:

Java開發
           

注:構造方法和普通方法一樣可以解除封裝,隻是很少這麼去做。而對于屬性的通路還是建議使用setter和getter方法完成。