Java基礎--反射

什麼是Java反射

概念

java反射是指java能夠在運行時確定類的類型信息,包括其方法、字段、構造函數等,並能夠通過反射調用類或者類對象的方法。在Java中,java.lang.Class類與java.lang.reflect類庫一起對反射的概念進行了支持。

簡單理解編譯和運行

在運行一個java程序的時候,需要依次調用javac java命令來執行編譯和運行。
對如下所示的程序

// OfficeCom.java
public interface OfficeCom {
    public void start();
}

// Word.java
public class Word implements OfficeCom {
    public void start() {
        System.out.println("Word!");
    }
}

// Excel.java
public class Excel implements OfficeCom {
    public void start() {
        System.out.println("Excel!");
    }
}

// Demo.java
public class Demo {
    public static void main (String[] args) {
        OfficeCom officeCom = null;
        if ("Word".equals(args[0])) {
            officeCom = new Word();
        } else if ("Excel".equals(args[0])) {
            officeCom = new Excel();
        }       
        officeCom.start();
    }   
}

執行編譯命令javac Demo.java後,在目錄下生成了Demo.class Word.class Excel.class OfficeCom.class文件,然後執行java Demo Word, 輸出Word!
如果目錄下缺少了Word.java文件後,執行javac Demo.java,編譯錯誤,編譯無法通過。

我們將Demo.java程序修改成反射獲取類信息

// Demo.java
public class Demo {
    public static void main (String[] args) {
        try {
            Class c = Class.forName(args[0]);
            OfficeCom o = (OfficeCom)c.newInstance();
            o.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

然後執行javac Demo.java, 在目錄下生成了Demo.classOfficeCom.class,如果這個時候執行java Demo Word,會報錯。對反射機制來說,.class文件在編譯時是不可獲取的,是在運行時打開和檢查.class文件(注:java編程思想P335)。在執行javac Word.java後再執行java Demo Word,則正常輸出Word!。注意到,這個時候,我們是否編寫了Excel.java不會影響程序的編譯和執行。如果後續需要更多的OfficeCom組件,只需要編寫相應的java文件而不需要修改Demo.java。這也是動態加載類的一個優點。

Class類與類類型

Class類

在java中一切都是對象,通過一個類(Class),我們可以實例化一個對象(Object),同時這個類也是Class類的對象。

Instances of the class Class represent classes and interfaces in a running Java application. An enum is a kind of class and an annotation is a kind of interface. Every array also belongs to a class that is reflected as a Class object that is shared by all arrays with the same element type and number of dimensions. The primitive Java types (boolean, byte, char, short, int, long, float, and double), and the keyword void are also represented as Class objects.

通過查看Class類的源碼, 只有JVM能夠構造Class類對象

    /*
     * Private constructor. Only the Java Virtual Machine creates Class objects.
     * This constructor is not used and prevents the default constructor being
     * generated.
     */
    private Class(ClassLoader loader) {
        // Initialize final field for classLoader.  The initialization value of non-null
        // prevents future JIT optimizations from assuming this final field is null.
        classLoader = loader;
    }

獲取類類型

我們通過一個已經創建的類獲得的Class對象可以簡單稱爲類類型(class type),獲得類類型有三種方法。

//字面常量的方式,實際在告訴我們任何一個類都有一個隱含的靜態成員變量class
Class c1 = Foo.class;

//第二中表達方式  已經知道該類的對象通過getClass方法
Class c2 = foo1.getClass();

//不管c1  or c2都代表了Foo類的類類型,一個類只可能是Class類的一個實例對象
System.out.println(c1 == c2);

//第三種表達方式
Class c3 = null;
try {
    c3 = Class.forName("com.imooc.reflect.Foo");
} catch (ClassNotFoundException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}
System.out.println(c2==c3);  // true

//我們完全可以通過類的類類型創建該類的對象實例
try {
    Foo foo = (Foo)c1.newInstance();//需要有無參數的構造方法
    foo.print();
} catch (InstantiationException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
} catch (IllegalAccessException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}

上述還演示了通過類類型來實例化一個類對象的方法。
通過類類型我們還能獲得更過關於這個類的信息。

反射方法的使用

Class c = obj.getClass();
// 獲取類方法
Method[] methods = c.getMethods(); // 獲取此類和其超類的public方法
Method[] methods = c.getDeclaredMethods(); // 獲取此類的所有聲明的方法,不包括繼承的方法

// 獲取構造函數
Constructor[] constructors = c.getConstructors(); // 獲取此類的public構造方法
Constructor[] constructors = c.getDeclaredConstructors(); // 獲取此類聲明的所有構造方法
// 獲取字段
Field[] fields = c.getFields(); // 獲取此類的public字段
Field[] fields = c.getDeclaredFields(); // 獲取所有此類聲明的字段

獲得到類的方法對象後,可以執行這個方法

public class Demo1 {

    public static void main(String[] args) {
        Foo foo = new Foo();
        Class c = foo.getClass();
        try {
            Method method = c.getMethod("print", int.class, int.class);
            method.invoke(foo, 10, 20); // invoke第一個參數是執行方法的對象
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}
class Foo {
    public void print(int a, int b) {
        System.out.println(a + b);
    }
    public void print(String a, String b) {
        System.out.println(a + b);
    }
}

同樣可以獲取構造函數生成一個實例

try {
         Constructor constructor = c.getDeclaredConstructor(String.class, int.class);
         constructor.setAccessible(true);  // 因爲是私有的,所以需要聲明可訪問
         constructor.newInstance("hello", 5);
      } catch (NoSuchMethodException e) {
          e.printStackTrace();
      } catch (IllegalAccessException e) {
          e.printStackTrace();
      } catch (InstantiationException e) {
          e.printStackTrace();
      } catch (InvocationTargetException e) {
          e.printStackTrace();
      }

其他

Java編程思想中提到RTTI(Runtime Type Information),這個與反射有什麼區別?參考知乎問題

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章