1. 程式人生 > 實用技巧 >Java基礎18--類載入器和反射機制

Java基礎18--類載入器和反射機制

1 類載入器

1.1 類載入

  • 類載入的描述
    • 當程式要使用某個類時,如果該類還未被載入到記憶體中,則系統會通過類的載入,類的連線,類的初始化這三個步驟來對類進行初始化。如果不出現意外情況,JVM將會連續完成這三個步驟,所以有時也把這三個步驟統稱為類載入或者類初始化
  • 類的載入
    • 就是指將class檔案讀入記憶體,併為之建立一個 java.lang.Class 物件
    • 任何類被使用時,系統都會為之建立一個 java.lang.Class 物件
  • 類的連線
    • 驗證階段:用於檢驗被載入的類是否有正確的內部結構,並和其他類協調一致
    • 準備階段:負責為類的類變數分配記憶體,並設定預設初始化值
    • 解析階段:將類的二進位制資料中的符號引用替換為直接引用
  • 類的初始化
    • 在該階段,主要就是對類變數進行初始化
  • 類的初始化步驟
    • 假如類還未被載入和連線,則程式先載入並連線該類
    • 假如該類的直接父類還未被初始化,則先初始化其直接父類
    • 假如類中有初始化語句,則系統依次執行這些初始化語句
    • 注意:在執行第2個步驟的時候,系統對直接父類的初始化步驟也遵循初始化步驟1-3
  • 類的初始化時機
    • 建立類的例項
    • 呼叫類的類方法
    • 訪問類或者介面的類變數,或者為該類變數賦值
    • 使用反射方式來強制建立某個類或介面對應的java.lang.Class物件
    • 初始化某個類的子類
    • 直接使用java.exe命令來執行某個主類

1.2 類載入器

1.2.1 類載入器的作用

  • 負責將.class檔案載入到記憶體中,併為之生成對應的 java.lang.Class 物件。雖然我們不用過分關心類載入機制,但是瞭解這個機制我們就能更好的理解程式的執行!

1.2.2 JVM的類載入機制

  • 全盤負責:就是當一個類載入器負責載入某個Class時,該Class所依賴的和引用的其他Class也將由該類載入器負責載入,除非顯示使用另外一個類載入器來載入
  • 父類委託:就是當一個類載入器負責載入某個Class時,先讓父類載入器試圖載入該Class,只有在父類載入器無法載入該類時才嘗試從自己的類路徑中載入該類
  • 快取機制:保證所有載入過的Class都會被快取,當程式需要使用某個Class物件時,類載入器先從快取區中搜索該Class,只有當快取區中不存在該Class物件時,系統才會讀取該類對應的二進位制資料,並將其轉換成Class物件,儲存到快取區

1.2.3 Java中的內建類載入器

  • Bootstrap class loader:它是虛擬機器的內建類載入器,通常表示為null ,並且沒有父null
  • Platform class loader:平臺類載入器可以看到所有平臺類 ,平臺類包括由平臺類載入器或其祖先定義的Java SE平臺API,其實現類和JDK特定的執行時類
  • System class loader:它也被稱為應用程式類載入器 ,與平臺類載入器不同。 系統類載入器通常用於定義應用程式類路徑,模組路徑和JDK特定工具上的類
  • 類載入器的繼承關係:System的父載入器為Platform,而Platform的父載入器為Bootstrap

1.2.4 ClassLoader 中的兩個方法

  • 方法分類

    方法名 說明
    static ClassLoader getSystemClassLoader() 返回用於委派的系統類載入器
    ClassLoader getParent() 返回父類載入器進行委派
  • 示例程式碼

    public class ClassLoaderDemo {
        public static void main(String[] args) {
            //static ClassLoader getSystemClassLoader():返回用於委派的系統類載入器
            ClassLoader c = ClassLoader.getSystemClassLoader();
            System.out.println(c); //AppClassLoader
    
            //ClassLoader getParent():返回父類載入器進行委派
            ClassLoader c2 = c.getParent();
            System.out.println(c2); //PlatformClassLoader
    
            ClassLoader c3 = c2.getParent();
            System.out.println(c3); //null
        }
    }
    

2 反射

2.1 反射的概述

  • 是指在執行時去獲取一個類的變數和方法資訊。然後通過獲取到的資訊來建立物件,呼叫方法的一種機制。由於這種動態性,可以極大的增強程式的靈活性,程式不用在編譯期就完成確定,在執行期仍然可以擴充套件

2.2 獲取Class類物件的三種方式

2.2.1 三種方式分類

  • 類名.class屬性
  • 物件名.getClass()方法
  • Class.forName(全類名)方法

2.2.2 示例程式碼

public class ReflectDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        //使用類的class屬性來獲取該類對應的Class物件
        Class<Student> c1 = Student.class;
        System.out.println(c1);

        Class<Student> c2 = Student.class;
        System.out.println(c1 == c2);
        System.out.println("--------");

        //呼叫物件的getClass()方法,返回該物件所屬類對應的Class物件
        Student s = new Student();
        Class<? extends Student> c3 = s.getClass();
        System.out.println(c1 == c3);
        System.out.println("--------");

        //使用Class類中的靜態方法forName(String className)
        Class<?> c4 = Class.forName("com.test.Student");
        System.out.println(c1 == c4);
    }
}

2.3 反射獲取構造方法並使用

2.3.1 Class類獲取構造方法物件的方法

  • 方法分類

    方法名 說明
    Constructor<?>[] getConstructors() 返回所有公共構造方法物件的陣列
    Constructor<?>[] getDeclaredConstructors() 返回所有構造方法物件的陣列
    Constructor getConstructor(Class<?>... parameterTypes) 返回單個公共構造方法物件
    Constructor getDeclaredConstructor(Class<?>... parameterTypes) 返回單個構造方法物件
  • 示例程式碼

    public class ReflectDemo01 {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
            //獲取Class物件
            Class<?> c = Class.forName("com.test.Student");
    
            //Constructor<?>[] getConstructors() 返回一個包含 Constructor物件的陣列, Constructor物件反映了由該 Class物件表示的類的所有公共建構函式
    //        Constructor<?>[] cons = c.getConstructors();
            //Constructor<?>[] getDeclaredConstructors() 返回反映由該 Class物件表示的類宣告的所有建構函式的 Constructor物件的陣列
            Constructor<?>[] cons = c.getDeclaredConstructors();
            for(Constructor con : cons) {
                System.out.println(con);
            }
            System.out.println("--------");
    
            //Constructor<T> getConstructor(Class<?>... parameterTypes) 返回一個 Constructor物件,該物件反映由該 Class物件表示的類的指定公共建構函式
            //Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 返回一個 Constructor物件,該物件反映由此 Class物件表示的類或介面的指定建構函式
            //引數:你要獲取的構造方法的引數的個數和資料型別對應的位元組碼檔案物件
    
            Constructor<?> con = c.getConstructor();
    
            //Constructor提供了一個類的單個建構函式的資訊和訪問許可權
            //T newInstance(Object... initargs) 使用由此 Constructor物件表示的建構函式,使用指定的初始化引數來建立和初始化建構函式的宣告類的新例項
            Object obj = con.newInstance();
            System.out.println(obj);
    
    //        Student s = new Student();
    //        System.out.println(s);
        }
    }
    

2.3.2 Constructor類用於建立物件的方法

方法名 說明
T newInstance(Object...initargs) 根據指定的構造方法建立物件

2.4 反射獲取構造方法並使用練習1

  • 案例需求

    • 通過反射獲取公共的構造方法並建立物件
  • 程式碼實現

    • 學生類

      public class Student {
          //成員變數:一個私有,一個預設,一個公共
          private String name;
          int age;
          public String address;
      
          //構造方法:一個私有,一個預設,兩個公共
          public Student() {
          }
      
          private Student(String name) {
              this.name = name;
          }
      
          Student(String name, int age) {
              this.name = name;
              this.age = age;
          }
      
          public Student(String name, int age, String address) {
              this.name = name;
              this.age = age;
              this.address = address;
          }
      
          //成員方法:一個私有,四個公共
          private void function() {
              System.out.println("function");
          }
      
          public void method1() {
              System.out.println("method");
          }
      
          public void method2(String s) {
              System.out.println("method:" + s);
          }
      
          public String method3(String s, int i) {
              return s + "," + i;
          }
      
          @Override
          public String toString() {
              return "Student{" +
                      "name='" + name + '\'' +
                      ", age=" + age +
                      ", address='" + address + '\'' +
                      '}';
          }
      }
      
    • 測試類

      public class ReflectDemo02 {
          public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
              //獲取Class物件
              Class<?> c = Class.forName("com.test.Student");
      
              //public Student(String name, int age, String address)
              //Constructor<T> getConstructor(Class<?>... parameterTypes)
              Constructor<?> con = c.getConstructor(String.class, int.class, String.class);
              //基本資料型別也可以通過.class得到對應的Class型別
      
              //T newInstance(Object... initargs)
              Object obj = con.newInstance("張三", 30, "西安");
              System.out.println(obj);
          }
      }
      

2.5 反射獲取構造方法並使用練習2

  • 案例需求

    • 通過反射獲取私有構造方法並建立物件
  • 程式碼實現

    • 學生類:參見上方學生類

    • 測試類

      public class ReflectDemo03 {
          public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
              //獲取Class物件
              Class<?> c = Class.forName("com.test.Student");
      
              //private Student(String name)
              //Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
              Constructor<?> con = c.getDeclaredConstructor(String.class);
      
              //暴力反射
              //public void setAccessible(boolean flag):值為true,取消訪問檢查
              con.setAccessible(true);
      
              Object obj = con.newInstance("張三");
              System.out.println(obj);
          }
      }
      

2.6 反射獲取成員變數並使用

2.6.1 Class類獲取成員變數物件的方法

  • 方法分類

    方法名 說明
    Field[] getFields() 返回所有公共成員變數物件的陣列
    Field[] getDeclaredFields() 返回所有成員變數物件的陣列
    Field getField(String name) 返回單個公共成員變數物件
    Field getDeclaredField(String name) 返回單個成員變數物件
  • 示例程式碼

    public class ReflectDemo01 {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
            //獲取Class物件
            Class<?> c = Class.forName("com.test.Student");
    
            //Field[] getFields() 返回一個包含 Field物件的陣列, Field物件反映由該 Class物件表示的類或介面的所有可訪問的公共欄位
            //Field[] getDeclaredFields() 返回一個 Field物件的陣列,反映了由該 Class物件表示的類或介面宣告的所有欄位
    //        Field[] fields = c.getFields();
            Field[] fields = c.getDeclaredFields();
            for(Field field : fields) {
                System.out.println(field);
            }
            System.out.println("--------");
    
            //Field getField(String name) 返回一個 Field物件,該物件反映由該 Class物件表示的類或介面的指定公共成員欄位
            //Field getDeclaredField(String name) 返回一個 Field物件,該物件反映由該 Class物件表示的類或介面的指定宣告欄位
            Field addressField = c.getField("address");
    
            //獲取無參構造方法建立物件
            Constructor<?> con = c.getConstructor();
            Object obj = con.newInstance();
    
    //        obj.addressField = "西安";
    
            //Field提供有關類或介面的單個欄位的資訊和動態訪問
            //void set(Object obj, Object value) 將指定的物件引數中由此 Field物件表示的欄位設定為指定的新值
            addressField.set(obj,"西安"); //給obj的成員變數addressField賦值為西安
    
            System.out.println(obj);
    
    
    
    //        Student s = new Student();
    //        s.address = "西安";
    //        System.out.println(s);
        }
    }
    

2.6.2 Field類用於給成員變數賦值的方法

方法名 說明
voidset(Object obj,Object value) 給obj物件的成員變數賦值為value

2.7 反射獲取成員變數並使用練習

  • 案例需求

    • 通過反射獲取成員變數並賦值
  • 程式碼實現

    • 學生類:參見上方學生類

    • 測試類

      public class ReflectDemo02 {
          public static void main(String[] args) throws Exception {
              //獲取Class物件
              Class<?> c = Class.forName("com.test.Student");
      
              //Student s = new Student();
              Constructor<?> con = c.getConstructor();
              Object obj = con.newInstance();
              System.out.println(obj);
      
              //s.name = "張三";
      //        Field nameField = c.getField("name"); //NoSuchFieldException: name
              Field nameField = c.getDeclaredField("name");
              nameField.setAccessible(true);
              nameField.set(obj, "張三");
              System.out.println(obj);
      
              //s.age = 30;
              Field ageField = c.getDeclaredField("age");
              ageField.setAccessible(true);
              ageField.set(obj,30);
              System.out.println(obj);
      
              //s.address = "西安";
              Field addressField = c.getDeclaredField("address");
              addressField.setAccessible(true);
              addressField.set(obj,"西安");
              System.out.println(obj);
          }
      }
      

2.8 反射獲取成員方法並使用

2.8.1 Class類獲取成員方法物件的方法

  • 方法分類

    方法名 說明
    Method[] getMethods() 返回所有公共成員方法物件的陣列,包括繼承的
    Method[] getDeclaredMethods() 返回所有成員方法物件的陣列,不包括繼承的
    Method getMethod(String name, Class<?>... parameterTypes) 返回單個公共成員方法物件
    Method getDeclaredMethod(String name, Class<?>... parameterTypes) 返回單個成員方法物件
  • 示例程式碼

    public class ReflectDemo01 {
        public static void main(String[] args) throws Exception {
            //獲取Class物件
            Class<?> c = Class.forName("com.test.Student");
    
            //Method[] getMethods() 返回一個包含 方法物件的陣列, 方法物件反映由該 Class物件表示的類或介面的所有公共方法,包括由類或介面宣告的物件以及從超類和超級介面繼承的類
            //Method[] getDeclaredMethods() 返回一個包含 方法物件的陣列, 方法物件反映由 Class物件表示的類或介面的所有宣告方法,包括public,protected,default(package)訪問和私有方法,但不包括繼承方法
    //        Method[] methods = c.getMethods();
            Method[] methods = c.getDeclaredMethods();
            for(Method method : methods) {
                System.out.println(method);
            }
            System.out.println("--------");
    
            //Method getMethod(String name, Class<?>... parameterTypes) 返回一個 方法物件,該物件反映由該 Class物件表示的類或介面的指定公共成員方法
            //Method getDeclaredMethod(String name, Class<?>... parameterTypes) 返回一個 方法物件,它反映此表示的類或介面的指定宣告的方法 Class物件
            //public void method1()
            Method m = c.getMethod("method1");
    
            //獲取無參構造方法建立物件
            Constructor<?> con = c.getConstructor();
            Object obj = con.newInstance();
    
    //        obj.m();
    
            //在類或介面上提供有關單一方法的資訊和訪問許可權
            //Object invoke(Object obj, Object... args) 在具有指定引數的指定物件上呼叫此 方法物件表示的基礎方法
            //Object:返回值型別
            //obj:呼叫方法的物件
            //args:方法需要的引數
            m.invoke(obj);
    
    //        Student s = new Student();
    //        s.method1();
        }
    }
    

2.8.2 Method類用於執行方法的方法

方法名 說明
Objectinvoke(Object obj,Object... args) 呼叫obj物件的成員方法,引數是args,返回值是Object型別

2.9 反射獲取成員方法並使用練習

  • 案例需求

    • 通過反射獲取成員方法並呼叫
  • 程式碼實現

    • 學生類:參見上方學生類

    • 測試類

      public class ReflectDemo02 {
          public static void main(String[] args) throws Exception {
              //獲取Class物件
              Class<?> c = Class.forName("com.test.Student");
      
              //Student s = new Student();
              Constructor<?> con = c.getConstructor();
              Object obj = con.newInstance();
      
              //s.method1();
              Method m1 = c.getMethod("method1");
              m1.invoke(obj);
      
              //s.method2("張三");
              Method m2 = c.getMethod("method2", String.class);
              m2.invoke(obj,"張三");
      
      //        String ss = s.method3("張三",30);
      //        System.out.println(ss);
              Method m3 = c.getMethod("method3", String.class, int.class);
              Object o = m3.invoke(obj, "張三", 30);
              System.out.println(o);
      
              //s.function();
      //        Method m4 = c.getMethod("function"); //NoSuchMethodException: com.test.Student.function()
              Method m4 = c.getDeclaredMethod("function");
              m4.setAccessible(true);
              m4.invoke(obj);
          }
      }
      

2.10 反射的案例

2.10.1 反射練習之越過泛型檢查

  • 案例需求

    • 通過反射技術,向一個泛型為Integer的集合中新增一些字串資料
  • 程式碼實現

    public class ReflectTest01 {
        public static void main(String[] args) throws Exception {
            //建立集合
            ArrayList<Integer> array = new ArrayList<Integer>();
    
    //        array.add(10);
    //        array.add(20);
    //        array.add("hello");
    
            Class<? extends ArrayList> c = array.getClass();
            Method m = c.getMethod("add", Object.class);
    
            m.invoke(array,"hello");
            m.invoke(array,"world");
            m.invoke(array,"java");
    
            System.out.println(array);
        }
    }
    

2.10.2 執行配置檔案中指定類的指定方法

  • 案例需求

    • 通過反射執行配置檔案中指定類的指定方法
  • 程式碼實現

    public class ReflectTest02 {
        public static void main(String[] args) throws Exception {
            //載入資料
            Properties prop = new Properties();
            FileReader fr = new FileReader("myReflect\\class.txt");
            prop.load(fr);
            fr.close();
    
            /*
                className=com.test.Student
                methodName=study
             */
            String className = prop.getProperty("className");
            String methodName = prop.getProperty("methodName");
    
            //通過反射來使用
            Class<?> c = Class.forName(className);//com.test.Student
    
            Constructor<?> con = c.getConstructor();
            Object obj = con.newInstance();
    
            Method m = c.getMethod(methodName);//study
            m.invoke(obj);
        }
    }