1. 程式人生 > 實用技巧 >Java的註解與反射機制

Java的註解與反射機制

註解

什麼是註解

  1. Annotation是從JDK5.0開始引入的新技術。
  2. Annotation的作用:
  • 不是程式本身,可以對程式作出解釋.(這一點和註釋(comment)沒什麼區別)
  • 可以被其他程式(比如:編譯器等)讀取.
  1. Annotation的格式:

    註解是以"@註釋名"在程式碼中存在的,還可以新增一些引數值﹐例如:@SuppressWarnings(value="unchecked").

  2. Annotation在哪裡使用?

    可以附加在package , class , method , field等上面﹐相當於給他們添加了額外的輔助資訊,我們可以通過反射機制程式設計實現對這些元資料的訪問。

內建註解

  • @Overrride:定義在java.lang.Override中,此註釋只適用於修辭方法﹐表示一個方法宣告打算重寫超類中的另一個方法宣告.
  • @Deprecated:定義遷java.lang.Deprecated中, 此註釋可以用於修辭方法,屬性,類,表示不鼓勵程式設計師使用這樣的元素,通常是因為它很危險或者存在更好的選擇.
  • @SuppressWarnings:定義在java.lang.SuppressWarnings中,用來抑制編譯時的警告資訊。
    • 與前兩個註釋有所不同,你需要新增一個引數才能正確使用,這些引數都是已經定義好了的,我們選擇性的使用就好了。
      • @SuppressWarnings("all")
      • @SuppressWarnings("unchecked")
      • @SuppressWarnings(value={"unchecked","deprecation"})
      • ......

練習程式碼:

package com.lijinyu.Annotation;

import java.util.ArrayList;
import java.util.List;

//什麼是註解

public class Test01 extends Object {
    //@Override  重寫的註解
    @Override
    public String toString() {
        return super.toString();
    }

    @Deprecated  //不推薦程式設計師使用,但可以使用,或者存在更好的方式
    public static void test() {
        System.out.println("Deprecated");

    }

    @SuppressWarnings("all")
    public void test02() {
        List list = new ArrayList();
    }

    public static void main(String[] args) {
        test();

    }
}

元註解

  • 無註解的作用就是負責註解其他註解,Java定義了4個標準的meta-annotation型別,他們被用來提供對其他annotation型別作說明。

  • 這些型別和它們所支援的類在java.lang.annotation包中可以找到.(@Target , @Retention ,@Documented , @Inherited )

    • @Target:用於描述註解的使用範圍(即:被描述的註解可以用在什麼地方)
    • @Retention:表示需要在上面級別儲存該註釋資訊,用於描述註解的生命週期
      • (SOURCE<CLASS<RUNTIME)
    • @Document:說明該註解將被包含在Javadoc中
    • @Inherited:說明子類可以繼承父類中的該註解

    練習程式碼:

    package com.lijinyu.Annotation;
    
    import java.lang.annotation.*;
    
    //測試元註解
    public class Test02 {
        @MyAnnotation
        public void test() {
    
        }
    }
    
    //定義一個註解
    //target表示我們的註解可以用在哪些地方
    @Target(value = {ElementType.METHOD, ElementType.TYPE})
    
    //Retention表示我們的註解在什麼地方還有效
    //runtime>class>sources
    @Retention(value = RetentionPolicy.RUNTIME)
    
    //Documented表示是否將我們的註解生產在Javadoc中
    @Documented
    
    //Inherited子類可以繼承父類的註解
    @Inherited
    @interface MyAnnotation {
    
    }
    

自定義註解

使用@interface自定義註解時,自動繼承了 java.lang.annotation.Annotation介面

分析:

  • interface用來宣告一個註解﹐格式:public interface註解名{定義內容}
  • 其中的每一個方法實際上是聲明瞭一個配置引數.
  • 方法的名稱就是引數的名稱.
  • 返回值型別就是引數的型別(返回值只能是基本型別,Class , String , enum ).
  • 可以通過default來宣告引數的預設值
  • 如果只有一個引數成員,一般引數名為value
  • 註解元素必須要有值,我們定義註解元素時,經常使用空字串,0作為預設值.

練習程式碼:

package com.lijinyu.Annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//自定義註解
public class Test03 {
    //註解可以顯示賦值,如果沒有預設值我們就必須給註解賦值
    @MyAnnotation2(age = 18, name = "李晉宇")
    public void test() {
    }

    @MyAnnotation3("李晉宇")
    public void test2() {

    }
}

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2 {
    //註解的引數:引數型別+引數名();
    String name() default "";

    int age();

    int id() default -1; //如果預設值為-1,代表不存在,indexof,如果找不到就返回-1

    String[] schools() default {"移通", "清華"};
}

@Target({ElementType.TYPE, ElementType.METHOD})
@interface MyAnnotation3 {
    String value();  //如果只有一個值 那麼通常用value 這樣在類上我們可以省去value


}

反射機制

靜態vs動態語言

動態語言

  1. 是一類在執行時可以改變其結構的語言:例如新的函式、物件、甚至程式碼可以被引進,已有的函式可以被刪除或是其他結構上的變化。通俗點說就是在執行時程式碼可以根據某些條件改變自身結構。
  2. 主要動態語言:Object-C、C#、JavaScript、PHP、Python等。

靜態語言

  • 與動態語言相對應的,執行時結構不可變的語言就是靜態語言。如Java、C、C++。
  • Java不是動態語言,但Java可以稱之為“準動態語言”。即Java有一定的動態性,我們可以利用反射機制獲得類似動態語言的特性。Java的動態性讓程式設計的時候更加靈活!

Java Reflection

Reflection(反射)是Java被視為動態語言的關鍵,反射機制允許程式在執行期藉助於Reflection API取得任何類的內部資訊,並能直接操作任意物件的內部屬性及方法。

Class c = Class.forName("java.lang.String")

載入完類之後,在堆記憶體的萬法區巴風教的米的結稱信拿廣我們可以通過這個對一個Class物件),這個物件就包含了完整的類的結構資訊。我們可以通過這個物件看到類的結構。這個物件就像一面鏡子,透過這個鏡子看到類的結構,所以,我們形象的稱之為:反射

Java反射機制研究及應用

JAVA反射機制提供的功能:

  • 在執行時判斷任意一個物件所屬的類
  • 在執行時構造任意一個類的物件
  • 在執行時判斷任意一個類所具有的成員變數和方法
  • 在執行時獲取泛型資訊
  • 在執行時呼叫任意一個物件的成員變數和方法
  • 在執行時處理註解
  • 生成動態代理

Java反射的優點與缺點

優點

可以實現動態建立物件和編譯,體現出很大的靈活性

缺點:

對效能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什麼並且它滿足我們的要求。這類操作總是慢於 直接執行相同的操作。

反射相關的主要API

java.lang.Class: 代表一個類

java.lang.reflection.Method:代表類的方法

java.lang.reflect.Field:代表類的成員變數

java.lang.reflection.Constructor:代表類的構造器

練習程式碼:

package com.lijinyu.reflection;

//什麼叫反射
public class Test02 {
    public static void main(String[] args) throws Exception {
        //通過反射獲取類Class物件
        Class c1 = Class.forName("com.lijinyu.reflection.User");
        System.out.println(c1);
        Class c2 = Class.forName("com.lijinyu.reflection.User");
        Class c3 = Class.forName("com.lijinyu.reflection.User");
        //一個類在記憶體中只有一個Class物件  hashCode相同
        //一個類的被載入後,類的整個結構都會被封裝在Class物件中
        System.out.println(c2.hashCode());
        System.out.println(c3.hashCode());


    }
}

//實體類:pojo 或者entity
class User {
    private String name;
    private int id;
    private int age;

    public User() {
    }

    public User(String name, int id, int age) {
        this.name = name;
        this.id = id;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", id=" + id +
                ", age=" + age +
                '}';
    }
}

Class類

在Object類中定義了一下的方法,此方法將所有子類繼承

public final Class getClass()

以上的方法返回值的型別是一個Class類,此類是Java反射的源頭,實際上所謂反射從程式的執行結果來看也很好理解,即:可以通過物件反射求出類的名稱。

物件照鏡子後可以得到的資訊:某個類的屬性、方法和構造器、某個類到底實現了哪些介面。對於每個類而言,JRE都為其保留一個不變的Class型別的物件。一個Class物件包含了特定某個結構(class/interface/enum/annotation/primitive type/void/[])的有關資訊。

  • Class本身也是一個類
  • Class物件只能由系統建立物件
  • 一個載入的類在JVM中只會有一個Class例項
  • 一個Class物件對應的是一個載入到JVM中的一個.class檔案
  • 每個類的例項都會記得自己是由哪個Class 例項所生成
  • 通過Class可以完整地得到一個類中的所有被載入的結構
  • Class類是Reflection的根源,針對任何你想動態載入、執行的類,唯有先獲得相應的Class物件

Class類的常用方法

方法名 功能說明
static ClassName(String name) 返回指定類名name的Class物件
Object newlnstance() 呼叫預設建構函式,返回Class物件的一個例項
getName() 返回此Class物件所表示的實體(類,介面,陣列類或void)的名稱。
Class getSuperClass() 返回當前Class物件的父類的Class物件
Class[] getinterfaces() 獲取當前Class物件的介面
ClassLoader getClassLoader() 返回該類的類載入器
Constructor[] getConstructors() 返回一個包含某些Constructor物件的陣列
Method getMothed(String name,Class...T) 返回一個Method物件,此物件的形參型別為paramType
Field[] getDeclaredFields() 返回Field物件的一個數組

獲取Class類的例項

  • 若已知具體的類,通過類的class屬性獲取,該方法最為安全可靠,程式效能最高。
Class clazz=Person.class;
  • 已知某個類的例項,呼叫該例項的getClass()方法獲取Class物件
Class clazz=Person.getClass();
  • 已知一個類的全類名,且該類在類路徑下,可通過Class類的靜態方法forName()獲取,可能丟擲ClassNotFoundException
Class clazz=Class.forName("demo01.Student");
  • 內建基本資料型別可以直接用類名.Type還可以

  • 利用ClassLoader我們之後講解

  • 練習程式碼:

  • package com.lijinyu.reflection;
    
    //測試Class類的建立方法有哪些
    public class Test03 {
        public static void main(String[] args) throws Exception {
            Person person = new Student();
            System.out.println("這個是人是" + person.name);
    
    
            //方式一:通過物件獲得
            Class c1 = person.getClass();
            System.out.println(c1.hashCode());
    
            //方式二:forname獲得
            Class c2 = Class.forName("com.lijinyu.reflection.Student");
            System.out.println(c2.hashCode());
    
            //方式三:通過類名。class獲得
            Class<Student> c3 = Student.class;
            System.out.println(c3.hashCode());
    
            //方式四:基本內建型別的包裝類都有一個Type屬性
            Class c4 = Integer.TYPE;
            System.out.println(c4.hashCode());
            System.out.println(c4);
            
            //獲得父類型別
            Class c5 = c1.getSuperclass();
            System.out.println(c5);
        }
    }
    
    class Person {
        public String name;
    
        public Person() {
        }
    
        public Person(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "Person{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
    
    class Student extends Person {
        public Student() {
            this.name = "學生";
    
        }
    }
    
    class Teacher extends Person {
        public Teacher() {
            this.name = "老師";
    
        }
    }
    

哪些型別可以有Class物件?

  • class外部類,成員(成員內部類,靜態內部類),區域性內部類,匿名內部類
  • interface:介面
  • []:陣列
  • enum:列舉
  • annotation:註解@interface
  • primitive type:基本資料型別
  • void

練習程式碼:

package com.lijinyu.reflection;

import org.omg.CORBA.ARG_OUT;

import java.lang.annotation.ElementType;

//所以型別的class
public class Test04 {
    public static void main(String[] args) {
        Class c1 = Object.class;   //類
        Class c2 = Comparable.class;   // 介面
        Class c3 = String[].class;  // 一維陣列
        Class c4 = int[][].class;   //二維陣列
        Class c5 = Override.class;  // 註解
        Class c6 = ElementType.class;   //列舉
        Class c7 = Integer.class;  //基本資料型別
        Class c8 = void.class;  //void
        Class c9 = Class.class;  //Class
        System.out.println(c1);
        System.out.println(c2);
        System.out.println(c3);
        System.out.println(c4);
        System.out.println(c5);
        System.out.println(c6);
        System.out.println(c7);
        System.out.println(c8);
        System.out.println(c9);

        //主要元素型別與維度一樣,就是同一個class。
        int[] a = new int[10];
        int[] b = new int[100];
        System.out.println(a.getClass().hashCode());
        System.out.println(b.getClass().hashCode());
    }
}

Java記憶體分析(重要,要自己懂類的載入過程等)

瞭解:類的載入過程

當程式主動使用某個類時,如果該類還未被載入到記憶體中,則系統會通過如下三個步驟來對該類進行初始化。

類的載入(Load)-->類的連線(Link)-->類的初始化(Initialize)

  • 類的載入(Load):將類的class檔案讀入記憶體,併為之建立個java.lang.Class物件。此過程由類載入器完成
  • 類的連線(Link):將類的二進位制資料合併到JRE中
  • 類的初始化(Initialize):JVM負責對類進行初始化

類的載入與ClassLoader的理解

  • 載入:將class檔案位元組碼內容載入到記憶體中,並將這些靜態資料轉換成方法區的執行時資料結構.然後生成—個代表這個類的iava.lana.Class物件
  • 連線:將Java類的二進位制程式碼合併到JVM的執行狀態之中的過程。
    • 驗證:確保載入的類資訊符合JVM規範,沒有安全方面的問題
    • 準備:正式為類變數(static)分配記憶體並設定類變數預設初始值的階段,這些記憶體都將在方法區中進行分配。
    • 解析:虛擬機器常量池內的符號引用(常量名)替換為直接引用(地址)的過程。
  • 初始化:
    • 執行類構造器()方法的過程。類構造器()方法是由編譯期自動收集類中所有類變數的賦值動作和靜態程式碼塊中的語句合併產生的。(類構造器是構造類資訊的,不是構造該類物件的構造器)。
    • 當初始化一個類的時候,如果發現其父類還沒有進行初始化,則需要先觸發其父類的初始化。
    • 虛擬機器會保證一個類的()方法在多執行緒環境中被正確加鎖和同步。

練習程式碼:

package com.lijinyu.reflection;

//類的載入
public class Test05 {
    public static void main(String[] args) {
        A a = new A();
        System.out.println(A.m);
        /*
        1.載入到記憶體,會產生一個類對應class物件
        2.連結,連結結束後 m=0
        2.初始化:
        <clinit>(){
         System.out.println("A類靜態程式碼塊初始化");
         m = 300;
         m = 100;
}
        m=100
         */
    }
}

class A {
    static {
        System.out.println("A類靜態程式碼塊初始化");
        m = 300;
    }

    static int m = 100;

    public A() {
        System.out.println("A類的無參構造初始化");
    }
}

什麼時候會發生類初始化?

  • 類的主動引用(一定會發生類的初始化)
    • 當虛擬機器啟動,先初始化main方法所在的類
    • new一個類的物件
    • 呼叫類的靜態成員(除了final常量)和靜態方法
    • 使用java.lang.reflect包的方法對類進行反射呼叫
    • 當初始化一個類,如果其父類沒有被初始化,則先會初始化它的父類
  • 類的被動引用(不會發生類的初始化)
    • 當訪問一個靜態域時,只有真正宣告這個域的類才會被初始化。如:當通過子類引用父類的靜態變數,不會導致子類初始化
    • 通過陣列定義類引用,不會觸發此類的初始化
    • 引用常量不會觸發此類的初始化(常量在連結階段就存入呼叫類的常量池中了)

練習程式碼:

package com.lijinyu.reflection;

//測試類什麼時候初始化
public class Test06 {

    static {
        System.out.println("main類被載入");
    }

    public static void main(String[] args) throws ClassNotFoundException {
        //1. 主動引用
        //Son son = new Son();

        //反射也會產生主動引用
        // Class.forName("com.lijinyu.reflection.Son");

        //不會產生類的引用的方法:
        //System.out.println(Son.b);

        //Son[] array = new Son[5];
        System.out.println(Son.M);
    }

}

class Father {
    static int b = 2;

    static {
        System.out.println("父類被載入");
    }

}

class Son extends Father {
    static {
        System.out.println("子類被載入");
        m = 300;
    }

    static int m = 100;
    static final int M = 1;
}

類載入器的作用

類載入的作用:將class檔案位元組碼內容載入到記憶體中,並將這些靜態資料轉換成方法區的執行時資料結構,然後在堆中生成一個代表這個類的java.lang.Class物件,作為方法區中類資料的訪問入口。

類快取:標準的JavaSE類載入器可以按要求查詢類,但一旦某個類被載入到類載入器中,它將維持載入(快取)一段時間。不過JVM垃圾回收機制可以回收這些Class物件

類載入器作用是用來把類(class)裝載進記憶體的。JVM規範定義瞭如下型別的類載入器。

  • 引導類載入器:用C++編寫的,是JVM自帶的類載入器,負責Java平臺核心庫,用來裝載核心類庫。該載入器無法直接獲取

  • 擴充套件類載入器:負責jre/lib/ext目錄下的jar包或-java.ext.dirs指定目錄下的jar包裝入工作庫

  • 系統類載入器:負責java -classpath或-Djava.class.path所指的目錄下的類與jar包裝入工作,是最常用的載入器

練習程式碼:

package com.lijinyu.reflection;

//獲取系統類的載入器
public class Test07 {
    public static void main(String[] args) throws ClassNotFoundException {
        //獲取系統類的載入器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);

        // 獲取系統類載入器的父類載入器-->擴充套件類載入器
        ClassLoader parent = systemClassLoader.getParent();
        System.out.println(parent);

        //獲取擴充套件類載入器的父類載入器-->根載入器(c/c++)
        ClassLoader parent1 = parent.getParent();
        System.out.println(parent1);

        //測試當前類是哪個載入器載入的
        ClassLoader classLoader = Class.forName("com.lijinyu.reflection.Test07").getClassLoader();
        System.out.println(classLoader);

        //測試JDK內建的類是誰載入的
        classLoader = Class.forName("java.lang.Object").getClassLoader();
        System.out.println(classLoader);

        //如何獲得系統類載入器可以載入的路徑

        System.out.println(System.getProperty("java.class.path"));

        //雙親委派機制   (如果你自己建立的類與根類重合了,那麼就會自己運用根目錄下的類) 安全性


    }
}

建立執行時類的物件

獲取執行時類的完整結構

通過反射獲取執行時類的完整結構:

Field(欄位) Method(方法) Constructor(構造器) Superclass(父類) Interface(介面) Annotation(註解)

  • 實現的全部介面
  • 所繼承的父類
  • 全部的構造器
  • 全部的方法
  • 全部的Field
  • 註解

練習程式碼:

package com.lijinyu.reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

//獲得類的資訊
public class Test08 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
        Class<?> c1 = Class.forName("com.lijinyu.reflection.User");

        //獲得類的名字
        System.out.println(c1.getName());  //獲得包名+類名
        System.out.println(c1.getSimpleName());  //獲得類名

        //獲得類的屬性
        System.out.println("====================================");
        Field[] fields = c1.getFields(); //只能找到public屬性

        fields = c1.getDeclaredFields();//找到全部的屬性
        for (Field field : fields) {
            System.out.println(field);

        }
        //獲得指定屬性的值
        Field name = c1.getDeclaredField("name");
        System.out.println(name);
        System.out.println("====================================");

        //獲得類的方法
        Method[] methods = c1.getMethods();  //獲得本類及其父類的全部public方法
        for (Method method : methods) {
            System.out.println("正常的:" + method);
        }
        methods = c1.getDeclaredMethods();//獲得本類的所有方法
        for (Method method : methods) {
            System.out.println("getDeclaredMethods" + method);
        }

        //獲得指定方法
        Method getName = c1.getMethod("getName", null);
        Method setName = c1.getMethod("setName", String.class);
        System.out.println(getName);
        System.out.println(setName);
        System.out.println("====================================");
        //獲得的構造器
        Constructor<?>[] constructors = c1.getConstructors();//獲得public
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructors);
        }
        constructors = c1.getDeclaredConstructors();//獲得本類的全部
        for (Constructor<?> constructor : constructors) {
            System.out.println("第二個" + constructors);

        }

        //獲得指定  的構造器
        Constructor<?> constructor = c1.getConstructor(String.class, int.class, int.class);
        System.out.println("指定構造器:" + constructor);

    }
}

小結:

  • 在實際的操作中,取得類的資訊的操作程式碼,並不會經常開發。
  • 一定要熟悉java.lang.reflect包的作用,反射機制
  • 如何取得屬性、方法、構造器的名字,修飾符

有了Class物件,能做什麼?

  • 建立類的物件:呼叫Class物件的newInstance()方法

    • 類必須有一個無引數的構造器
    • 類的構造器的訪問許可權需要足夠

    思考?難道沒有無參的構造器就不能建立物件了嗎?只要在操作的時候明確的呼叫類中的構造器,並將引數傳遞進去之後,才可以例項化操作。

  • 步驟如下:

    • 通過Class類的getDeclaredConstructor(Class..parameterTyper)取得本類的指定形參型別的構造器
    • 向構造器的形參中傳遞一個物件陣列進去,裡面包含了構造器中所需的各個引數。
    • 通過Constructor例項化物件

練習程式碼:

package com.lijinyu.reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

//動態的建立物件,通過反射
public class Test09 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        //獲得class物件
        Class c1 = Class.forName("com.lijinyu.reflection.User");

        //構造一個物件
        //  User user = (User)c1.newInstance();//本質上是呼叫了類的無參構造器
        // System.out.println(user);

        //通過構造器建立物件
        //  Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
        // User user2 = (User)constructor.newInstance("李x", 01, 21);
        //System.out.println(user2);

        //通過反射呼叫普通方法
        User user3 = (User) c1.newInstance();
        //通過反射獲取一個方法
        Method setName = c1.getDeclaredMethod("setName", String.class);
        //invoke:啟用的意思
        //(物件,“方法的值”)   使用方法
        setName.invoke(user3, "李xx");
        System.out.println(user3.getName());

        //通過反射操作屬性
        User user4 = (User) c1.newInstance();
        Field name = c1.getDeclaredField("name");
        //不能直接操作私有屬性,我們需要關閉程式的安全檢查,屬性或者方法的setAccessible(true)
        name.setAccessible(true);
        name.set(user4, "李xxxx");
        System.out.println(user4.getName());

    }
}

呼叫指定的方法

通過反射,呼叫類中的方法,通過Method類完成。

  1. 通過Class類的getMethod(String name,Class ...parameterTypes)方法取得一個人Method物件,並設定此方法操作時所需要的引數型別
  2. 之後使用object invoke(Object obj, Object[] args)進行呼叫,並向方法中傳遞要設定的obj物件的引數資訊

Object invoke (Object obj,Object... args)

  • Object對應原方法的返回值,若原方法無返回值,此過返回null
  • 若原方法若為靜態方法,此時形參Object obj可為null
  • 若原方法形參列表為空,則Object[] args為null
  • 若原方法宣告為privat e,則需要在呼叫此invoke()方法前,顯式呼叫方法物件的setAccessible(true)方法,將可訪問private的方法。

setAccessible

  • Method和Field、Constructor物件都有setAccessible()方法

  • setAccessible作用是啟動和禁用訪問安全檢查的開關。

  • 引數值為true則指示反射的物件在使用時應該取消Java語言訪問檢查。

    • 提高反射的效率。如果程式碼中必須用反射,而該句程式碼需要頻繁的被呼叫,那麼請設定為true。
    • 使得原本無法訪問的私有成員也可以訪問
  • 引數值為false則指示反射的物件應該實施Java語言訪問檢查

效能對比分析

練習程式碼:

package com.lijinyu.reflection;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

//分析效能問題
public class Test10 {

    //普通方式呼叫
    public static void test01() {
        User user = new User();

        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            user.getName();
        }

        long endTime = System.currentTimeMillis();
        System.out.println("通過普通方式需要的時間:" + (endTime - startTime) + "ms");
    }


    //反射方式呼叫
    public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class c1 = user.getClass();

        Method getName = c1.getDeclaredMethod("getName", null);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user, null);
        }

        long endTime = System.currentTimeMillis();
        System.out.println("通過反射方式需要的時間:" + (endTime - startTime) + "ms");
    }


    //反射方式呼叫  關閉檢查
    public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        User user = new User();
        Class c1 = user.getClass();

        Method getName = c1.getDeclaredMethod("getName", null);
        getName.setAccessible(true);

        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user, null);
        }

        long endTime = System.currentTimeMillis();
        System.out.println("通過反射方式並關閉檢查需要的時間:" + (endTime - startTime) + "ms");
    }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        test01();
        test02();
        test03();
    }

}

反射操作泛型

  • Java採用泛型擦除的機制來引入泛型,Java中的泛型僅僅是給編譯器javac使用的,確保資料的安全性和免去強制型別轉換問題,但是,一旦編譯完成,所有和泛型有關的型別全部擦除

  • 為了通過反射操作這些型別,Java新增了ParameterizedType , GenericArrayType ,TypeVariable和WildcardType幾種型別來代表不能被歸一到Class類中的型別但是又和原始型別齊名的型別.

  • ParameterizedType:表示一種引數化型別,比如Collection

  • GenericArrayType:表示一種元素型別是引數化型別或者型別變數的陣列型別

  • TypeVariable:是各種型別變數的公共父介面

  • WildcardType :代表一種萬用字元型別表示式

練習程式碼:

package com.lijinyu.reflection;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Map;

//通過反射獲取泛型
public class Test11 {

    public void test01(Map<String, User> map, List<User> list) {
        System.out.println("test01");

    }

    public Map<String, User> test02() {
        System.out.println("test02");
        return null;

    }

    public static void main(String[] args) throws NoSuchMethodException {
        Method method = Test11.class.getMethod("test01", Map.class, List.class);
        Type[] genericParameterTypes = method.getGenericParameterTypes();
        for (Type genericParameterType : genericParameterTypes) {
            System.out.println("**" + genericParameterTypes);
            if (genericParameterType instanceof ParameterizedType) {
                Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();

                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println(actualTypeArgument);

                }
            }

        }
        method = Test11.class.getMethod("test02", null);
        Type genericReturnType = method.getGenericReturnType();
        if (genericReturnType instanceof ParameterizedType) {
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();

            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println(actualTypeArgument);

            }
        }

    }


}

反射操作註解

練習:ORM

  • 瞭解什麼是ORM?

    • Object relationship Mapping-->物件關係對映
class Student{
    int id;
    String name;
    int age;
}

對應下表:

id name age
001 21
002 22
  • 類和表結構對應

  • 屬性和欄位對應

  • 物件和記錄對應

要求:利用註解和反射完成類和表結構的對映關係

練習程式碼:

package com.lijinyu.reflection;

import org.omg.SendingContext.RunTime;

import java.lang.annotation.*;
import java.lang.reflect.Field;

//練習反射操作註解
public class Test12 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class c1 = Class.forName("com.lijinyu.reflection.student");

        //通過反射獲得註解
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
        //獲得註解的value的值
        Tableli tableli = (Tableli) c1.getAnnotation(Tableli.class);
        String value = tableli.value();
        System.out.println(value);

        //獲得類指定的註解
        Field f = c1.getDeclaredField("name");
        Fieldli annotation = f.getAnnotation(Fieldli.class);
        System.out.println(annotation.columnName());
        System.out.println(annotation.length());
        System.out.println(annotation.type());

    }
}

@Tableli("db_student")
class student {

    @Fieldli(columnName = "db_id", type = "int", length = 10)
    private int id;
    @Fieldli(columnName = "db_age", type = "int", length = 10)
    private int age;
    @Fieldli(columnName = "db_name", type = "varchar", length = 3)
    private String name;

    public student() {
    }

    public student(int id, int age, String name) {
        this.id = id;
        this.age = age;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "student{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

//類名的註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Tableli {
    String value();
}

//屬性的註解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Fieldli {
    String columnName(); //列名

    String type();

    int length();
}

小結

  • 在實際的操作中,取得類的資訊的操作程式碼,並不會經常開發。
  • —定要熟悉java.lang.reflect包的作用,反射機制。
  • 如何取得屬性、方法、構造器的名稱,修飾符等

筆記來源:狂神說Java學習視訊