深刻理解Java反射(一)

一、什麼是反射?

咱們先給一個比較官方的說法:反射(Reflection)是Java 程序開發語言的特徵之一,它容許運行中的 Java 程序獲取自身的信息,而且能夠操做類或對象的內部屬性。
簡單的說就是:
經過反射,咱們能夠在運行時得到程序或程序集中每個類型的成員和成員的信息。
咱們知道咱們在寫代碼的時候,對象類型通常都是在編譯期肯定下來的。反射機制容許咱們動態的建立對象而且調用其相關屬性。即便類型在編譯期是未知的,反射機制也能經過加載類型信息,建立相關對象。 反射的核心是JVM在運行時才動態加載類或調用方法/訪問屬性,它不須要事先(寫代碼的時候或編譯期)知道運行對象是誰。
java

二、Java反射框架提供的相關功能

  • 在運行時判斷任意一個對象所屬的類;
  • 在運行時構造任意一個類的對象;
  • 在運行時判斷任意一個類所具備的成員變量和方法(經過反射甚至能夠調用private方法);
  • 在運行時調用任意一個對象的方法。

判斷任意一個對象所屬的類(獲取Class對象)

有3種方法能夠獲取Class對象,咱們來依次介紹數組

Class類的Class.forName靜態方法。

Class<?> personClass=Class.forName("com.learn.example.Person");
複製代碼

直接獲取某一類型的class

Class<?> personClass=Person.class;
複製代碼

經過調用對象的getClass()方法

Person createPerson=new Person();
personClass=createPerson.getClass();
複製代碼

判斷是不是某一個類的實例

一般這類操做咱們是經過instanceof關鍵字來進行判斷的,不過經過Class對象,咱們能夠經過isInstance方法來進行判斷。bash

Class<?> personClass=Person.class;
personClass.isInstance(obj);
複製代碼

經過isInstance方法咱們能夠判斷obj對象是不是Person類的實例。框架

經過反射來建立實例

經過反射來建立實例主要有兩種方法。函數

使用Class對象的newInstance()方法來建立Class對象對應類的實例

Class<?> personClass=Person.class;
Person createPerson;
createPerson=(Person)personClass.newInstance();
複製代碼

注意:經過這種方法只能調用默認的構造函數。ui

先經過Class對象獲取指定的Constructor對象,再調用Constructor對象的newInstance()方法來建立實例。這種方法能夠用指定的構造器構造類的實例。

//調用無參構造函數
createPerson=(Person)personClass.getConstructor().newInstance();
createPerson.sayInfo();
//調用有兩個參數的構造函數
createPerson=(Person)personClass.getConstructor(String.class,int.class).newInstance("gong",27);
createPerson.sayInfo();
複製代碼

經過這種方式咱們既能夠調用默認的構造函數,也能夠調用指定的構造函數,這種方式的靈活性更好一些。this

獲取一個類的方法

在討論這個議題以前,咱們先把Person類的代碼貼出來spa

class Person {
    private String name;
    private int age;
    
    public Person() {
    	this("init",88);
    }
    
    public Person(String name,int age) {
    	this.name=name;
    	this.age=age;
    }
    
    private void setName(String name) {
    	this.name=name;
    }
    
    private void setAge(int age) {
    	this.age=age;
    }
    
    public void sayInfo() {
    	System.out.println("name:"+this.name);
    	System.out.println("age:"+this.age);
    }
}
複製代碼

獲取一個類的方法集合,有三種方法。code

getDeclaredMethods()方法返回類或接口聲明的全部方法,包括公共、保護、默認(包)訪問和私有方法,但不包括繼承的方法。

//不包含繼承的方法
Method[] declareMethods=personClass.getDeclaredMethods();
if(declareMethods!=null &&declareMethods.length>0) {
    for (Method method : declareMethods) {
        System.out.println(method);
    }
}
複製代碼

咱們看輸出:
對象

public void com.learn.example.Person.sayInfo()
private void com.learn.example.Person.setAge(int)
private void com.learn.example.Person.setName(java.lang.String)
複製代碼

從輸出結果咱們看到,getDeclaredMethods()方法只會返回當前類或接口定義的全部方法。

getMethods()方法返回某個類的全部公用(public)方法,包括其繼承類的公用方法。

//類的全部公用(public)方法,包括其繼承類的公用方法
Method[] methods=personClass.getMethods();
for (Method method : methods) {
    System.out.println(method);
}
複製代碼

咱們看輸出:

public void com.learn.example.Person.sayInfo()
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 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()
複製代碼

從結果裏面咱們看到,getMethods()方法只返回Person類中public方法sayInfo()。剩下的就是繼承自Object基類的全部public方法。

獲取一個類的字段

Java反射機制也提供了相關方法來獲取一個類的相關字段。
主要是這幾個方法:

  • getFiled: 訪問公有的成員變量
  • getDeclaredField:類中已聲明的private成員變量。但不能獲得其父類的成員變量
  • getFileds和getDeclaredFields用法同上(參照Method)
    請看下面的例子:
Field field=personClass.getDeclaredField("name");
//容許訪問private字段
field.setAccessible(true);
field.set(createPerson, "XXXXXXX");
createPerson.sayInfo();
複製代碼

咱們經過getDeclaredField方法來動態修改了實例的name屬性值。這樣輸出的時候createPerson對象的name屬性值就被咱們動態的修改成「XXXXXXX」了。

在運行時調用任意一個對象的方法(invoke方法)

咱們前面經過getMethods()方法和getDeclaredMethods()方法獲取了類的方法列表。其實反射還支持動態執行類方法,下面咱們經過例子來看下:

//動態執行方法
Method method=personClass.getDeclaredMethod("setName",String.class);
method.setAccessible(true);
method.invoke(createPerson,"WWWWW");
createPerson.sayInfo();
複製代碼

咱們在例子裏面,咱們看到咱們經過getDeclaredMethod方法來動態調用setName方法。第一個參數是方法名,後面的參數是方法的參數。細心的朋友可能發現setName是private方法啊,這也能調用?的確,默認狀況是不能調用的。可是咱們能夠經過setAccessible來設置是否容許調用private(私有)的方法。
注意: getDeclaredMethod()方法和getDeclaredMethods()方法功能是對應的,這個方法只能獲取到類本身定義的方法。 getMethod()方法和getMethods()方法功能是對應的。這個方法只能訪問全部的public方法。

使用反射來建立數組

數組在Java裏是比較特殊的一種類型,它能夠賦值給一個Object Reference。下面咱們看一看利用反射建立數組的例子:

public static void testArray() throws ClassNotFoundException {
    Class<?> cls = Class.forName("java.lang.String");
    Object array = Array.newInstance(cls,10);
    //往數組裏添加內容
    Array.set(array,0,"Java");
    Array.set(array,1,"C++");
    Array.set(array,2,"JavaScript");
    Array.set(array,3,"Python");
    Array.set(array,4,"Kotlin");
    //獲取某一項的內容
    System.out.println(Array.get(array,3));
    }
複製代碼

其中的Array類爲java.lang.reflect.Array類。咱們經過Array.newInstance()建立數組對象。第一個參數是數組的類型,第二個參數是數組的長度。

三、反射的做用

反射最重要的用途就是開發各類通用框架。 不少框架(如Spring)都是配置化的(好比經過XML文件配置JavaBean,Action之類的),爲了保證框架的通用性,它們可能須要根據配置文件加載不一樣的對象或類,調用不一樣的方法,這個時候就必須用到反射--運行時動態加載須要加載的對象。

相關文章
相關標籤/搜索