反射(Reflection)

什麼是反射

反射就是在運行時把 Java 類中的各類成分映射成相應的 Java 類(Method、Annotation等),能夠動態得獲取全部的屬性以及動態調用任意一個方法。java


this

class A{
    public A(){
        System.out.println(this.toString() + ";;;;" + this.getClass().getName());
    }
}

class B extends A{
    public B() {
        System.out.println(this.toString() + ";;;;" + super.getClass().getName());
    }
}

public class C extends B{
    public C() {
        System.out.println(this.toString() + ";;;;" + this.getClass().getName());
    }
    
    public static void main(String[] args) {
        C c = new C();        
    }
    
}

輸出結果:
test.C@33b7b32c;;;;test.C
test.C@33b7b32c;;;;test.C
test.C@33b7b32c;;;;test.Cc++

可見this是基於運行時,this的物理內存地址都是C。bootstrap


Class概念

Class類是繼承Type的。
圖片描述數組

數組屬於被映射爲 Class 對象的一個類,具備相同元素類型和維數的數組都共享該 Class 對象。基本的 Java 數據類型和關鍵字 void 也表示爲 Class 對象。
Class 沒有公共構造方法。Class<T> 對象是在加載類時由類加載器中的 defineClass 方法自動構造的。
裝載:
類的裝載是經過類加載器完成的,加載器將*.class文件的字節碼文件裝入JVM的方法區,而且在堆區建立描述這個類的java.lang.Class對象。 可是同一個類只會被類裝載器裝載一遍。
鏈接:
鏈接就是將已經讀入到內存的類的二進制數據合併到虛擬機的運行時環境中去,分爲校驗,準備,解析這3個階段。校驗通常爲字節碼合法性驗證,還有語義檢查等;準備就是爲靜態變量分配內存空間,並設置默認值;解析指類的二進制數據中的符號引用替換爲直接引用(指針)。jvm

Class.forName(ClassName).newInstance() //類實例化,默認用的空參構造函數。ide

//經過指定的Constructor對象來實例化對象
public static void main(String[] args) {  
   try {  
           Class demo = Class.forName("mypackage.Tests"); //getSuperclass() 取得父class        
           Class[] cl = {int.class, int.class};  //指定 Constructor的構造類型
           Constructor con = demo.getConstructor(cl);              
           Object[] x = {new Integer(33),new Integer(67)};  //給傳入參數賦初值  
           Object obj = con.newInstance(x);  //實例化  
       } catch (Exception e) {  
           
       }  
}  

class Tests{  
    public Tests(int x, int y){  
       System.out.println(x+"    "+y);  
    }  
}

反射API操做

Class<?> demo = Class.forName("wangliqiu.test.reflect.MyTest");
Field[] fields = demo.getDeclaredFields();
/*
 * getFields()得到某個類的全部public的字段,包括父類。 
 * getDeclaredFields()得到某個類的public、private和proteced的字段,可是不包括父類的申明字段。 
 * 一樣還有getConstructors()和getDeclaredConstructors();getMethods()和getDeclaredMethods()。
 */
for (Field field : fields) {
    field.setAccessible(true);// 強制訪問
    // 屬性
    System.out.println("Field: " + field.getName());
    // 權限修飾符
    int mo = field.getModifiers();
    System.out.println("Modifier: " + Modifier.toString(mo));
    // 屬性類型
    Class<?> classType = field.getType();
    System.out.println("Type: " + classType.getName());
}

Class<?> superClass = demo.getSuperclass();// 僅獲取父類
System.out.println("getSuperclass: " + superClass);
Type superType = demo.getGenericSuperclass();// 獲取完整的父類(帶有泛型參數)
System.out.println("getGenericSuperclass: " + superType);
// ParameterizedType參數化類型,即泛型
// getActualTypeArguments獲取類型的參數類型,泛型參數可能有多個
Type[] types = ((ParameterizedType) superType).getActualTypeArguments();
for (Type type : types) {
    System.out.println("getActualTypeArguments: " + type);
}

// 可能存在同名但不一樣參數個數或參數類型的方法,因此getMethod()的方法特徵簽名要全面。
Method method = demo.getMethod("justTest", String.class, int.class);
method.invoke(demo.newInstance(), "wangliqiu", 20);

public void justTest(String name, int age) {
    System.out.println(name + "====" + age);
}

ClassLoader

1)Bootstrap ClassLoader 用c++編寫,引導做用。
2)Extension ClassLoader 用java編寫,用來進行擴展類的加載,通常對應的是jrelibext目錄中的類
3)AppClassLoader 用java編寫,加載classpath指定的類,是最經常使用的加載器。函數

在加載類時,每一個類加載器會先將加載類的任務交給其parent,若是parent找不到,再由本身負責加載。因此加載順序以下圖,若都找不到,會拋出NoClassDefFoundError。
圖片描述this

// Bootstrap Loader會加載系統參數sun.boot.class.path指定位置中的文件
System.out.println(System.getProperty("sun.boot.class.path"));
// ExtClassLoader會加載系統參數java.ext.dirs指定位置中的文件
System.out.println(System.getProperty("java.ext.dirs"));
// AppClassLoader會加載系統參數java.class.path指定位置中的文件,即Classpath路徑
System.out.println(System.getProperty("java.class.path"));

/*
 * java.exe啓動會嘗試找到JRE安裝目錄的jvm.dll,接着啓動JVM ,產生Bootstrap Loader,Bootstrap Loader會加載
 * ExtClassLoader,並設定ExtClassLoader的parent爲Bootstrap Loader。接着Bootstrap
 * Loader會加載AppClassLoader,並將AppClassLoader的parent設定爲Extended Loader。
 */
ClassLoader loader = this.getClass().getClassLoader();
System.out.println(loader.getClass().getName());

// This method will return null if this class was loaded by the bootstrap class loader.
ClassLoader loaderloader = loader.getClass().getClassLoader();
System.out.println(loaderloader);

ClassLoader parentLoader = loader.getParent();
System.out.println(parentLoader.getClass().getName());

// This method will return null if this class loader's parent is the bootstrap class loader.
ClassLoader GrandLoader = parentLoader.getParent();
System.out.println(GrandLoader);

注:
Class.forName除了將class文件加載到jvm中以外,還會執行類中的static塊。而classLoader只將*.class文件加載到jvm中,不會執行static塊,只有在newInstance纔會去執行static塊。Class.forName(name, initialize, loader)帶參函數也可控制是否加載static塊。spa


類加載及初始化過程

  1. 類加載:Bootstrap Loader -> ExtClassLoader, Bootstrap Loader -> AppClassLoader 指針

  2. 靜態代碼塊初始化 

  3. 連接

    a) 驗證:是否符合java規範 
    b) 準備:默認初始值 (如boolean爲false,int爲0等)
    c) 解析:符號引用轉爲直接引用,解析地址 
  4. 初始化 

    a) 賦值:類中屬性的初始值 
    b) 構造:構造函數 

@interface

@Retention、@Target、@Documented、@Inherited能夠用來修飾註解,是註解的註解,稱爲元註解。

  • @Retention

Retention註解有一個屬性value,是Enum RetentionPolicy枚舉類型,RetentionPolicy有3個值:CLASS  RUNTIME   SOURCE。
@Retention(RetentionPolicy.SOURCE ) 表示註解的信息只留在源文件中,不參與編譯;
@Retention(RetentionPolicy.CLASS) 表示註解的信息被保留在*.class文件(字節碼文件)中(程序編譯中),但不會被JVM讀取;
@Retention(RetentionPolicy.RUNTIME ) 表示註解的信息被保留在*.class文件(字節碼文件)中,也會被JVM保留在運行時。

例如:

@Retention(RetentionPolicy.SOURCE )  
public @interface Override  
 
@Retention(RetentionPolicy.SOURCE )  
public @interface SuppressWarnings  
 
@Retention(RetentionPolicy.RUNTIME )  
public @interface Deprecated
  • @Target

例子:

@Retention(RetentionPolicy.RUNTIME)  
@interface Test{
    String value();
}

@Retention(RetentionPolicy.RUNTIME)  
@interface MyAnnotation{  
    String hello() default "shanghai";  
    String world();    //world()沒有默認值,添加註釋MyAnnotation時, world屬性必須輸入參數
    int[] array() default { 2, 4, 5, 6 };      
    Test lannotation() default @Test(value = "ddd");  
    Class<?> style() default String.class;  
}

@MyAnnotation(hello = "beijing", world="class", array={1, 2, 3}, style= int.class)  
class MyTest{  
    @MyAnnotation(world = "method", lannotation=@Test(value="baby"))  
    @Deprecated  
    @SuppressWarnings("")
    public void output(String str, int i){  
        System.out.println("str:" + str +"   int:" + i);  
    }  
}

public class MyReflection{  
    public static void main(String[] args) throws Exception{  
        
        MyAnnotation myClassAnnotation = MyTest.class.getAnnotation(MyAnnotation.class);
        System.out.println("hello:" + myClassAnnotation.hello() + "    world:" + myClassAnnotation.world());
        
        MyAnnotation myMethodAnnotation = method.getAnnotation(MyAnnotation.class);                      
        System.out.println("hello:" + myMethodAnnotation.hello() + "   world:" + myMethodAnnotation.world() +
                "    @Test.value:" + myMethodAnnotation.lannotation().value()     );
        
        Annotation[] annotations = method.getAnnotations();  //得到方法的全部運行時的註釋
        for (Annotation annotation : annotations) {  
            System.out.println(annotation.annotationType().getName());  
        }  
   }  
}

輸出:
str:gogo int:9999
hello:beijing world:class
hello:shanghai world:method @Test.value:baby
test.MyAnnotation
java.lang.Deprecated
@SuppressWarnings("")沒有顯示,可見只有運行時的註釋纔會執行。

  • @Documented

由@Documented修飾的註解能夠在javadoc中顯示。

@Documented
public @interface Column {
     ....
}
@Column     //@Column將在javadoc中顯示
Public Demo demo {
....
}
  • @Inherited

子類能夠繼承父類的帶有@Inherited修飾的註解。

@Inherited
public @interface Column {
     ....
}
@Column 
Public class Super {
    ....
}

Public class Sub extends Super {
    ....
}

則Sub繼承@Column註解。

相關文章
相關標籤/搜索