JAVA反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意方法和屬性;這種動態獲取信息以及動態調用對象方法的功能稱為java語言的反射機制。 一:Class類 在面向對象的世界里,萬物皆對象。類也是對象,類是java.lang.Class類 ...
JAVA反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意方法和屬性;這種動態獲取信息以及動態調用對象方法的功能稱為java語言的反射機制。
一:Class類
在面向對象的世界里,萬物皆對象。類也是對象,類是java.lang.Class類的實例對象。
Class類的實例表示正在運行的 Java 應用程式中的類和介面。枚舉是一種類,註釋是一種介面。每個數組屬於被映射為 Class 對象的一個類,所有具有相同元素類型和維數的數組都共用該 Class 對象。
基本的 Java 類型(
boolean
、byte
、char
、short
、int
、long
、float
和double
)和關鍵字void
也表示為 Class 對象。Class 沒有公共構造方法。Class 對象是在載入類時由 Java 虛擬機以及通過調用類載入器中的
defineClass
方法自動構造的。
上面來自於JDK的羅里吧嗦,下麵我來說下自己的體會:
類不是抽象的,類是具體的!
類是.class位元組碼文件,要想獲取一個Class實例對象,首先需要獲取.class位元組碼文件!
然後調用Class對象的一些方法,進行動態獲取信息以及動態調用對象方法!
二:類類型
新建一個Foo類。Foo這個類也是實例對象,是Class的實例對象。
不知道你是否在意過類的聲明與方法的聲明:
public class Foo{
Foo(){
//構造方法
}
}
public Foo method(){
//...
}
我們知道public後跟返回類型,也就可以知道class也是一個類型。
如何表示Class的實例對象?
public static void main(String[] args) {
//Foo的實例對象,new 就出來了
Foo foo1 = new Foo();
//如何表示?
//第一種:告訴我們任何一個類都有一個隱含的靜態成員變數class
Class c1 = Foo.class;
//第二種:已經知道該類的對象通過getClass方法
Class c2 = foo1.getClass();
System.out.println(c1 == c2);
//第三種:動態載入
Class c3 = null;
try {
c3 = Class.forName("cn.zyzpp.reflect.Foo");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
System.out.println(c2 == c3);
}
上述列印結果全是true
儘管 c1或c2 都代表了Foo的類類型,一個類只能是Class類的一個實例變數。
我們完全可以通過類的類類型(Class類型)創建類的實例對象。
//此時c1 c2 c3為Class的實例對象
try {
// Foo foo = (Foo)c1.newInstance();
Foo foo = (Foo)c3.newInstance();
foo.print();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
靜態載入
new 創建對象是靜態載入類,在編譯時刻就需要載入所有的可能使用到的類 。
動態載入
使用 Class.forName("類的全稱") 載入類稱作為動態載入 。
編譯時刻載入類是靜態載入類,運行時刻載入類是動態載入類。
舉個例子
定義Office類
public class Office {
public void print() {
System.out.println("office");
}
}
定義Loading類
public class Loading {
public static void main(String[] args) {
try {
//在運行時再動態載入類
//arg[0] 為java執行命令時傳的參數
Class<?> a = Class.forName(args[0]);
Office office = (Office) a.newInstance();
office.print();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
執行過程
D:\>javac -encoding utf-8 Loading.java Office.java
D:\>java Loading Office
office
通過Class a=Class.forName(arg[0])
動態載入獲取類,因編譯時不知道使用哪個類,因此編譯沒有載入任何類,直接通過編譯,運行時,根據 java Loading Office
(office是一個類類型/類,下標arg[0]),去確定a是哪個類。這就是動態載入。如果Office類不存在,此時運行會報錯。這就是為何有時候會出現編譯通過,運行報錯的原因。
動態載入一個好處,就是可以隨時增加需要編譯的類。例如把Office改造為抽象類或介面,定義不同的子類,動態選擇載入。
三:類的反射
通過上面的三種方法獲取到類的類類型,就可以獲取到該類的成員方法,成員變數,方法參數註釋等信息。
方法對象是Method類,一個成員方法就是一個Method對象。
方法 | 解釋 |
---|---|
getMethods() |
返回該類繼承以及自身聲明的所有public的方法數組 |
getDeclaredMethods() |
返回該類自身聲明的所有public的方法數組,不包括繼承而來 |
成員變數也是對象,是java.lang.reflect.Field對象,Field類封裝了關於成員變數的操作。
方法 | 解釋 |
---|---|
getFields() |
獲取所有的public的成員變數信息,包括繼承的。 |
getDeclaredFields() |
獲取該類自己聲明的成員變數信息,public,private等 |
獲取Java語言修飾符(public、private、final、static)的int返回值,再調用Modifier.toString()
獲取修飾符的字元串形式,註意該方法會返回所有修飾符。
方法 | 解釋 |
---|---|
getModifiers() |
以整數形式返回由此對象表示的欄位的 Java 語言修飾符。 |
獲取註釋
方法 | 解釋 |
---|---|
getAnnotations() |
返回此元素上存在的所有註釋。 |
getDeclaredAnnotations() |
返回直接存在於此元素上的所有註釋。 |
構造函數也是對象,是java.lang.reflect.Constructor的對象。
方法 | 解釋 |
---|---|
getConstructors() |
返回所有public構造方法 |
getDeclaredConstructors() |
返回類的所有構造方法,不止public |
完整示例
private void printClassMessage(Object obj){
//要獲取類的信息,首先獲取類的類類型
Class clazz = obj.getClass();
//獲取類的名稱
System.out.println(Modifier.toString(clazz.getModifiers())+" "+ clazz.getClass().getName()+" "+clazz.getName()+"{");
System.out.println("----構造方法----");
//構造方法
Constructor[] constructors = clazz.getDeclaredConstructors();
for (Constructor constructor: constructors){
//構造方法修飾符與名字
System.out.print(Modifier.toString(constructor.getModifiers())+" "+constructor.getName()+"(");
//構造函數的所有參數類型
Class[] parameterTypes = constructor.getParameterTypes();
for (Class c: parameterTypes){
System.out.print(c.getName()+", ");
}
System.out.println("){}");
}
System.out.println("----成員變數----");
//成員變數
Field[] fields = clazz.getDeclaredFields();
for (Field field: fields){
System.out.println(" "+Modifier.toString(field.getModifiers())+" "+field.getType().getName() + " " + field.getName()+";");
}
System.out.println("----成員方法----");
//Method類,方法對象,一個成員方法就是一個Method對象
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods){
//獲取方法返回類型
Class returnType = method.getReturnType();
//獲取方法上的所有註釋
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation: annotations){
//列印註釋類型
System.out.println(" @"+annotation.annotationType().getName()+" ");
}
//列印方法聲明
System.out.print(" "+Modifier.toString(returnType.getModifiers())+" "+returnType.getName()+" "+method.getName()+"(");
//獲取方法的所有參數類型
Class<?>[] parameterTypes = method.getParameterTypes();
//獲取方法的所有參數
Parameter[] parameters = method.getParameters();
for (Parameter parameter: parameters){
//參數的類型,形參(全是arg123..)
System.out.print(parameter.getType().getName()+" "+parameter.getName()+", ");
}
System.out.println(")");
}
System.out.println("}");
}
以String對象為例,列印結果:
public final java.lang.Class java.lang.String{
----構造方法----
public java.lang.String([B, int, int, ){}
java.lang.String([C, boolean, ){}
----成員變數----
private final [C value;
private int hash;
----成員方法----
@java.lang.Deprecated
public abstract final void getBytes(int arg0, int arg1, [B arg2, int arg3, )
......
}
四:方法的反射
定義了一個類Foo用於測試
public class Foo{
public void print(String name,int num) {
System.out.println("I am "+name+" age "+num);
}
}
目標:通過反射獲取該方法,傳入參數,執行該方法!
1.獲取類的方法就是獲取類的信息,獲取類的信息首先要獲取類的類類型
Class clazz = Foo.class;
2.通過名稱+參數類型獲取方法對象
Method method = clazz.getMethod("print", new Class[]{String.class,int.class});
3.方法的反射操作是通過方法對象來調用該方法,達到和new Foo().print()一樣的效果
方法若無返回值則返回null
Object o = method.invoke(new Foo(),new Object[]{"name",20});
五:通過反射認識泛型
public static void main(String[] args) {
ArrayList<String> stringArrayList = new ArrayList<>();
stringArrayList.add("hello");
ArrayList arrayList = new ArrayList();
Class c1 = stringArrayList.getClass();
Class c2 = arrayList.getClass();
System.out.println(c1 == c2);
}
列印結果為true
c1==c2的結果返回說明編譯之後集合的泛型是去泛型化的。換句話說,泛型不同,對類型沒有影響。
Java中集合的泛型其實只是為了防止錯誤輸入,只在編譯階段有效,繞過編譯就無效。
驗證
我們可以通過反射來操作,繞過編譯。
public static void main(String[] args) {
ArrayList<String> stringArrayList = new ArrayList<>();
stringArrayList.add("hello");
ArrayList arrayList = new ArrayList();
Class c1 = stringArrayList.getClass();
Class c2 = arrayList.getClass();
System.out.println(c1 == c2);
try {
Method method = c1.getMethod("add",Object.class);
method.invoke(stringArrayList,20);
System.out.println(stringArrayList.toString());
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
列印結果:
true
[hello, 20]
成功繞過了泛型<String>的約束。