歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
 Linux教程網 >> Linux編程 >> Linux編程 >> Java反射機制淺析

Java反射機制淺析

日期:2017/4/19 14:16:59      编辑:Linux編程

概念
  Java反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制。

  Class類與java.lang.reflect類庫一起對反射的概念進行了支持,該類庫包含了Field、Method以及Constructor類(每個類都實現了Member接口)。這些類型的對象是由JVM在運行時創建的,用以表示未知類裡對應的成員。這樣開發人員就可以使用Constructor創建新的對象,用get()和set()方法讀取和修改與Field對象關聯的字段,用invoke()方法調用與Method對象關聯的方法。另外,還可以調用getFields()、getMethods()、getConstructors()等很便利的方法,以返回表示字段、方法以及構造器的對象的數組。這樣,匿名對象的類信息就能在運行時被完全確認下來,而在編譯時不需要知道任何事情。

Class 

  類是程序的一部分,每個類都有一個Class對象。換言之,每當編寫並且編譯一個新類,就會產生一個Class對象(更恰當地說,是被保存在一個同名的.class文件中),它包含了與類有關的信息。為了生成這個類的對象,運行這個對象的虛擬機(JVM)將使用被稱為“類加載器(ClassLoader)”的子系統。下面測試類的一些最基本信息。

public class ClassInfo {
 
    /**
    * @description 輸出不同格式類名
    * @param clazz
    */
    public static void printName(Class<?> clazz) {
        System.out.println("getName: " + clazz.getName());
        System.out.println("getCanonicalName: " + clazz.getCanonicalName());
        System.out.println("getSimpleName: " + clazz.getSimpleName());
    }
   
    /**
    * @description 輸出類的父類和接口
    * @param clazz
    */
    public static void printClassIntf(Class<?> clazz) {
        Class<?> superClass = clazz.getSuperclass();
        Class<?>[] interfaces = clazz.getInterfaces();
        if(superClass != null) {
            System.out.print(clazz.getSimpleName() + " extends " + superClass.getSimpleName());
        }
        if(interfaces.length > 0) {
            System.out.print(" implements ");
            for(int i = 0; i < interfaces.length - 1; i++) {
                System.out.print(interfaces[i].getSimpleName() + ", ");
            }
            System.out.println(interfaces[interfaces.length - 1].getSimpleName());
        }
    }
}

測試類 測試用例:ArrayList

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
 
public class ClassInfoTest {
   
    private Class<?> clazz;
   
    private String className = "java.util.ArrayList";
   
    /**
    * forName()是獲取Class對象的引用的一種方法。
    * 它是用一個包含目標類的文本名的String作輸入參數,返回的是一個Class對象的引用。
    */
    @Before
    public void before() {
        try {
            clazz = Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
   
    @After
    public void after() {
        clazz = null;
    }
   
    @Test
    public void testGetName() {
        ClassInfo.printName(clazz);
    }
   
    @Test
    public void testPrintClassIntf() {
        ClassInfo.printClassIntf(clazz);
    }
 
}

測試結果

getName: java.util.ArrayList
getCanonicalName: java.util.ArrayList
getSimpleName: ArrayList
ArrayList extends AbstractList implements List, RandomAccess, Cloneable, Serializable

Constructor
  Constructor類是對Java普通類中的構造器的抽象。通過Class類的getConstructors()方法可以取得表示構造器的對象的數組,通過getConstructor(Class<?>... parameterTypes)可以取得指定參數類型的構造器。通過newInstance(Object... initargs)方法可以構建一個實例對象。需要注意的是:newInstance()方法的參數要和getConstructor()方法參數相對應。例如 getConstructor(String.class) --- getInstance("Jack")。

  以下的測試都是假設我們從磁盤上或者網絡中獲取一個類的字節,得知這個類的包名(reflcet)和類名(Reflect)和相關字段名稱和方法名稱,並通過熱加載已經加載到工程中。

  method.invoke()會在下文Method中講到。

 待測類

package reflect;
 
/**
 * @description 運行時獲取的類
 * @author Administrator
 */
public class Reflect {
   
    public int id;
    private String name;
   
    public Reflect() {
        this.name = "Tom";
    }
   
    public Reflect(String name) {
        this.name = name;
    }
   
    public Reflect(int id, String name) {
        this.id = id;
        this.name = name;
    }
   
    public void setName(String name) {
        this.name = name;
    }
   
    public String getName() {
        return name;
    }
   
    @SuppressWarnings("unused")
    private void setId(int id) {
        this.id = id;
    }
   
    public int getId() {
        return id;
    }
   
    @Override
    public String toString() {
        return "id:" + id + ", name:" + name;
    }
 
}

 測試類

import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
 
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
 
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
 
public class ReflectTest {
   
    Class<?> clazz;
   
    /**
    * className = "包名.類名"
    */
    String className = "reflect.Reflect";
   
    @Before
    public void before() {
        try {
            clazz = Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
   
    @After
    public void after() {
        clazz = null;
    }
   
    @Test
    public void testConstructor() {
        try {
            /**
            * 獲取無參構造器
            */
            Constructor<?> constructor = clazz.getConstructor();
            Object obj = constructor.newInstance();
            Method method = clazz.getMethod("getName");
            assertThat((String)method.invoke(obj), containsString("Tom"));
            /**
            * 獲取帶參構造器
            */
            constructor = clazz.getConstructor(String.class);
            obj = constructor.newInstance("Jack");
            assertThat((String)method.invoke(obj), containsString("Jack"));
            /**
            * 獲取多個參數構造器
            */
            constructor = clazz.getConstructor(int.class, String.class);
            obj = constructor.newInstance(6, "Rose");
            method = clazz.getMethod("toString");
            assertThat((String)method.invoke(obj), allOf(containsString("id:6"), containsString("name:Rose")));
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
       
    }
   
}

Field

  Field類是對Java普通類中的屬性或者稱字段的抽象。通過Class類的getFields()方法可以取得表示字段的對象的數組,通過getField(String name)獲取給定名稱的字段的對象,如果字段修飾符為private或protected,則getField()方法會拋出java.lang.NoSuchFieldException異常。對於非公有的屬性的設定,可以使用getDeclaredField()方法,並調用setAccessible(true),使屬性可獲得。


測試方法

@Test
public void testField() {
    try {
        /**
        * Class類的newInstance()方法會調用默認構造函數創建一個實例對象
        */
        Object obj = clazz.newInstance();
        Method method = clazz.getMethod("getName");
        assertThat((String)method.invoke(obj), containsString("Tom"));
        /**
        * 設定private屬性的值
        */
        Field field = clazz.getDeclaredField("name");
        field.setAccessible(true);
        field.set(obj, "Jack");
        assertThat((String)method.invoke(obj), containsString("Jack"));
        /**
        * 設定public屬性的值
        */
        field = clazz.getField("id");
        field.setInt(obj, 9);
        method = clazz.getMethod("getId");
        assertThat(String.valueOf(method.invoke(obj)), containsString("9"));
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (SecurityException e) {
        e.printStackTrace();
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    } catch (NoSuchFieldException e) {
        e.printStackTrace();
    }
}

Method

  Method類是對Java普通類中的方法的抽象。通過Class類的getMethods()方法可以取得表示方法的對象的數組,通過getMethod(String name, Class<?>... parameterTypes)方法可以取得指定方法名稱以及方法參數類型的方法的對象,如果方法修飾符為private或protected,getMethod()方法會拋出java.lang.NoSuchMethodException異常。對於非公有的方法,可以通過getDeclaredMethod()方法,並調用setAccessible(true),使方法可獲得。調用method.invoke(Object obj, Object... args)方法,實現obj對象對方法method的調用,參數為args。和構造器同樣的道理,getMethod()方法和invoke方法要相對應。例如getMethod("setName", String.class) --- invoke(obj, "Rose")。

測試方法

@Test
public void testMethod() {
    try {
        /**
        * 調用無參公有方法
        */
        Object obj = clazz.newInstance();
        Method method1 = clazz.getMethod("getName");
        assertThat((String)method1.invoke(obj), containsString("Tom"));
        /**
        * 調用帶參公有方法
        */
        Method method2 = clazz.getMethod("setName", String.class);
        method2.invoke(obj, "Jack");
        assertThat((String)method1.invoke(obj), containsString("Jack"));
        /**
        * 調用帶參私有方法
        */
        Method method3 = clazz.getDeclaredMethod("setId", int.class);
        method3.setAccessible(true);
        method3.invoke(obj, 5);
        Method method = clazz.getMethod("getId");
        assertThat(String.valueOf(method.invoke(obj)), containsString("5"));
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    } catch (SecurityException e) {
        e.printStackTrace();
    } catch (IllegalArgumentException e) {
        e.printStackTrace();
    } catch (InvocationTargetException e) {
        e.printStackTrace();
    }
}

類方法提取器
  當開發者學習一個類(比如:ArrayList)時,通過浏覽實現了類定義的源代碼或是其JDK文檔,只能找到在這個類定義中被定義或被覆蓋的方法。但對開發者來說,可能有數十個更有用的方法都是繼承自基類的。要找出這些方法可能很乏味且費時。幸運的是,反射機制提供了一個方法,使開發者能夠編寫可以自動展示完整接口的簡單工具。工作方式如下:

/**
 * @description 類方法提取器
 * @param clazz
 */
public static void printClassInfo(Class<?> clazz) {
    Pattern pattern = Pattern.compile(("\\w+\\."));
    Constructor<?>[] constructors = clazz.getConstructors();
    Method[] methods = clazz.getMethods();
    Field[] fields = clazz.getDeclaredFields();
    for(Field field : fields) {
        System.out.println(pattern.matcher(field.toGenericString()).replaceAll(""));
    }
    for(Constructor<?> constructor : constructors) {
        System.out.println(pattern.matcher(constructor.toGenericString()).replaceAll(""));
    }
    for(Method method : methods) {
        System.out.println(pattern.matcher(method.toGenericString()).replaceAll(""));
    }
}

 測試方法 測試用例:ArrayList

@Test
public void testPrintClassInfo() {
    try {
        ClassInfo.printClassInfo(Class.forName("java.util.ArrayList"));
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }
}

 測試結果:

private static final long serialVersionUID
private static final int DEFAULT_CAPACITY
private static final Object[] EMPTY_ELEMENTDATA
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA
transient Object[] elementData
private int size
private static final int MAX_ARRAY_SIZE
public ArrayList(Collection<? extends E>)
public ArrayList()
public ArrayList(int)
public boolean add(E)
public void add(int,E)
public boolean remove(Object)
public E remove(int)
public E get(int)
public Object clone()
public int indexOf(Object)
public void clear()
public boolean contains(Object)
public boolean isEmpty()
public Iterator<E> iterator()
public int lastIndexOf(Object)
public void replaceAll(UnaryOperator<E>)
public int size()
public List<E> subList(int,int)
public <T> T[] toArray(T[])
public Object[] toArray()
public Spliterator<E> spliterator()
public boolean addAll(int,Collection<? extends E>)
public boolean addAll(Collection<? extends E>)
public void forEach(Consumer<? super E>)
public E set(int,E)
public void ensureCapacity(int)
public void trimToSize()
public ListIterator<E> listIterator(int)
public ListIterator<E> listIterator()
public boolean removeAll(Collection<?>)
public boolean removeIf(Predicate<? super E>)
public boolean retainAll(Collection<?>)
public void sort(Comparator<? super E>)
public boolean equals(Object)
public int hashCode()
public String toString()
public boolean containsAll(Collection<?>)
public final void wait() throws InterruptedException
public final void wait(long,int) throws InterruptedException
public final native void wait(long) throws InterruptedException
public final native Class<?> getClass()
public final native void notify()
public final native void notifyAll()
public default Stream<E> stream()
public default Stream<E> parallelStream()

JVM
  反射機制並沒有什麼神奇之處。當通過反射與一個未知類型的對象打交道時,JVM只是簡單的檢查這個對象。因此,那個類的.class文件對於JVM來說必須是可獲取的:要麼在本地機器上,要麼可以通過網絡取得。對已反射機制來說,.class文件在編譯時時不可獲取的 所以是在運行時打開和檢查.class文件。

應用
  假設開發者從磁盤文件,或者網絡連接中獲取一串字節,並且被告知這些字節代表了一個類。既然這個類在程序編譯後很久才出現,若想使用這個類,就需要采用發射機制。熱加載就屬於這種場景。

  在運行時獲取類的信息的另一個場景,開發者希望提供在跨網絡的遠程平台上創建和運行對象的能力。這被稱為遠程方法調用(RMI) 它允許一個Java程序將對象分布到多台機器上。

  在自己寫框架時候,開發者肯定會用到反射,很簡單的例子就是事件總線和注解框架。


總結

  反射很靈活,在日常開發中,慎用少用反射,反射會犧牲部分性能。在寫框架時,不避諱反射,在關鍵時利用反射助自己一臂之力。

Copyright © Linux教程網 All Rights Reserved