歡迎來到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