1. 程式人生 > 實用技巧 >【Python基礎程式設計022 ● 判斷語句和迴圈語句 ● 得到隨機數】

【Python基礎程式設計022 ● 判斷語句和迴圈語句 ● 得到隨機數】

反射(Reflection)

目錄

1. Java反射機制概述

反射機制允許程式在執行期藉助於Reflection API取得任何類的內部資訊,並能註解操作任意物件的內部屬性及方法

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

載入完類之後,堆記憶體的方法區就產生了一個Class型別的物件(一個類只有一個Class物件),這個物件就包含了完整的雷達結構資訊。我們可以通過這個物件看到類的結構。這個物件就像一面鏡子,透過這個鏡子可以看到類的結構,因此我們將其稱為“反射”

優點

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

缺點

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

反射的主要API

  • java.lang.Class:代表一個類
  • java.lang.reflect.Method:代表類的方法
  • java.lang.reflect.Field:代表類的成員變數
  • java.lang.reflect.Constructor:代表類的構造器
  • ......
package com.wang.reflection;

//什麼叫反射
public class test02 {
    public static void main(String[] args) throws ClassNotFoundException {
        //通過反射獲得類的Class物件
        Class c1 = Class.forName("com.wang.reflection.User");
        System.out.println(c1);

        Class c2 = Class.forName("com.wang.reflection.User");
        Class c3 = Class.forName("com.wang.reflection.User");
        Class c4 = Class.forName("com.wang.reflection.User");

        //一個類在記憶體中只有一個Class物件
        //Class類:描述類的類,所有類的父類
        //一個類被載入後,類的整個結構都被封裝在Class物件中
        System.out.println(c2.hashCode());
        System.out.println(c3.hashCode());
        System.out.println(c4.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 +
                '}';
    }
}

2. Class類

1. Class類介紹

對於每個類而言,JRE都為其保留了一個不變的Class型別的物件。一個Class物件包含了特定某個結構(class/interface/enum/annotation/primitive type/void/[])的有關資訊

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

2. 獲取Class類的例項

  • 已知具體的類,通過類的class屬性獲取,該方法最為安全可靠,程式效能最高

    Class clazz = Person.class;
    
  • 已知某個類的例項,呼叫該例項的getClass()方法獲取Class物件

    Class clazz = preson.getClass();
    
  • 已知一個類的全類名,且該類在類路徑下,可通過Class類的靜態方法forName()獲取,可能丟擲ClassNotFoundException異常

    Class clazz = Class.forName("demo01.Student");
    
  • 內建的基本資料型別可以直接用類名.Type

  • 還可以利用ClassLoader

    package com.wang.reflection;
    
    //測試class類的建立方式有哪些
    public class test03 {
        public static void main(String[] args) throws ClassNotFoundException {
            Person person = new Student();
            System.out.println("這個人是:" + person.name);
    
            //方式一:通過物件獲得
            Class c1 = person.getClass();
            System.out.println(c1.hashCode());
    
            //方式二:forName獲得
            Class c2 = Class.forName("com.wang.reflection.Student");
            System.out.println(c2.hashCode());
    
            //方式三:通過類名.class獲得
            Class c3 = Student.class;
            System.out.println(c3.hashCode());
    
            //方式四:基本內建型別的包裝類都有一個Type屬性
            Class c4 = Integer.TYPE;
            System.out.println(c4);
    
            //獲得父類的型別
            Class c5 = c1.getSuperclass();
            System.out.println(c5);
    
        }
    }
    
    class Person {
        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 = "老師";
        }
    }
    

3. 哪些型別可以有Class物件

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

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());
    }
}

3. Java記憶體分析

graph LR; id1(Java記憶體)-->id2(堆) id1-->id3(棧) id1-->id4(方法區) id2---id5[可以存放new物件和陣列] id2---id6[可以被所有的執行緒共享,不會存放別的物件引用] id3---id7[存放基本變數型別,會包含這個基本型別的具體數值] id3---id8[引用物件的變數會存放這個引用在堆裡面的具體地址] id4---id9[可以被所有的執行緒共享] id4---id10[包含了所有的class和static變數]

4. 類的載入過程

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

1. 類的載入(Load)

將類的class檔案讀入記憶體,併為之建立一個java.lang.Class物件.此過程由類載入器完成

將類的二進位制資料合併到JRE中

  1. 驗證:確保載入的類的資訊符合JVM規範,沒有安全方面的問題
  2. 準備:正式為類變數(static)分配記憶體並設定類變數預設初始值的階段,這些記憶體都將在方法區中進行分配
  3. 解析:虛擬機器常量池的符號引用(常量名)替換為直接引用(地址)的過程

3. 類的初始化(Initialize)

JVM負責對類進行初始化

  • 執行類構造器<clint> ()方法的過程.類構造器<clint> ()方法是由編譯器自動收集類中所有類變數的賦值動作和靜態程式碼塊中的語句合併產生的(類構造器是構造類資訊的,不是構造該類物件的構造器)
  • 當初始化一個類的時候,如果發現其父類還沒有進行初始化,則需要先觸發其父類的初始化
  • 虛擬機器會保證一個類的<clinit>()方法再多執行緒環境中被正確加鎖和同步

4. 具體分析

package com.wang.reflection;

public class test05 {
    public static void main(String[] args) {
        A a = new A();
        System.out.println(A.m);

        /*
        1.載入到記憶體,會產生一個類對應的Class物件
        2.連結,連結結束後m被初始化為0
        3.初始化
            <clinit>(){
                   System.out.println("A類靜態程式碼塊初始化");
                   m = 300;
                   m = 100;
                   }
             合併靜態程式碼(static)
             合併靜態程式碼的順序遵循寫程式碼時的順序
             此時m=100
         */
    }
}
/*
靜態程式碼塊優先於構造塊執行
構造塊優先於方法呼叫執行
 */
class A {
    
    static {
        System.out.println("A類靜態程式碼塊初始化");
        m = 300;
    }

    static int m = 100;

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

重點理解static的載入!

  1. 載入到記憶體,會產生一個類對應的Class物件
  2. 連結,連結結束後m被初始化為0
  3. 初始化
    (){
    System.out.println("A類靜態程式碼塊初始化");
    m = 300;
    m = 100;
    }
    合併靜態程式碼(static)
    合併靜態程式碼的順序遵循寫程式碼時的順序
    此時m=100

5. 分析類的初始化

1. 類的主動引用(一定會發生類的初始化)

  • 當虛擬機器啟動,先初始化main方法所在的類
  • new一個類的物件
  • 呼叫類的靜態成員(除了final常量)和靜態方法
  • 使用java.lang.reflect包的方法對類進行反射呼叫
  • 當初始化一個類,如果其父類沒有被初始化,則先會初始化它的父類

2. 類的被動引用(不會發生類的初始化)

  • 當訪問一個靜態域,只有真正宣告這個域的類才會被初始化.如:當通過子類引用父類的靜態變數,不會導致子類初始化
  • 通過陣列定義類引用,不會觸發此類的初始化
  • 引用常量(final)不會觸發此類的初始化(常量在連結階段就存入呼叫類的常量池中了)
package com.wang.reflection;

import java.sql.SQLOutput;

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

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

    public static void main(String[] args) throws ClassNotFoundException {
        //1.主動引用:先初始化父類再初始化子類(當虛擬機器啟動,先初始化main方法所在的類)
        //Son son = new Son();

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

        //不會產生類的引用的方法:子類呼叫父類的靜態變數,不會導致子類被初始化
        //System.out.println(Son.b);

        //通過陣列定義類引用,不會觸發此類的初始化
        //Son[] array = new Son[5];

        //引用常量(final)不會觸發此類的初始化
        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;
}

6. 類載入器的作用

1. 類載入器的作用

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

2. 類快取

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

3. 類載入器的型別

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

package com.wang.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 parentParent = parent.getParent();
        System.out.println(parentParent);

        //測試當前類是哪個載入器載入的-->系統類載入器
        ClassLoader classLoader = Class.forName("com.wang.reflection.test07").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"));
    }
}

7. 獲取執行時類的完整結構

需要注意private方法和欄位要寫getDeclaredXXX()方法

package com.wang.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.wang.reflection.User");

//        User user = new User();
//        Class c1 = user.getClass();

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

        //獲得類的屬性
        System.out.println("==============================");
        Field[] fields = c1.getFields();    //getFields()只能找到public屬性
        //fields.for    -->     遍歷陣列的快捷鍵

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

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

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

        //獲得指定方法
        //注意,要寫獲得方法的引數型別,無引數則為null,否則寫引數的類(引數型別.class)  ---->   這是由於Java存在過載的特性
        System.out.println("=======================");
        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);
        }
        constructors = c1.getDeclaredConstructors();
        for (Constructor constructor : constructors) {
            System.out.println("#" + constructor);
        }

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

    }
}

8. 反射建立物件即物件操作

package com.wang.reflection;

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.wang.reflection.User");

//        //構造一個物件:newInstance()
//        User user = (User)c1.newInstance();     //本質上是呼叫了類的無參構造器
//        System.out.println(user);
//
//        //通過構造器建立物件(如果沒有寫無參構造器),通過已有的構造器建立
//        /*
//        先獲取類物件
//        然後獲取有參構造
//        通過有參構造獲取物件
//         */
//        Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
//        User user2 = (User) constructor.newInstance("wang", 001, 18);
//        System.out.println(user2);

        //通過反射呼叫普通方法
        User user3 = (User)c1.newInstance();
        //通過反射獲取一個方法
        Method setName = c1.getDeclaredMethod("setName", String.class);

        //invoke:啟用
        //方法.invoke(物件,"方法的值")
        setName.invoke(user3, "wang");
        System.out.println(user3.getName());

        //通過反射操作屬性
        System.out.println("===========================");
        User user4 = (User)c1.newInstance();
        Field name = c1.getDeclaredField("name");

        //不能直接操作私有屬性,我們需要關閉程式的安全監測,通過屬性或方法的setAccessible(true)
        name.setAccessible(true);
        name.set(user4, "wang_sky");
        System.out.println(user4.getName());
    }
}

setAccessible

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

  • setAccessible的作用是啟動和禁用訪問安全檢查的開關(能否訪問private)

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

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

反射會影響程式的速度,關閉安全檢查會提高程式的速度

下面為測試程式碼

package com.wang.reflection;

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

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

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

    //普通方式呼叫
    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("普通方式執行10億次:" + (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("反射方式執行10億次:" + (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("反射方式,關閉安全檢測,執行10億次:" + (endTime - startTime) + "ms");
    }
}

結果

普通方式執行10億次:4ms

反射方式執行10億次:2760ms

反射方式,關閉安全檢測,執行10億次:1331ms

9. 反射操作泛型

  • ParameterizedType:表示一種引數化型別,比如Collection<String>
  • GenericArrayType:表示一種元素型別是引數化型別或者型別變數的陣列型別
  • TypeVariable:是各種型別變數的公共父介面
  • WildcardType:代表一種萬用字元型別表示式
package com.wang.reflection;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
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("#" + genericParameterType);
            //如果是泛型,執行getActualTypeArguments返回真實的型別
            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);
            }
        }
    }
}

10. 反射操作註解

getAnnotation()方法

package com.wang.reflection;

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.wang.reflection.Student2");

        //通過反射獲得註解
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }

        //獲得註解的value的值
        Table_wang table_wang = (Table_wang) c1.getAnnotation(Table_wang.class);
        String value = table_wang.value();
        System.out.println(value);

        //獲得類指定的註解
        Field f = c1.getDeclaredField("id");
        Field_wang annotation = f.getAnnotation(Field_wang.class);
        System.out.println(annotation.colunmName());
        System.out.println(annotation.type());
        System.out.println(annotation.length());
    }



}

@Table_wang("db_Student")
class Student2 {

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

    public Student2() {
    }

    public Student2(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 "Student2{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

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

//屬性的註解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Field_wang {
    String colunmName();
    String type();
    int length();
}