Java基礎13——深入理解java中的反射機制
java中的反射機制
什麼是反射?
反射(Reflection)是Java 程式開發語言的特徵之一,它允許執行中的 Java 程式獲取自身的資訊,並且可以操作類或物件的內部屬性。
簡而言之,通過反射,我們可以在執行時獲得程式或程式集中每一個型別的成員和成員的資訊。
程式中一般的物件的型別都是在編譯期就確定下來的,而Java反射機制可以動態地建立物件並呼叫其屬性,這樣的物件的型別在編譯期是未知的。所以我們可以通過反射機制直接建立物件,即使這個物件的型別在編譯期是未知的。
反射的核心是JVM在執行時才動態載入類或呼叫方法/訪問屬性,它不需要事先(寫程式碼的時候或編譯期)知道執行物件是誰。
Java反射框架主要提供以下功能:
1.在執行時判斷任意一個物件所屬的類;
2.在執行時構造任意一個類的物件;
3.在執行時判斷任意一個類所具有的成員變數和方法(通過反射甚至可以呼叫private方法);
4.在執行時呼叫任意一個物件的方法
重點:是執行時而不是編譯時
反射的基礎:關於Class類
詳細的介紹見上一章。
1、Class是一個類,一個描述類的類(也就是描述類本身),封裝了描述方法的Method,描述欄位的Filed,描述構造器的Constructor等屬性
2、物件照鏡子後(反射)可以得到的資訊:某個類的資料成員名、方法和構造器、某個類到底實現了哪些介面。
3、對於每個類而言,JRE 都為其保留一個不變的 Class 型別的物件。一個Class物件包含了特定某個類的有關資訊。
4、Class 物件只能由系統建立物件
5、一個類在 JVM 中只會有一個Class例項
//總結一下就是,JDK有一個類叫做Class,這個類用來封裝所有Java型別,包括這些類的所有資訊,JVM中類資訊是放在方法區的。
//所有類在載入後,JVM會為其在堆中建立一個Class<類名稱>的物件,並且每個類只會有一個Class物件,這個類的所有物件都要通過Class<類名稱>來進行例項化。
//上面說的是JVM進行例項化的原理,當然實際上在Java寫程式碼時只需要用 類名稱就可以進行例項化了。
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {
虛擬機器會保持唯一一
//通過類名.class獲得唯一的Class物件。
Class<UserBean> cls = UserBean.class;
//通過integer.TYPEl來獲取Class物件
Class<Integer> inti = Integer.TYPE;
//介面本質也是一個類,一樣可以通過.class獲取
Class<User> userClass = User.class;
獲得Class物件方法
(1)使用Class類的forName靜態方法:、
public static Class<?> forName(String className)
在JDBC開發中常用此方法載入資料庫驅動:
要使用全類名來載入這個類,一般資料庫驅動的配置資訊會寫在配置檔案中。載入這個驅動前要先匯入jar包
Class.forName(driver);
(2)直接獲取某一個物件的class,比如:
//Class<?>是一個泛型表示,用於獲取一個類的型別。
Class<?> klass = int.class;
Class<?> classInt = Integer.TYPE;
(3)呼叫某個物件的getClass()方法,比如:
StringBuilder str = new StringBuilder("123");
Class<?> klass = str.getClass();
判斷是否為某個類的例項
一般地,我們用instanceof關鍵字來判斷是否為某個類的例項。同時我們也可以藉助反射中Class物件的isInstance() 方法來判斷是否為某個類的例項,它是一個Native方法:
public native boolean isInstance(Object obj);
建立例項
(1)使用Class物件的newInstance()方法來建立Class物件對應類的例項。
注意:利用newInstance建立物件:呼叫的類必須有無參的構造器
//Class<?>代表任何類的一個類物件。
//使用這個類物件可以為其他類進行例項化
//因為jvm載入類以後自動在堆區生成一個對應的*.Class物件
//該物件用於讓JVM對進行所有*物件例項化。
Class<?> c = String.class;
//Class<?> 中的 ? 是萬用字元,其實就是表示任意符合泛類定義條件的類,和直接使用 Class
//效果基本一致,但是這樣寫更加規範,在某些型別轉換時可以避免不必要的 unchecked 錯誤。
Object str = c.newInstance();
(2)先通過Class物件獲取指定的Constructor物件,再呼叫Constructor物件的newInstance()方法來建立例項。這種方法可以用指定的構造器構造類的例項。
//獲取String所對應的Class物件
Class<?> c = String.class;
//獲取String類帶一個String引數的構造器
Constructor constructor = c.getConstructor(String.class);
//根據構造器建立例項
Object obj = constructor.newInstance("23333");
System.out.println(obj);
獲取方法
1、getDeclaredMethods()方法返回類或介面宣告的所有方法,包括公共、保護、預設(包)訪問和私有方法,但不包括繼承的方法。
public Method[] getDeclaredMethods() throws SecurityException
2、getMethods()方法返回某個類的所有公用(public)方法,包括其繼承類的公用方法。
public Method[] getMethods() throws SecurityException
3、getMethod方法返回一個特定的方法,其中第一個引數為方法名稱,後面的引數為方法的引數對應Class的物件。
public Method getMethod(String name, Class<?>... parameterTypes)
//userbean的父類personbean
public class PersonBean {
private String name;
int id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//介面user
public interface User {
public void login ();
}
//userBean實現user介面,繼承personbean
public class UserBean extends PersonBean implements User{
@Override
public void login() {
}
class B {
}
public String userName;
protected int i;
static int j;
private int l;
private long userId;
public UserBean(String userName, long userId) {
this.userName = userName;
this.userId = userId;
}
public String getName() {
return userName;
}
public long getId() {
return userId;
}
@Invoke
public static void staticMethod(String devName,int a) {
System.out.printf("Hi %s, I'm a static method", devName);
}
@Invoke
public void publicMethod() {
System.out.println("I'm a public method");
}
@Invoke
private void privateMethod() {
System.out.println("I'm a private method");
}
}
getMethods和getDeclaredMethods的區別
public class 動態載入類的反射 {
public static void main(String[] args) {
try {
Class clazz = Class.forName("com.javase.反射.UserBean");
for (Field field : clazz.getDeclaredFields()) {
// field.setAccessible(true);//對私有欄位的訪問取消許可權,暴力訪問
System.out.println(field);
}
//getDeclaredMethods()獲取的是類自身宣告的所有方法,包含public、protected和private方法。
System.out.println("------共有方法------");
// getDeclaredMethods()獲取的是類自身宣告的所有方法,包含public、protected和private方法。
// getMethods()獲取的是類的所有共有方法,這就包括自身的所有public方法,和從基類繼承的、從介面實現的所有public方法。
for (Method method : clazz.getMethods()) {
String name = method.getName();
System.out.println(name);
//打印出了UserBean.java的所有方法以及父類的方法
}
System.out.println("------獨佔方法------");
for (Method method : clazz.getDeclaredMethods()) {
String name = method.getName();
System.out.println(name);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
獲取構造器資訊
public class 列印構造方法 {
public static void main(String[] args) {
// constructors
Class<?> clazz = UserBean.class;
Class userBeanClass = UserBean.class;
//獲得所有的構造方法
Constructor[] constructors = userBeanClass.getDeclaredConstructors();
for (Constructor constructor : constructors) {
String s = Modifier.toString(constructor.getModifiers()) + " ";
s += constructor.getName() + "(";
//構造方法的引數型別
Class[] parameters = constructor.getParameterTypes();
for (Class parameter : parameters) {
s += parameter.getSimpleName() + ", ";
}
s += ")";
System.out.println(s);
//列印結果//public com.javase.反射.UserBean(String, long, )
}
}
}
獲取類的成員變數(欄位)資訊
主要是這幾個方法,在此不再贅述:
getFiled: 訪問公有的成員變數
getDeclaredField:所有已宣告的成員變數。但不能得到其父類的成員變數
getFileds和getDeclaredFields用法同上(參照Method)
public class 列印成員變數 {
public static void main(String[] args) {
Class userBeanClass = UserBean.class;
//獲得該類的所有成員變數,包括static private
Field[] fields = userBeanClass.getDeclaredFields();
for(Field field : fields) {
//private屬性即使不用下面這個語句也可以訪問
// field.setAccessible(true);
//因為類的私有域在反射中預設可訪問,所以flag預設為true。
String fieldString = "";
fieldString += Modifier.toString(field.getModifiers()) + " "; // `private`
fieldString += field.getType().getSimpleName() + " "; // `String`
fieldString += field.getName(); // `userName`
fieldString += ";";
System.out.println(fieldString);
//列印結果
// public String userName;
// protected int i;
// static int j;
// private int l;
// private long userId;
}
}
}
呼叫方法
當我們從類中獲取了一個方法後,我們就可以用invoke()方法來呼叫這個方法。invoke方法的原型為:
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
public class 使用反射呼叫方法 {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchMethodException {
Class userBeanClass = UserBean.class;
//獲取該類所有的方法,包括靜態方法,例項方法。
//此處也包括了私有方法,只不過私有方法在用invoke訪問之前要設定訪問許可權
//也就是使用setAccessible使方法可訪問,否則會丟擲異常
// // IllegalAccessException的解釋是
// * An IllegalAccessException is thrown when an application tries
// * to reflectively create an instance (other than an array),
// * set or get a field, or invoke a method, but the currently
// * executing method does not have access to the definition of
// * the specified class, field, method or constructor.
// getDeclaredMethod*()獲取的是類自身宣告的所有方法,包含public、protected和private方法。
// getMethod*()獲取的是類的所有共有方法,這就包括自身的所有public方法,和從基類繼承的、從介面實現的所有public方法。
//就是說,當這個類,域或者方法被設為私有訪問,使用反射呼叫但是卻沒有許可權時會丟擲異常。
Method[] methods = userBeanClass.getDeclaredMethods(); // 獲取所有成員方法
for (Method method : methods) {
//反射可以獲取方法上的註解,通過註解來進行判斷
if (method.isAnnotationPresent(Invoke.class)) { // 判斷是否被 @Invoke 修飾
//判斷方法的修飾符是是static
if (Modifier.isStatic(method.getModifiers())) { // 如果是 static 方法
//反射呼叫該方法
//類方法可以直接呼叫,不必先例項化
method.invoke(null, "wingjay",2); // 直接呼叫,並傳入需要的引數 devName
} else {
//如果不是類方法,需要先獲得一個例項再呼叫方法
//傳入構造方法需要的變數型別
Class[] params = {String.class, long.class};
//獲取該類指定型別的構造方法
//如果沒有這種型別的方法會報錯
Constructor constructor = userBeanClass.getDeclaredConstructor(params); // 獲取引數格式為 String,long 的建構函式
//通過構造方法的例項來進行例項化
Object userBean = constructor.newInstance("wingjay", 11); // 利用建構函式進行例項化,得到 Object
if (Modifier.isPrivate(method.getModifiers())) {
method.setAccessible(true); // 如果是 private 的方法,需要獲取其呼叫許可權
// Set the {@code accessible} flag for this object to
// * the indicated boolean value. A value of {@code true} indicates that
// * the reflected object should suppress Java language access
// * checking when it is used. A value of {@code false} indicates
// * that the reflected object should enforce Java language access checks.
//通過該方法可以設定其可見或者不可見,不僅可以用於方法
//後面例子會介紹將其用於成員變數
//列印結果
// I'm a public method
// Hi wingjay, I'm a static methodI'm a private method
}
method.invoke(userBean); // 呼叫 method,無須引數
}
}
}
}
}
利用反射建立陣列
陣列在Java裡是比較特殊的一種型別,它可以賦值給一個Object Reference。下面我們看一看利用反射建立陣列的例子:
public class 用反射建立陣列 {
public static void main(String[] args) {
Class<?> cls = null;
try {
cls = Class.forName("java.lang.String");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Object array = Array.newInstance(cls,25);
//往數組裡新增內容
Array.set(array,0,"hello");
Array.set(array,1,"Java");
Array.set(array,2,"fuck");
Array.set(array,3,"Scala");
Array.set(array,4,"Clojure");
//獲取某一項的內容
System.out.println(Array.get(array,3));
//Scala
}
}
其中的Array類為java.lang.reflect.Array類。我們通過Array.newInstance()建立陣列物件,它的原型是:
public static Object newInstance(Class<?> componentType, int length)
throws NegativeArraySizeException {
return newArray(componentType, length);
}