天天看點

java反射-學習筆記

反射

靜态VS動态語言

動态語言

  • 運作時結構可以發生改變的語言,稱為動态語言。
  • 主要動态語言:Object-C、C#、JavaScript、PHP、Python等。

靜态語言

  • 運作時結構不可改變的語言,稱為靜态語言。
  • 靜态語言列如:Java、C、C++。
  • Java不是動态語言,但是Java稱為“準動态語言”,因為Java有一定的動态性,我們可以使用反射獲得類似動态語言的特性。

什麼是反射(Java Reflection)

  • 反射(Java Reflection)是java被視為準動态語言的關鍵,可以在程式的執行期間,借助Reflection Api取得任何類的内部資訊,并能直接操作任意對象的内部屬性及方法。

反射的功能

  • 在運作時判斷任意一個對象所屬的類。
  • 在運作時構造任意一個類的對象。
  • 在運作時(判斷/調用)任意一個類所具有的成員變量和方法。
  • 在運作時擷取泛型的資訊。
  • 在運作時處理注解。
  • 生成動态代理。

反射的優缺點

優點

  • 可以實作動态的建立對象和編譯,展現出很大的靈活性。

缺點

  • 對性能有影響,這類操作總是慢于直接執行相同的操作。

反射相關API

java.lang.Class;//代表一個類
    java.lang.reflect.Method;//代表類的方法
    java.lang.reflect.Field;//代表類的成員變量
    java.lang.reflect.Constructor;//代表類的構造器
           

使用反射擷取類的Class對象

/**
 * @Author: Boran
 * @Description:反射
 * @Date: Created in 10:12 2020/7/11
 * @Modified By:
 */
public class Te {

    public static void main(String[] args) throws ClassNotFoundException {
        /**
         * 使用反射擷取類的Class對象
         */
        Class c1 = Class.forName("com.boran.reflection.User");
        System.out.println(c1);
        /**
         * 一個類在記憶體中隻有一個Class對象
         * 一個類被加載後,類的整個結構會被封裝在Class對象中 
         */
        Class c2 = Class.forName("com.boran.reflection.User");
        Class c3 = Class.forName("com.boran.reflection.User");
        Class c4 = Class.forName("com.boran.reflection.User");

    }

}
//實體類
class User{
    private String id;
    private String name;
    private int age;

    public User() {
    }

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

    public String getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

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

Class類

  • Class本身也是一個類
  • Class對象隻能有系統建立對象
  • 一個加載的類在JVM中隻有一個Class執行個體
  • 一個Class對象對應的是一個加載到JVM中的一個.class檔案
  • 每個類的執行個體都會記得自己是由哪個Class執行個體所生成的
  • 通過Class可以完整的得到一個類中所有被加載的結構
  • Class類時反射(Reflection)的根源,針對任何你想動态加載、運作的類,唯有先擷取相應的Class對象

Class類的常用方法

java反射-學習筆記

獲得Class類的幾種方式

/**
 * @Author: Boran
 * @Description:
 * @Date: Created in 10:45 2020/7/11
 * @Modified By:
 */
public class T1 {
    public static void main(String[] args) throws ClassNotFoundException {
        System.out.println("結果:");
        Person person=new Student();
        System.out.println("這個人是"+person.name);

        //方式1 通過對象獲得
        Class c1 = person.getClass();
        System.out.println(c1.hashCode());

        //方式2 通過forName獲得
        Class c2 = Class.forName("com.boran.refiection.Student");
        System.out.println(c2.hashCode());

        //方式3 通過類名.class獲得
        Class c3 = Student.class;
        System.out.println(c3.hashCode());

        //方式4 基本内置類型的包裝類都有一個TYPE屬性
        Class c4 = Integer.TYPE;
        System.out.println(c4);

        //獲得父類類型
        Class c5 = c1.getSuperclass();
        System.out.println(c5);

        /**
         * 結果:
         * 這個人是學生
         * 681842940
         * 681842940
         * 681842940
         * int
         * class com.boran.reflection.Person
         */
    }
}
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
/**
 * @Author: Boran
 * @Description:所有類型的Class
 * @Date: Created in 14:32 2020/7/11
 * @Modified By:
 */
public class T2 {
    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());
        /**
         * 輸出結果:
         * class java.lang.Object
         * interface java.lang.Comparable
         * class [Ljava.lang.String;
         * class [[I
         * interface java.lang.Override
         * class java.lang.annotation.ElementType
         * class java.lang.Integer
         * void
         * class java.lang.Class
         * 681842940
         * 681842940
         */

    }
}
           

Java記憶體分析

java反射-學習筆記

類加載與ClassLoader的了解

  • 加載:将class檔案位元組碼内容加載到記憶體中,并将這些靜态資料轉換成方法區的運作時資料結構,然後生成一個代表這個類的java.lang.Class對象。
  • 連結:将Java的二進制代碼合并到JVM運作狀态之中的過程。
    • 驗證:確定加載的類資訊符合JVM規範,沒有安全方面的問題
    • 準備:正式為類變量(static)配置設定記憶體并設定類變量預設初始值的階段,這些記憶體都将在方法區中進行配置設定。
    • 解析:虛拟機常量池内的符号引用(常量名)替換為直接引用(位址)的過程。
  • 初始化:
    • 執行類構造器()方法的過程。類構造器()方法是由編譯器自動收集類中的所有類變量指派動作和靜态代碼塊中的語句的合并産生的。(類構造器時構造類的資訊的,不是構造該類對象的構造器)。
    • 當初始化一個類的時候,如果發現父類還是沒有進行初始化,則需要先觸發其父類的初始化。
    • 虛拟機會保證一個類的()方法在多線程環境中被正确枷鎖和同步。
/**
 * @Author: Boran
 * @Description:類加載
 * @Date: Created in 15:12 2020/7/11
 * @Modified By:
 */
public class T3 {
    public static void main(String[] args) throws ClassNotFoundException {
        A a = new A();
        System.out.println(A.m);
        /**
         * 加載到記憶體,會産生一個類對應的Class對象
         * 連結,連結結束後m=0
         * 初始化:
         * <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包的方法對類進行反射調用
    • 當初始化一個類,如果其父類沒有初始化,則先會初始化它的父類
  • 類的被動引用(不會發生類的初始化)
    • 當通路一個靜态域時,隻有真正的聲明這個域的類才會被初始化。如:當通過子類引用父類的靜态變量,不會導緻子類初始化
    • 通過數組定義的類引用,不會發生此類的初始化
    • 引用常量不會觸發此類的初始化(常量在連結階段就存入調用類的常量池中了)
/**
 * @Author: Boran
 * @Description:
 * @Date: Created in 15:37 2020/7/11
 * @Modified By:
 */
public class T4 {
    static {
        System.out.println("main類被加載");
    }

    public static void main(String[] args) throws ClassNotFoundException {
        //主動引用
        //Son son = new Son();
        //反射也會産生主動引用
        //Class son1 = Class.forName("com.boran.reflection.Son");
        //不會産生類的引用的方法
        //System.out.println(Son.b);
        //Son[] sons = new Son[5];
        System.out.println(Son.A);
    }
}
class Father{
    static  int b = 2;
    static {
        System.out.println("父類被加載");
    }
}
class Son extends Father{
    static {
        System.out.println("子類被加載");
        a = 300;
    }
    static int a = 100;
    static final int A =1;
}
           

類加載器的作用

  • 類加載的作用:将class檔案位元組碼内容加載到記憶體中,并将這些靜态資料換成方法區的運作時資料結構,然後在堆中生成一個代表這個類的java.lang.Class對象,作為方法區中資料通路入口。
  • 類緩存:标準的javaSE類加載器可以按照要求查找類,但是一旦某個類被加載到類加載器中,他将維持加載(緩存一段時間),不過JVM垃圾回收可以回收這些Class對象。
    java反射-學習筆記
/**
 * @Author: Boran
 * @Description:類加載
 * @Date: Created in 16:04 2020/7/11
 * @Modified By:
 */
public class T5 {

    public static void main(String[] args) throws ClassNotFoundException {
        //擷取系統類的加載器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);
        //擷取系統加載器的父類加載器-->擴充類加載器
        ClassLoader parent = systemClassLoader.getParent();
        System.out.println(parent);
        //擷取擴充類加載器的父類-->跟加載器
        ClassLoader parent1 = parent.getParent();
        System.out.println(parent1);
        //測試目前類是由那個類加載加載的
        ClassLoader classLoader = Class.forName("com.boran.refiection.T5").getClassLoader();
        System.out.println(classLoader);
        //測試jdk内類是由那個類加載加載的
        ClassLoader classLoader1 = Class.forName("java.lang.Object").getClassLoader();
        System.out.println(classLoader1);

        //如何獲得類加載器可以加載的路徑
        System.out.println(System.getProperty("java.class.path"));
    }
}
           

擷取運作類的完整結構

  • 用反射擷取
/**
 * @Author: Boran
 * @Description:
 * @Date: Created in 16:31 2020/7/11
 * @Modified By:
 */
public class T6 {
    public static void main(String[] args) throws Exception{
        Class c1 = Class.forName("com.boran.refiection.User");

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

        System.out.println("=================================");
        //獲得類的屬性
        Field[] fields = c1.getFields();//隻能找到public屬性
        Field[] fields1 = c1.getDeclaredFields();//找到全部的屬性
        for (Field field : fields1) {
            System.out.println(field);
        }
        //獲得指定屬性的值
        Field name = c1.getDeclaredField("name");
        System.out.println(name);
        //擷取類的方法
        Method[] methods = c1.getMethods();//擷取本類及其父類所有的public方法
        for (Method method : methods) {
            System.out.println("正常的"+method);
        }
        Method[] methods1 = c1.getDeclaredMethods();//獲得本類的所有方法
        for (Method method : methods1) {
            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();
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }
        Constructor[] constructors1 = c1.getDeclaredConstructors();
        for (Constructor constructor : constructors1) {
            System.out.println(constructor);
        }
        //獲得指定構造器
         Constructor constructor = c1.getConstructor(String.class, String.class, int.class);
        System.out.println("指定"+constructor);
    }

}
           

動态建立對象

  • 建立類的對象:調用Class對象的newInstance()方法
    • 要點一:類必須有一個無參的構造器
    • 要點二:類的構造器的通路權限需要足夠
    • 步驟如下:
      • 1)通過Class類的getDeclaredConstructor(Class … parameterTypes)取得本類的指定形參類型的構造器
      • 2)向構造器的形參中傳遞一個對象數組進去,裡面包含了構造器中所需的各個參數
      • 3)通過Constructor執行個體化對象

調用指定方法

Object invoke(Object obj, Object … args)

  • Object對應原方法的傳回值,若原方法無傳回值,此時傳回null
  • 若原方法若為靜态方法,此時形參Object obj可為null
  • 若原方法形參清單為空,則Object[] args為null
  • 若原方法聲明為private,則需要在調用此invoke()方法前, 顯式調用方法對象的

    setAccessible(true)方法,将可通路private的方法。

setAccessible

  • Method和Field、 Constructor對象都有setAccessible()方法。
  • setAccessible作用是啟動和禁用通路安全檢查的開關。
  • 參數值為true則訓示反射的對象在使用時應該取消Java語言通路檢查。
    • 提高反射的效率。如果代碼中必須用反射,而該句代碼需要頻繁的被調用,那麼請設定為true.
    • 使得原本無法通路的私有成員也可以通路
  • 參數值為false則訓示反射的對象應該實施Java語言通路檢查
/**
 * @Author: Boran
 * @Description:動态的建立對象,通過反射
 * @Date: Created in 18:32 2020/7/11
 * @Modified By:
 */
public class T7 {
    public static void main(String[] args) throws Exception {
        //後的Class對象
        Class c1 = Class.forName("com.boran.refiection.User");
        //構造一個對象,本質上是調用無參構造器
        User  user =(User) c1.newInstance();
        System.out.println(user);
        //通過構造器建立對象
        Constructor constructor = c1.getDeclaredConstructor(String.class, String.class, int.class);
        User user2 =(User)constructor.newInstance("1", "高樂", 22);
        System.out.println(user2);
        //通過反射調用普通方法
        User user3 = (User)c1.newInstance();
        //通過反射擷取一個方法
        Method setName = c1.getDeclaredMethod("setName", String.class);
        //invoke激活的意思
        setName.invoke(user3, "高樂");
        System.out.println(user3);
        //通過反射操作屬性
        User user4 =(User) c1.newInstance();
        Field name = c1.getDeclaredField("name");
        //不能直接操作私有屬性
        // 要想操作私有屬性要取消安全檢測,使用setAccessible(true)方法
        name.setAccessible(true);
        name.set(user4, "高樂樂");
        System.out.println(user4);
    }
}
           

反射操作泛型

  • Java采用泛型擦除的機制來引|入泛型, Java中的泛型僅僅是給編譯器javac使用的,確定資料

    的安全性和免去強制類型轉換問題,但是, - -旦編譯完成,所有和泛型有關的類型全部擦除

  • 為了通過反射操作這些類型, Java新增了ParameterizedType , GenericArrayType,

    TypeVariable和WildcardType幾種類型來代表不能被歸一到Class類中的類型但是又和原

    始類型齊名的類型.

  • ParameterizedType : 表示一-種參數化類型,比如Collection
  • GenericArrayType :表示- -種元素類型是參數化類型或者類型變量的數組類型
  • TypeVariable :是各種類型變量的公共父接口
  • WildcardType :代表-種通配符類型表達式
/**
 * @Author: Boran
 * @Description:通過反射擷取泛型
 * @Date: Created in 20:00 2020/7/11
 * @Modified By:
 */
public class T8 {

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

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

    public static void main(String[] args) throws Exception {
        Method method = T8.class.getMethod("test1", Map.class, List.class);

        Type[] types = method.getGenericParameterTypes();
        for (Type type : types) {
            System.out.println(type);
            if(type instanceof ParameterizedType){
                Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println("參數"+actualTypeArgument);
                }
            }
        }
        //擷取傳回值類型
        method = T8.class.getMethod("test2",null);
        Type type = method.getGenericReturnType();

        if(type instanceof ParameterizedType){
            Type[] actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println("傳回值"+actualTypeArgument);
            }
        }
    }

}
           

反射操作注解

  • getAnnotations
  • getAnnotatiohn
/**
 * @Author: Boran
 * @Description:反射操作注解
 * @Date: Created in 20:38 2020/7/11
 * @Modified By:
 */
public class T9 {

    public static void main(String[] args) throws Exception {
        Class c1 = Class.forName("com.boran.refiection.Student2");
        //通過反射獲得注解
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
        //擷取注解的值
        TableLe annotation =(TableLe) c1.getAnnotation(TableLe.class);
        String value = annotation.value();
        System.out.println(value);
        //獲得指定類的注解
        Field f = c1.getDeclaredField("name");
        FieldLe annotation1 = f.getAnnotation(FieldLe.class);
        System.out.println(annotation1.columName());
        System.out.println(annotation1.length());
        System.out.println(annotation1.type());
    }

}
@TableLe("db_student")
class Student2{
    @FieldLe(columName = "db_id",type = "int",length = 3)
    private int id;
    @FieldLe(columName = "db_name",type = "String",length = 10)
    private String name;
    @FieldLe(columName = "db_age",type = "varchar",length = 10)
    private  int age;

    public Student2() {
    }

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

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

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

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

}
//屬性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldLe{
    String columName();
    String type();
    int length();

}