1. 程式人生 > >如何理解java反射?

如何理解java反射?

stat 過程 最大 inter pack -m 向下轉型 hibernate con

一、反射基本概念

反射之中包含了一個“反”的概念,所以要想解釋反射就必須先從“正”開始解釋,一般而言,當用戶使用一個類的時候,應該先知道這個類,而後通過這個類產生實例化對象,但是“反”指的是通過對象找到類。

packagecn.mldn.demo;

class Person {
    
}

public class TestDemo {
    public static void main(String[] args) throws Exception {
        Person per = new Person(); // 正著操作
        System.out.println(per.getClass().getName()); // 反著來
    }
}

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

取得Class對象:public final Class<?> getClass(),反射之中的所有泛型都定義為?,返回值都是Object。 而這個getClass()方法返回的對象是Class類的對象,所以這個Class就是所有反射操作的源頭。但是在講解其真正使用之前還有一個需要先解釋的問題,既然Class是所有反射操作的源頭,那麽這個類肯定是最為重要的,而如果要想取得這個類的實例化對象。

Java中定義了三種方式:

  • 方式一:通過Object類的getClass()方法取得,基本不用:
packagecn.mldn.demo;

class Person {
}

public class TestDemo {
    public static void main(String[] args) throws Exception {
        Person per = new Person(); // 正著操作
        Class<?> cls = per.getClass(); // 取得Class對象
        System.out.println(cls.getName()); // 反著來
    }
}
  • 方式二:使用“類.class”取得,在日後學習Hibernate開發的時候使用
packagecn.mldn.demo;

class Person {
}

public class TestDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Person.class; // 取得Class對象
        System.out.println(cls.getName()); // 反著來
    }
}
  • 方式三:使用Class類內部定義的一個static方法,主要使用取得Class類對象:

    public static Class<?>  forName(String className) throws   ClassNotFoundException;
packagecn.mldn.demo;

class Person {
}

public class TestDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("cn.mldn.demo.Person"); // 取得Class對象
        System.out.println(cls.getName()); // 反著來
    }
}

那麽現在一個新的問題又來了,取得了Class類的對象有什麽用處呢?對於對象的實例化操作之前一直依靠構造方法和關鍵字new完成,可是有了Class類對象之後,現在又提供了另外一種對象的實例化方法:

通過反射實例化對象:

public T newInstance() throws InstantiationException,IllegalAccessException;

範例:通過反射實例化對象

packagecn.mldn.demo;

class Person {
    @Override
    publicString toString() {
        return "Person Class Instance .";
    }
}

public class TestDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("cn.mldn.demo.Person"); // 取得Class對象
        Object obj = cls.newInstance(); // 實例化對象,和使用關鍵字new一樣
        Person per = (Person) obj; // 向下轉型
        System.out.println(per);
    }
}

那麽現在可以發現,對於對象的實例化操作,除了使用關鍵字new之外又多了一個反射機制操作,而且這個操作要比之前使用的new復雜一些,可是有什麽用?

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

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

packagecn.mldn.demo;

interface Fruit {
    public void eat();
}

class Apple implements Fruit {
    public void eat() {
        System.out.println("吃蘋果。");
    }

}

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();
    }
}

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

反射機制實例化對象的時候實際上只需要“包.類”就可以,於是根據此操作,修改工廠設計模式。

packagecn.mldn.demo;

interface Fruit {
    public void eat();
}

class Apple implements Fruit {
    public void eat() {
        System.out.println("吃蘋果。");
    }

    ;
}

class Orange implements Fruit {
    public void eat() {
        System.out.println("吃橘子。");
    }

}

class Factory {
    public static Fruit getInstance(String className) {
        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("cn.mldn.demo.Orange");
        f.eat();
    }
}

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

二、反射的深入應用

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

1、調用構造使用

反射機制也可以取得類之中的構造方法,這個方法在Class類之中已經明確定義了:以下兩個方法取得一個類的全部構造:
public Constructor<?>[] getConstructors() throws
SecurityException

取得一個類的指定參數構造:

public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException

現在發現以上的兩個方法返回的都是java.lang.reflect.Constructor類的對象。

範例:取得一個類之中的全部構造

packagecn.mldn.demo;
import java.lang.reflect.Constructor;

class Person { // CTRL + K
    public Person() {
    }

    public Person(String name) {
    }

    public Person(String name, intage) {
    }
}

public class TestDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("cn.mldn.demo.Person"); // 取得Class對象
        Constructor<?> cons[] = cls.getConstructors(); // 取得全部構造
        for (intx = 0; x < cons.length; x++) {
            System.out.println(cons[x]);
        }
    }
}


驗證:在之前強調的一個簡單Java類必須存在一個無參構造方法

範例:觀察沒有無參構造的情況

packagecn.mldn.demo;

class Person { // CTRL + K
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

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

public class TestDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("cn.mldn.demo.Person"); // 取得Class對象
        Object obj = cls.newInstance(); // 實例化對象
        System.out.println(obj);
    }
}

此時程序運行的時候出現了錯誤提示“java.lang.InstantiationException”,因為以上的方式使用反射實例化對象時需要的是類之中要提供無參構造方法,但是現在既然沒有了無參構造方法,那麽就必須明確的找到一個構造方法,而後利用Constructor類之中的新方法實例化對象:

實例化對象:

public T newInstance(Object...initargs) throws InstantiationException, IllegalAccessException,IllegalArgumentException,
InvocationTargetException
packagecn.mldn.demo;
import java.lang.reflect.Constructor;

class Person { // CTRL + K
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

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

public class TestDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("cn.mldn.demo.Person"); // 取得Class對象
        // 取得指定參數類型的構造方法
        Constructor<?> cons = cls.getConstructor(String.class, int.class);
        Object obj = cons.newInstance("張三", 20); // 為構造方法傳遞參數
        System.out.println(obj);
    }
}

很明顯,調用無參構造方法實例化對象要比調用有參構造的更加簡單、方便,所以在日後的所有開發之中,凡是有簡單Java類出現的地方,都一定要提供無參構造。

2、調用普通方法

當取得了一個類實例化對象之後,下面最需要調用的肯定是類之中的方法,所以可以繼續使用Class類取得一個類中所定義的方法定義:

  • 取得全部方法:

    public Method[] getMethods() throws SecurityException;
  • 取得指定方法:

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

    發現以上的方法返回的都是java.lang.reflect.Method類的對象。

範例:取得一個類之中所定義的全部方法

packagecn.mldn.demo;
importjava.lang.reflect.Method;

class Person {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

public class TestDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("cn.mldn.demo.Person"); // 取得Class對象
        Method met[] = cls.getMethods(); // 取得全部方法
        for (intx = 0; x < met.length; x++) {
            System.out.println(met[x]);
        }
    }
}

但是取得了Method類對象最大的作用不再於方法的列出(方法的列出都在開發工具上使用了),但是對於取得了Method類對象之後還有一個最大的功能,就是可以利用反射調用類中的方法:

  • 調用方法:

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

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

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

packagecn.mldn.demo;
import java.lang.reflect.Method;

class Person {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

public class TestDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("cn.mldn.demo.Person"); // 取得Class對象
        Object obj = cls.newInstance(); // 實例化對象,沒有向Person轉型
        String attribute = "name"; // 要調用類之中的屬性
        Method setMet = cls.getMethod("set" + initcap(attribute), String.class);// setName()
        Method getMet = cls.getMethod("get" + initcap(attribute));// getName()
        setMet.invoke(obj, "張三"); // 等價於:Person對象.setName("張三")
        System.out.println(getMet.invoke(obj));// 等價於:Person對象.getName()
    }

    public static String initcap(String str) {
        return str.substring(0, 1).toUpperCase().concat(str.substring(1));
    }
}

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

3、調用成員類之中最後一個組成部分

就是成員(Field,也可以稱為屬性),如果要通過反射取得類的成員可以使用方法如下:

  • 取得本類的全部成員:

    public Field[] getDeclaredFields() throws SecurityException;
  • 取得指定的成員:

    public Field getDeclaredField(String name) throws NoSuchFieldException, SecurityException;

    這兩個方法的返回值類型是java.lang.reflect.Field類的對象,下面首先觀察如何取得一個類之中的全部屬性。

範例:取得一個類之中的全部屬性

packagecn.mldn.demo;
import java.lang.reflect.Field;

class Person {
    private String name;
}

public class TestDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("cn.mldn.demo.Person"); // 取得Class對象
        Field field[] = cls.getDeclaredFields(); // 取得全部屬性
        for (intx = 0; x < field.length; x++) {
            System.out.println(field[x]);
        }
    }
}

但是找到Field實際上就找到了一個很有意思的操作,在Field類之中提供了兩個方法:

  • 設置屬性內容(類似於:對象.屬性= 內容):public void set(Object obj,
    Object value)throws
    IllegalArgumentException, IllegalAccessException;

  • 取得屬性內容(類似於:對象.屬性):public Object get(Object obj)throws
    IllegalArgumentException, IllegalAccessException可是從類的開發要求而言,一直都強調類之中的屬性必須封裝,所以現在調用之前要想辦法解除封裝。
    解除封裝:

    public void
    setAccessible(boolean flag) throws SecurityException;

    範例:利用反射操作類中的屬性

packagecn.mldn.demo;
        import java.lang.reflect.Field;

class Person {
    private String name;
}

public class TestDemo {
    public static void main(String[] args) throws Exception {
        Class<?> cls = Class.forName("cn.mldn.demo.Person"); // 取得Class對象
        Object obj = cls.newInstance(); // 對象實例化屬性才會分配空間
        Field nameField = cls.getDeclaredField("name"); // 找到name屬性
        nameField.setAccessible(true); // 解除封裝了
        nameField.set(obj, "張三"); // Person對象.name = "張三"
        System.out.println(nameField.get(obj)); // Person對象.name
    }
}

轉自:https://www.zhihu.com/question/24304289/answer/38218810

如何理解java反射?