Java反射示例教程

Java Reflection提供了檢查和修改應用程序的運行時行爲的功能。Java中的反射是核心java的一個先進主題。使用java反射,我們可以在運行時檢查類,接口枚舉,獲取它們的結構,方法和字段信息,即使在編譯時無法訪問類。我們還可以使用反射來實例化對象,調用它的方法,更改字段值。

 

  1. Java中的反射

Java中的反思是一個非常強大的概念,它在普通編程中幾乎沒有用,但它是大多數Java,J2EE框架的支柱。一些使用java反射的框架是:

  1. JUnit - 使用反射來解析@Test註釋以獲取測試方法,然後調用它。
  2. Spring - 依賴注入,在Spring Dependency Injection中閱讀更多內容
  3. Tomcat Web容器通過解析其web.xml文件並請求URI來將請求轉發到正確的模塊。
  4. Eclipse自動完成方法名稱
  5. 支柱
  6. 過冬

列表是無窮無盡的,它們都使用java反射,因爲所有這些框架都不知道和訪問用戶定義的類,接口,它們的方法等。

我們不應該在正常編程中使用反射,因爲以下缺點我們已經可以訪問類和接口。

  • 性能不佳 - 由於java反射動態解析類型,因此涉及掃描類路徑以查找要加載的類的處理,從而導致性能降低。
  • 安全限制 - Reflection需要運行時權限,這些權限可能不適用於在安全管理器下運行的系統。由於安全管理器,這可能導致應用程序在運行時失敗。
  • 安全問題 - 使用反射,我們可以訪問我們不應該訪問的部分代碼,例如,我們可以訪問類的私有字段並更改它的值。這可能是嚴重的安全威脅,並導致您的應用程序出現異常行爲。
  • 高維護 - 反射代碼很難理解和調試,在編譯時也無法找到代碼的任何問題,因爲這些類可能不可用,使其不太靈活且難以維護。

2.類的Java反射

在java中,每個對象都是基本類型或引用。所有類,枚舉,數組都是引用類型並繼承自java.lang.Object。原始類型是 - 布爾值,字節,短,int,long,char,float和double。

java.lang.Class是所有反射操作的入口點。對於每種類型的對象,JVM都實例化一個不可變實例,java.lang.Class該實例提供了檢查對象的運行時屬性並創建新對象,調用其方法以及獲取/設置對象字段的方法。

在本節中,我們將研究Class的重要方法,爲方便起見,我正在創建一些具有繼承層次結構的類和接口。


package com.journaldev.reflection;

public interface BaseInterface {
    
    public int interfaceInt=0;
    
    void method1();
    
    int method2(String str);
}
 


package com.journaldev.reflection;

public class BaseClass {

    public int baseInt;
    
    private static void method3(){
        System.out.println("Method3");
    }
    
    public int method4(){
        System.out.println("Method4");
        return 0;
    }
    
    public static int method5(){
        System.out.println("Method5");
        return 0;
    }
    
    void method6(){
        System.out.println("Method6");
    }
    
    // inner public class
    public class BaseClassInnerClass{}
        
    //member public enum
    public enum BaseClassMemberEnum{}
}
 

 

 


package com.journaldev.reflection;

@Deprecated
public class ConcreteClass extends BaseClass implements BaseInterface {

    public int publicInt;
    private String privateString="private string";
    protected boolean protectedBoolean;
    Object defaultObject;
    
    public ConcreteClass(int i){
        this.publicInt=i;
    }

    @Override
    public void method1() {
        System.out.println("Method1 impl.");
    }

    @Override
    public int method2(String str) {
        System.out.println("Method2 impl.");
        return 0;
    }
    
    @Override
    public int method4(){
        System.out.println("Method4 overriden.");
        return 0;
    }
    
    public int method5(int i){
        System.out.println("Method4 overriden.");
        return 0;
    }
    
    // inner classes
    public class ConcreteClassPublicClass{}
    private class ConcreteClassPrivateClass{}
    protected class ConcreteClassProtectedClass{}
    class ConcreteClassDefaultClass{}
    
    //member enum
    enum ConcreteClassDefaultEnum{}
    public enum ConcreteClassPublicEnum{}
    
    //member interface
    public interface ConcreteClassPublicInterface{}

}
 

讓我們看一下類的一些重要的反思方法。

獲取類對象

我們可以使用三種方法獲取對象的類 - 通過靜態變量class,使用getClass()對象和方法java.lang.Class.forName(String fullyClassifiedClassName)。對於原始類型和數組,我們可以使用靜態變量class。包裝類提供另一個靜態變量TYPE來獲取類。

 


// Get Class using reflection
Class<?> concreteClass = ConcreteClass.class;
concreteClass = new ConcreteClass(5).getClass();
try {
    // below method is used most of the times in frameworks like JUnit
    //Spring dependency injection, Tomcat web container
    //Eclipse auto completion of method names, hibernate, Struts2 etc.
    //because ConcreteClass is not available at compile time
    concreteClass = Class.forName("com.journaldev.reflection.ConcreteClass");
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}
System.out.println(concreteClass.getCanonicalName()); // prints com.journaldev.reflection.ConcreteClass

//for primitive types, wrapper classes and arrays
Class<?> booleanClass = boolean.class;
System.out.println(booleanClass.getCanonicalName()); // prints boolean

Class<?> cDouble = Double.TYPE;
System.out.println(cDouble.getCanonicalName()); // prints double

Class<?> cDoubleArray = Class.forName("[D");
System.out.println(cDoubleArray.getCanonicalName()); //prints double[]

Class<?> twoDStringArray = String[][].class;
System.out.println(twoDStringArray.getCanonicalName()); // prints java.lang.String[][]
 

 getCanonicalName()返回基礎類的規範名稱。請注意,java.lang.Class使用Generics,它幫助框架確保檢索的Class是框架Base Class的子類。查看Java Generics Tutorial以瞭解泛型及其通配符。

獲得超類

Class對象上的getSuperclass()方法返回類的超類。如果此Class表示Object類,接口,基本類型或void,則返回null。如果此對象表示數組類,則返回表示Object類的Class對象。

 
Class<?> superClass = Class.forName("com.journaldev.reflection.ConcreteClass").getSuperclass();
System.out.println(superClass); // prints "class com.journaldev.reflection.BaseClass"
System.out.println(Object.class.getSuperclass()); // prints "null"
System.out.println(String[][].class.getSuperclass());// prints "class java.lang.Object"
 

 

獲取公共成員類

getClasses()object的類表示形式的方法返回一個數組,該數組包含Class對象,這些對象表示作爲此Class對象所表示的類的成員的所有公共類,接口和枚舉。這包括從超類繼承的公共類和接口成員,以及由類聲明的公共類和接口成員。如果此Class對象沒有公共成員類或接口,或者此Class對象表示基本類型,數組類或void,則此方法返回長度爲0的數組。

 
Class<?>[] classes = concreteClass.getClasses();
//[class com.journaldev.reflection.ConcreteClass$ConcreteClassPublicClass, 
//class com.journaldev.reflection.ConcreteClass$ConcreteClassPublicEnum, 
//interface com.journaldev.reflection.ConcreteClass$ConcreteClassPublicInterface,
//class com.journaldev.reflection.BaseClass$BaseClassInnerClass, 
//class com.journaldev.reflection.BaseClass$BaseClassMemberEnum]
System.out.println(Arrays.toString(classes));

獲取聲明的類

getDeclaredClasses()method返回Class對象的數組,這些對象反映聲明爲此Class對象所表示的類的成員的所有類和接口。返回的數組不包括在繼承的類和接口中聲明的類。

 
//getting all of the classes, interfaces, and enums that are explicitly declared in ConcreteClass
Class<?>[] explicitClasses = Class.forName("com.journaldev.reflection.ConcreteClass").getDeclaredClasses();
//prints [class com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultClass, 
//class com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultEnum, 
//class com.journaldev.reflection.ConcreteClass$ConcreteClassPrivateClass, 
//class com.journaldev.reflection.ConcreteClass$ConcreteClassProtectedClass, 
//class com.journaldev.reflection.ConcreteClass$ConcreteClassPublicClass, 
//class com.journaldev.reflection.ConcreteClass$ConcreteClassPublicEnum, 
//interface com.journaldev.reflection.ConcreteClass$ConcreteClassPublicInterface]
System.out.println(Arrays.toString(explicitClasses));

獲取聲明類

getDeclaringClass() method返回表示聲明它的類的Class對象。


Class<?> innerClass = Class.forName("com.journaldev.reflection.ConcreteClass$ConcreteClassDefaultClass");
//prints com.journaldev.reflection.ConcreteClass
System.out.println(innerClass.getDeclaringClass().getCanonicalName());
System.out.println(innerClass.getEnclosingClass().getCanonicalName());

getPackage()method返回此類的包。此類的類加載器用於查找包。我們可以調用getName()Package的方法來獲取包的名稱。 


//prints "com.journaldev.reflection"
System.out.println(Class.forName("com.journaldev.reflection.BaseInterface").getPackage().getName());

獲得類修飾符

getModifiers()方法返回類修飾符的int表示,我們可以使用java.lang.reflect.Modifier.toString()方法以源代碼中使用的字符串格式獲取它。

 

 
System.out.println(Modifier.toString(concreteClass.getModifiers())); //prints "public"
//prints "public abstract interface"
System.out.println(Modifier.toString(Class.forName("com.journaldev.reflection.BaseInterface").getModifiers())); 

獲取類型參數

getTypeParameters()如果存在與該類關聯的任何Type參數,則返回TypeVariable數組。類型參數的返回順序與聲明的順序相同。

 

 
//Get Type parameters (generics)
TypeVariable<?>[] typeParameters = Class.forName("java.util.HashMap").getTypeParameters();
for(TypeVariable<?> t : typeParameters)
System.out.print(t.getName()+",");

 

獲得已實現的接口

getGenericInterfaces()method返回由類實現的接口數組和泛型類型信息。我們還可以使用getInterfaces()獲取所有已實現接口的類表示。

 

 
Type[] interfaces = Class.forName("java.util.HashMap").getGenericInterfaces();
//prints "[java.util.Map<K, V>, interface java.lang.Cloneable, interface java.io.Serializable]"
System.out.println(Arrays.toString(interfaces));
//prints "[interface java.util.Map, interface java.lang.Cloneable, interface java.io.Serializable]"
System.out.println(Arrays.toString(Class.forName("java.util.HashMap").getInterfaces()));        

獲取所有公共構造函數

getConstructors() 方法返回對象的類引用的公共構造函數列表。

 

 
//Get All public constructors
Constructor<?>[] publicConstructors = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructors();
//prints public constructors of ConcreteClass
System.out.println(Arrays.toString(publicConstructors));

獲取所有註釋

getAnnotations()方法返回元素的所有註釋,我們也可以將它與類,字段和方法一起使用。請注意,只有帶反射的註釋可以使用RUNTIME的保留策略,請查看Java Annotations Tutorial
我們將在後面的章節中詳細介紹這一點。

 

 
java.lang.annotation.Annotation[] annotations = Class.forName("com.journaldev.reflection.ConcreteClass").getAnnotations();
//prints [@java.lang.Deprecated()]
System.out.println(Arrays.toString(annotations));

 

 

 3.字段的Java反射

Reflection API提供了幾種方法來分析類字段並在運行時修改它們的值,在本節中我們將研究一些常用的方法反射函數。

 

獲得公共領域

在上一節中,我們瞭解瞭如何獲取類的所有公共字段的列表。Reflection API還提供了通過getField()方法獲取類的特定公共字段的方法。此方法在指定的類引用中查找字段,然後在超級接口中查找,然後在超類中查找。

 
Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("interfaceInt");
 

上面的調用將返回由ConcreteClass實現的BaseInterface字段。如果沒有找到任何字段,則拋出NoSuchFieldException。

字段聲明類

我們可以使用getDeclaringClass()field對象來獲取聲明該字段的類。

 

 
try {
    Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("interfaceInt");
    Class<?> fieldClass = field.getDeclaringClass();
    System.out.println(fieldClass.getCanonicalName()); //prints com.journaldev.reflection.BaseInterface
} catch (NoSuchFieldException | SecurityException e) {
    e.printStackTrace();
}

獲取字段類型

getType()方法返回聲明的字段類型的Class對象,如果field是基本類型,則返回包裝類對象。

 

 
Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("publicInt");
Class<?> fieldType = field.getType();
System.out.println(fieldType.getCanonicalName()); //prints int            

 

獲取/設置公共字段值

我們可以使用反射來獲取和設置對象中字段的值。

 

 
Field field = Class.forName("com.journaldev.reflection.ConcreteClass").getField("publicInt");
ConcreteClass obj = new ConcreteClass(5);
System.out.println(field.get(obj)); //prints 5
field.setInt(obj, 10); //setting field value to 10 in object
System.out.println(field.get(obj)); //prints 10

get()方法返回Object,因此如果field是基本類型,則返回相應的Wrapper類。如果該字段是靜態的,我們可以在get()方法中將Object作爲null傳遞。

有幾個set *()方法可以將Object設置爲字段,或者爲字段設置不同類型的基元類型。我們可以獲取字段類型,然後調用正確的函數來正確設置字段值。如果該字段是final,則set()方法拋出java.lang.IllegalAccessException。

獲取/設置私有字段值

我們知道私有字段和方法不能在類外部訪問,但是使用反射我們可以通過關閉字段修飾符的java訪問檢查來獲取/設置私有字段值。

 

 
Field privateField = Class.forName("com.journaldev.reflection.ConcreteClass").getDeclaredField("privateString");
//turning off access check with below method call
privateField.setAccessible(true);
ConcreteClass objTest = new ConcreteClass(1);
System.out.println(privateField.get(objTest)); // prints "private string"
privateField.set(objTest, "private string updated");
System.out.println(privateField.get(objTest)); //prints "private string updated"

4.方法的Java反射

使用反射我們可以獲得有關方法的信息,我們也可以調用它。在本節中,我們將學習獲取方法,調用方法和訪問私有方法的不同方法。

獲取公共方法

我們可以使用getMethod()來獲取類的公共方法,我們需要傳遞方法的方法名和參數類型。如果在類中找不到該方法,則反射API將在超類中查找該方法。

在下面的例子中,我使用反射獲取HashMap的put()方法。該示例還顯示瞭如何獲取方法的參數類型,方法修飾符和返回類型。

 
Method method = Class.forName("java.util.HashMap").getMethod("put", Object.class, Object.class);
//get method parameter types, prints "[class java.lang.Object, class java.lang.Object]"
System.out.println(Arrays.toString(method.getParameterTypes()));
//get method return type, return "class java.lang.Object", class reference for void
System.out.println(method.getReturnType());
//get method modifiers
System.out.println(Modifier.toString(method.getModifiers())); //prints "public"

調用公共方法

我們可以使用Method對象的invoke()方法來調用方法,在下面的示例代碼中我使用反射調用HashMap上的put方法。

 

 
Method method = Class.forName("java.util.HashMap").getMethod("put", Object.class, Object.class);
Map<String, String> hm = new HashMap<>();
method.invoke(hm, "key", "value");
System.out.println(hm); // prints {key=value}

如果方法是靜態的,我們可以傳遞NULL作爲對象參數。

調用私有方法

我們可以使用getDeclaredMethod()來獲取私有方法,然後關閉訪問檢查以調用它,下面的示例顯示了我們如何調用BaseClass的method3()是靜態的並且沒有參數。

 
//invoking private method
Method method = Class.forName("com.journaldev.reflection.BaseClass").getDeclaredMethod("method3", null);
method.setAccessible(true);
method.invoke(null, null); //prints "Method3"

5.構造函數的Java反射

Reflection API提供了獲取類的構造函數進行分析的方法,我們可以通過調用構造函數來創建類的新實例。我們已經學會了如何獲得所有公共構造函數。

獲取公共構造函數

我們可以在對象的類表示上使用getConstructor()方法來獲取特定的公共構造函數。下面的示例演示如何獲取上面定義的ConcreteClass的構造函數和HashMap的無參數構造函數。它還顯示瞭如何獲取構造函數的參數類型數組。

 
Constructor<?> constructor = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructor(int.class);
//getting constructor parameters
System.out.println(Arrays.toString(constructor.getParameterTypes())); // prints "[int]"
        
Constructor<?> hashMapConstructor = Class.forName("java.util.HashMap").getConstructor(null);
System.out.println(Arrays.toString(hashMapConstructor.getParameterTypes())); // prints "[]"

使用Constructor實例化對象

我們可以在構造函數對象上使用newInstance()方法來實例化該類的新實例。由於我們在編譯時沒有類信息時使用反射,我們可以將它分配給Object,然後進一步使用反射來訪問它的字段並調用它的方法。


Constructor<?> constructor = Class.forName("com.journaldev.reflection.ConcreteClass").getConstructor(int.class);
//getting constructor parameters
System.out.println(Arrays.toString(constructor.getParameterTypes())); // prints "[int]"
        
Object myObj = constructor.newInstance(10);
Method myObjMethod = myObj.getClass().getMethod("method1", null);
myObjMethod.invoke(myObj, null); //prints "Method1 impl."

Constructor<?> hashMapConstructor = Class.forName("java.util.HashMap").getConstructor(null);
System.out.println(Arrays.toString(hashMapConstructor.getParameterTypes())); // prints "[]"
HashMap<String,String> myMap = (HashMap<String,String>) hashMapConstructor.newInstance(null);
 

6.註釋的反思

Java 1.5中引入了註釋來提供類,方法或字段的元數據信息,現在它在Spring和Hibernate等框架中被大量使用。Reflection API也得到了擴展,以提供在運行時分析註釋的支持。

使用反射API,我們可以分析其保留策略爲運行時的註釋。我已經編寫了一個關於註釋的詳細教程以及我們如何使用反射API來解析註釋,因此我建議您查看Java Annotations Tutorial

這就是所有的java反射示例教程,我希望你喜歡這個教程並理解Java Reflection API的重要性。

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