Java反射使用總結

Java反射使用總結
最近公司招了幾名剛畢業的大學生,在給他們培訓的過程中,講到反射,他們有些人聽不懂,對反射的概念雲裏霧裏的,不知道反射有什麼用。

因此就有了本文的誕生。

反射是java提供的一個重要功能,可以在運行時檢查類、接口、方法和變量等信息,無需知道類的名字,方法名等。還可以在運行時實例化新對象,調用方法以及設置和獲取變量值。

反射非常強大和有用,很多java框架中都有反射的影子,例如spring、mybatis等等,

JDBC利用反射將數據庫的表字段映射到java對象的getter/setter方法。

Jackson, GSON, Boon等類庫也是利用反射將JSON文件的屬性映射到java對的象getter/setter方法。

可見,只要使用java,反射就無處不在。

Class對象

檢查一個類之前,必須獲取到java.lang.Class對象,java中的所有類型,包括long,int,數組等基本數據類型,都和Class對象有關係。

我們很多人去醫院參加體檢的時候,都做過B超檢查,醫生只需把一個探頭在我們身上滑動就可以將我們體內的肝、膽、腎等器官反射到B超設備上顯示。

Class類對象就相當於B超的探頭,將一個類的方法、變量、接口、類名、類修飾符等信息告訴運行的程序。

Java提供了兩種方式獲取Class對象,一種是使用.class,另外一種是使用Class.forName()。
.class方式適用於在編譯時已經知道具體的類。

Class alunbarClass = Alunbar.class;
Class.forName()方式適用於運行時動態獲取Class對象,只需將類名作爲forName方法的參數:

try{

Class alunbarClass1 = Class.forName("Alunbar");

}
catch(ClassNotFoundException e){

System.out.println("找不到Alunbar類");

}
這個方法會出現類找不到的情況,因此使用這個方法獲取Class對象時,必須捕獲ClassNotFoundException異常。

獲取類名

package cn.alunbar;

public class Alunbar {

public static void  main(String arts[]){
    Class alunbarClass = Alunbar.class;
    System.out.println(alunbarClass.getName());
    System.out.println(alunbarClass.getSimpleName());
}

}
上面代碼運行結果如下:

cn.alunbar.Alunbar
Alunbar
getName()方法獲取的類名包含包信息。getSimpleName()方法只是獲取類名,不包含包信息。

獲取類修飾符

public class Alunbar {

public static void  main(String arts[]){
    Class alunbarClass = Alunbar.class;
    System.out.println(alunbarClass.getModifiers());
    System.out.println(Modifier.isPublic(alunbarClass.getModifiers()));
    
    Class birdClass = Bird.class;
    System.out.println(birdClass.getModifiers());
    System.out.println(Modifier.isPublic(birdClass.getModifiers()));
    
}

private class Bird{
    
}

}
類修飾符有public、private等類型,getModifiers()可以獲取一個類的修飾符,但是返回的結果是int,結合Modifier提供的方法,就可以確認修飾符的類型。

Modifier.isAbstract(int modifiers)
Modifier.isFinal(int modifiers)
Modifier.isInterface(int modifiers)
Modifier.isNative(int modifiers)
Modifier.isPrivate(int modifiers)
Modifier.isProtected(int modifiers)
Modifier.isPublic(int modifiers)
Modifier.isStatic(int modifiers)
Modifier.isStrict(int modifiers)
Modifier.isSynchronized(int modifiers)
Modifier.isTransient(int modifiers)
Modifier.isVolatile(int modifiers)
獲取包信息

package cn.alunbar;

public class Alunbar {

public static void  main(String arts[]){
    
    Class birdClass = Bird.class;
    System.out.println(birdClass.getPackage());
    
}

private class Bird{

}

}
getPackage()方法獲取包信息。上面代碼運行的結果:

package cn.alunbar
獲取父類的Class對象

public class Alunbar {

public static void  main(String arts[]){
    
    Class birdClass = Bird.class;
    Class superclass = birdClass.getSuperclass();
    System.out.println(superclass.getSimpleName());
}

private class Bird extends Animal{

}

private class Animal{
    
}

}
上面代碼運行的結果:

Animal
getSuperclass()方法返回的父類的Class對象。

獲取接口信息
獲取接口信息的方法:

Class[] interfaces = birdClass.getInterfaces();
一個類可以實現多個接口,所以getInterfaces()方法返回的是Class[]數組。
注意:getInterfaces()只返回指定類實現的接口,不會返父類實現的接口。

獲取構造函數Constructor
獲取構造函數的方法:

Class birdClass = Bird.class;
Constructor[] constructors = birdClass.getConstructors();
一個類會有多個構造函數,getConstructors()返回的是Constructor[]數組,包含了所有聲明的用public修飾的構造函數。

如果你已經知道了某個構造的參數,可以通過下面的方法獲取到迴應的構造函數對象:

public class Alunbar {

public static void  main(String arts[]){
    
    Class birdClass = Bird.class;
    try{
        Constructor constructors = birdClass.getConstructor(new Class[]{String.class});
    }catch(NoSuchMethodException  e){
        
    }
}

private class Bird {
    public Bird(){
        
    }
    
    public Bird(String eat){
        
    }
}

}
上面獲取構造函數的方式有2點需要注意:
1、只能獲取到public修飾的構造函數。
2、需要捕獲NoSuchMethodException異常。

獲取構造函數的參數
獲取到構造函數的對象之後,可以通過getParameterTypes()獲取到構造函數的參數。

Constructor constructors = birdClass.getConstructor(new Class[]{String.class});

        Class[] parameterTypes = constructors.getParameterTypes();

初始化對象

通過反射獲取到構造器之後,通過newInstance()方法就可以生成類對象。

public class Alunbar {

public static void  main(String arts[]){
    
    Class birdClass = Bird.class;
    try{
        Constructor constructors = birdClass.getConstructor(new Class[]{String.class});
        Bird bird = (Bird)constructors.newInstance("eat tea");
        
    }catch(Exception  e){
        System.out.println("沒有對應的構造函數");
    }
}

 class Bird {
    public Bird(){
        
    }
    
    protected Bird(String eat){
        
    }
}

}
newinstance()方法接受可選數量的參數,必須爲所調用的構造函數提供準確的參數。如果構造函數要求String的參數,在調用newinstance()方法是,必須提供String類型的參數。

獲取Methods方法信息
下面代碼是通過反射可以獲取到該類的聲明的成員方法信息:

Method[] metchods = birdClass.getMethods();
Method[] metchods1 = birdClass.getDeclaredMethods();
Method eatMetchod = birdClass.getMethod("eat", new Class[]{int.class});
Method eatMetchod1 = birdClass.getDeclaredMethod("eat", new Class[]{int.class});
無參的getMethods()獲取到所有public修飾的方法,返回的是Method[]數組。
無參的getDeclaredMethods()方法到的是所有的成員方法,和修飾符無關。
對於有參的getMethods()方法,必須提供要獲取的方法名以及方法名的參數。如果要獲取的方法沒有參數,則用null替代:

Method eatMetchod = birdClass.getMethod("eat", null);
無參的getMethods()和getDeclaredMethods()都只能獲取到類聲明的成員方法,不能獲取到繼承父類的方法。

獲取成員方法參數

Class birdClass = Bird.class;
Class[] parameterTypes = eatMetchod1.getParameterTypes();
獲取成員方法返回類型

Class birdClass = Bird.class;
Class returnType = eatMetchod1.getReturnType();
invoke()方法
java反射提供invoke()方法,在運行時根據業務需要調用相應的方法,這種情況在運行時非常常見,只要通過反射獲取到方法名之後,就可以調用對應的方法:

Class birdClass = Bird.class;
Constructor constructors1 = birdClass.getConstructor();
Method eatMetchod = birdClass.getMethod("eat", new Class[]{int.class});
System.out.println(eatMetchod.invoke(constructors1.newInstance(), 2));
invoke方法有兩個參數,第一個參數是要調用方法的對象,上面的代碼中就是Bird的對象,第二個參數是調用方法要傳入的參數。如果有多個參數,則用數組。

如果調用的是static方法,invoke()方法第一個參數就用null代替:

public class Alunbar {

public static void  main(String arts[]){
    try{
        Class birdClass = Bird.class;
        Constructor constructors1 = birdClass.getConstructor();
        Method eatMetchod = birdClass.getMethod("eat", new Class[]{int.class});
        System.out.println(eatMetchod.invoke(null, 2));
    }catch(Exception  e){
        e.printStackTrace();
        System.out.println("沒有對應的構造函數");
    }
}

}

class Bird{

public static int eat(int eat){
    return eat;
}
public Bird(){
    
}

public Bird(String eat){
    
}

private void talk(){}

}

class Animal{

 public void run(){
     
 }

}
使用反射可以在運行時檢查和調用類聲明的成員方法,可以用來檢測某個類是否有getter和setter方法。getter和setter是java bean必須有的方法。
getter和setter方法有下面的一些規律:
getter方法以get爲前綴,無參,有返回值
setter方法以set爲前綴,有一個參數,返回值可有可無,
下面的代碼提供了檢測一個類是否有getter和setter方法:

public static void printGettersSetters(Class aClass){
Method[] methods = aClass.getMethods();

for(Method method : methods){

if(isGetter(method)) System.out.println("getter: " + method);
if(isSetter(method)) System.out.println("setter: " + method);

}
}

public static boolean isGetter(Method method){
if(!method.getName().startsWith("get")) return false;
if(method.getParameterTypes().length != 0) return false;
if(void.class.equals(method.getReturnType()) return false;
return true;
}

public static boolean isSetter(Method method){
if(!method.getName().startsWith("set")) return false;
if(method.getParameterTypes().length != 1) return false;
return true;
}
獲取成員變量
通過反射可以在運行時獲取到類的所有成員變量,還可以給成員變量賦值和獲取成員變量的值。

Class birdClass = Bird.class;
Field[] fields1 = birdClass.getFields();
Field[] fields2 = birdClass.getDeclaredFields();
Field fields3 = birdClass.getField("age");
Field fields4 = birdClass.getDeclaredField("age");
getFields()方法獲取所有public修飾的成員變量,getField()方法需要傳入變量名,並且變量必須是public修飾符修飾。

getDeclaredFields方法獲取所有生命的成員變量,不管是public還是private。

獲取成員變量類型

Field fields4 = birdClass.getDeclaredField("age");
Object fieldType = fields4.getType();
成員變量賦值和取值
一旦獲取到成員變量的Field引用,就可以獲取通過get()方法獲取變量值,通過set()方法給變量賦值:

Class birdClass = Bird.class;
Field fields3 = birdClass.getField("age");
Bird bird = new Bird();
Object value = fields3.get(bird);
fields3.set(bird, value);
訪問私有變量
有很多文章討論禁止通過反射訪問一個對象的私有變量,但是到目前爲止所有的jdk還是允許通過反射訪問私有變量。

使用 Class.getDeclaredField(String name)或者Class.getDeclaredFields()才能獲取到私有變量。

package field;

import java.lang.reflect.Field;

public class PrivateField {

protected  String name;

public PrivateField(String name){
    this.name = name;
}

}

public class PrivateFieldTest {

public static void main(String args[])throws Exception{
    Class privateFieldClass = PrivateField.class;
    Field privateName = privateFieldClass.getDeclaredField("name");
    privateName.setAccessible(false);
    PrivateField privateField = new PrivateField("Alunbar");
    String privateFieldValue = (String) privateName.get(privateField);
    System.out.println("私有變量值:" + privateFieldValue);
}

}
上面的代碼有點需要注意:必須調用setAccessible(true)方法,這是針對私有變量而言,public和protected等都不需要。這個方法是允許通過反射訪問類的私有變量。

訪問私有方法
和私有變量一樣,私有方法也是不允許其他的類隨意調用的,但是通過反射可以饒過這一限制。
使用Class.getDeclaredMethod(String name, Class[] parameterTypes)或者Class.getDeclaredMethods()方法獲取到私有方法。

public class PrivateMethod {

private String accesPrivateMethod(){
    return "成功訪問私有方法";
}

}

public class PrivateMethodTest {

public static void main(String args[])throws Exception{
    Class privateMethodClass = PrivateMethod.class;
    
    Method privateStringMethod = privateMethodClass.getDeclaredMethod("accesPrivateMethod", null);
    privateStringMethod.setAccessible(true);
    String returnValue = (String)privateStringMethod.invoke(new PrivateMethod(), null);

    System.out.println("returnValue = " + returnValue);
}

}
和訪問私有變量一樣,也要調用setAccessible(true)方法,允許通過反射訪問類的私有方法。

訪問類註解信息
通過反射可以在運行時獲取到類、方法、變量和參數的註解信息。

訪問類的所有註解信息:

Class aClass = TheClass.class;
Annotation[] annotations = aClass.getAnnotations();

for(Annotation annotation : annotations){

if(annotation instanceof MyAnnotation){
    MyAnnotation myAnnotation = (MyAnnotation) annotation;
    System.out.println("name: " + myAnnotation.name());
    System.out.println("value: " + myAnnotation.value());
}

}
訪問類特定的註解信息:

Class aClass = TheClass.class;
Annotation annotation = aClass.getAnnotation(MyAnnotation.class);

if(annotation instanceof MyAnnotation){

MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());

}
訪問方法註解信息:

Method method = ... //obtain method object
Annotation[] annotations = method.getDeclaredAnnotations();

for(Annotation annotation : annotations){

if(annotation instanceof MyAnnotation){
    MyAnnotation myAnnotation = (MyAnnotation) annotation;
    System.out.println("name: " + myAnnotation.name());
    System.out.println("value: " + myAnnotation.value());
}

}
訪問特定方法註解信息:

Method method = ... // obtain method object
Annotation annotation = method.getAnnotation(MyAnnotation.class);

if(annotation instanceof MyAnnotation){

MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());

}
訪問參數註解信息:

Method method = ... //obtain method object
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Class[] parameterTypes = method.getParameterTypes();

int i=0;
for(Annotation[] annotations : parameterAnnotations){
Class parameterType = parameterTypes[i++];

for(Annotation annotation : annotations){

if(annotation instanceof MyAnnotation){
    MyAnnotation myAnnotation = (MyAnnotation) annotation;
    System.out.println("param: " + parameterType.getName());
    System.out.println("name : " + myAnnotation.name());
    System.out.println("value: " + myAnnotation.value());
}

}
}
Method.getParameterAnnotations()方法返回的是一個二維的Annotation數組,其中包含每個方法參數的註解數組。

訪問類所有變量註解信息:

Field field = ... //obtain field object
Annotation[] annotations = field.getDeclaredAnnotations();

for(Annotation annotation : annotations){

if(annotation instanceof MyAnnotation){
    MyAnnotation myAnnotation = (MyAnnotation) annotation;
    System.out.println("name: " + myAnnotation.name());
    System.out.println("value: " + myAnnotation.value());
}

}
訪問類某個特定變量的註解信息:

Field field = ... // obtain method object
Annotation annotation = field.getAnnotation(MyAnnotation.class);

if(annotation instanceof MyAnnotation){

MyAnnotation myAnnotation = (MyAnnotation) annotation;
System.out.println("name: " + myAnnotation.name());
System.out.println("value: " + myAnnotation.value());

}
獲取泛型信息
很多人認爲java類在編譯的時候會把泛型信息給擦除掉,所以在運行時是無法獲取到泛型信息的。其實在某些情況下,還是可以通過反射在運行時獲取到泛型信息的。

獲取到java.lang.reflect.Method對象,就有可能獲取到某個方法的泛型返回信息。

泛型方法返回類型
下面的類中定義了一個返回值中有泛型的方法:

public class MyClass {

protected List stringList = ...;

public List getStringList(){

return this.stringList;

}
}
下面的代碼使用反射檢測getStringList()方法返回的是List而不是List

Method method = MyClass.class.getMethod("getStringList", null);

Type returnType = method.getGenericReturnType();

if(returnType instanceof ParameterizedType){

ParameterizedType type = (ParameterizedType) returnType;
Type[] typeArguments = type.getActualTypeArguments();
for(Type typeArgument : typeArguments){
    Class typeArgClass = (Class) typeArgument;
    System.out.println("typeArgClass = " + typeArgClass);
}

}
上面這段代碼會打印:typeArgClass = java.lang.String

泛型方法參數類型
下面的類定義了一個有泛型參數的方法setStringList():

public class MyClass {
protected List stringList = ...;

public void setStringList(List list){

this.stringList = list;

}
}
Method類提供了getGenericParameterTypes()方法獲取方法的泛型參數。

method = Myclass.class.getMethod("setStringList", List.class);

Type[] genericParameterTypes = method.getGenericParameterTypes();

for(Type genericParameterType : genericParameterTypes){

if(genericParameterType instanceof ParameterizedType){
    ParameterizedType aType = (ParameterizedType) genericParameterType;
    Type[] parameterArgTypes = aType.getActualTypeArguments();
    for(Type parameterArgType : parameterArgTypes){
        Class parameterArgClass = (Class) parameterArgType;
        System.out.println("parameterArgClass = " + parameterArgClass);
    }
}

}
上面的代碼會打印出parameterArgType = java.lang.String

泛型變量類型
通過反射也可以獲取到類的成員泛型變量信息——靜態變量或實例變量。下面的類定義了一個泛型變量:

public class MyClass {
public List stringList = ...;
}
通過反射的Filed對象獲取到泛型變量的類型信息:

Field field = MyClass.class.getField("stringList");

Type genericFieldType = field.getGenericType();

if(genericFieldType instanceof ParameterizedType){

ParameterizedType aType = (ParameterizedType) genericFieldType;
Type[] fieldArgTypes = aType.getActualTypeArguments();
for(Type fieldArgType : fieldArgTypes){
    Class fieldArgClass = (Class) fieldArgType;
    System.out.println("fieldArgClass = " + fieldArgClass);
}

}
Field對象提供了getGenericType()方法獲取到泛型變量。
上面的代碼會打印出:fieldArgClass = java.lang.String

動態代理

使用反射可以在運行時創建接口的動態實現,java.lang.reflect.Proxy類提供了創建動態實現的功能。我們把運行時創建接口的動態實現稱爲動態代理。

動態代理可以用於許多不同的目的,例如數據庫連接和事務管理、用於單元測試的動態模擬對象以及其他類似aop的方法攔截等。

創建代理

調用java.lang.reflect.Proxy類的newProxyInstance()方法就可以常見動態代理,newProxyInstance()方法有三個參數:
1、用於“加載”動態代理類的類加載器。
2、要實現的接口數組。
3、將代理上的所有方法調用轉發到InvocationHandler的對象。
代碼如下:

InvocationHandler handler = new MyInvocationHandler();
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(

                        MyInterface.class.getClassLoader(),
                        new Class[] { MyInterface.class },
                        handler);

運行上面代碼後,proxy變量包含了MyInterface接口的動態實現。

對代理的所有調用都將由到實現了InvocationHandler接口的handler 對象來處理。

InvocationHandler
如上面說的一樣,必須將InvocationHandler的實現傳遞給Proxy.newProxyInstance()方法。對動態代理的所有方法調用都轉發到實現接口的InvocationHandler對象。
InvocationHandler代碼:

public interface InvocationHandler{
Object invoke(Object proxy, Method method, Object[] args)

     throws Throwable;

}
實現InvocationHandler接口的類:

public class MyInvocationHandler implements InvocationHandler{

public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {

//do something "dynamic"

}
}
下面詳細介紹傳遞給invoke方法的三個參數。

Object proxy參數,實現接口的動態代理對象。通常不需要這個對象。

Method method參數,表示在動態代理實現的接口上調用的方法。通過Method對象,可以獲取到方法名,參數類型,返回類型等信息。

Object[] args參數,包含調用接口中實現的方法時傳遞給代理的參數值。注意:如果接口中的參數是int、long等基本數據時,這裏的args必須使用Integer, Long等包裝類型。

上面代碼中會生成一個MyInterface接口的對象proxy,通過proxy對象調用的方法都會由MyInvocationHandler類的invoke方法處理。

動態代理使用場景:
1、數據庫連接和事務管理。例如Spring框架有一個事務代理,可以啓動和提交/回滾事務
2、用於單元測試的動態模擬對象
3、類似AOP的方法攔截。

本文重點介紹瞭如何通過反射獲取到某個類的方法、成員變量、構造函數等信息,同時也介紹動態代理的用法,這些都是反射的基礎功能,反射的其他功能裏就不一一介紹了。

用心寫好每篇技術文章
作者:alunbar
原文地址https://www.cnblogs.com/airnew/p/11437090.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章