day43-反射02

2.Class類

2.1基本介紹

image-20220926201405016

  1. Class類也是類,因此也繼承Object類

    image-20220926192116952

  2. Class類對象不是new出來的,而是系統創建的

  3. 對於某個類的Class類對象,在內存中只有一份,因為類只加載一次

  4. 每個類的實例都會記得自己是由哪個Class實例所生成

  5. 通過Class對象可以得到一個類的完整結構(通過一系列API)

  6. Class對象是存放在堆的

  7. 類的位元組碼二進制數據,是放在方法區的,有的地方稱為類的元數據(包括 方法代碼,變量名,方法名,訪問權限等)

    當我們加載完類之後,除了會在堆里生成一個Class類對象,還會在方法區生成一個類的位元組碼二進制數據(元數據)

例子:

package li.reflection.class_;

import li.reflection.Cat;

//對Class類的特點的梳理
public class Class01 {
    public static void main(String[] args) throws ClassNotFoundException {

        //1.Class類對象不是new出來的,而是系統創建的
        //1.1.傳統的 new對象
        /**通過ClassLoader類中的loadClass方法:
         *  public Class<?> loadClass(String name) throws ClassNotFoundException {
         *         return loadClass(name, false);
         *  }
         */
         //Cat cat = new Cat();

        //1.2反射的方式
        /**在這裡debug,需要先將上面的Cat cat = new Cat();注釋掉,因為同一個類只加載一次,否則看不到loadClass方法
         * (這裡也驗證了:3.對於某個類的Class類對象,在內存中只有一份,因為類只加載一次)
         * 仍然是通過 ClassLoader類的loadClass方法加載 Cat類的 Class對象
         *  public Class<?> loadClass(String name) throws ClassNotFoundException {
         *         return loadClass(name, false);
         *     }
         */
        Class cls1 = Class.forName("li.reflection.Cat");

        //2.對於某個類的Class類對象,在內存中只有一份,因為類只加載一次
        Class cls2 = Class.forName("li.reflection.Cat");
        //這裡輸出的hashCode是相同的,說明cls1和cls2是同一個Class類對象
        System.out.println(cls1.hashCode());//1554874502
        System.out.println(cls2.hashCode());//1554874502     
    }
}

Class類對象不是new出來的,而是系統創建的:

  1. Cat cat = new Cat();處打上斷點,點擊force step into,可以看到

image-20220926192820227

  1. 注釋Cat cat = new Cat();,在Class cls1 = Class.forName("li.reflection.Cat");處打上斷點,可以看到 仍然是通過 ClassLoader類加載 Cat類的 Class對象

image-20220926200747357

2.2Class類常用方法

public static Class<?> forName(String className)//傳入完整的「包.類」名稱實例化Class對象
public Constructor[] getContructors() //得到一個類的全部的構造方法
public Field[] getDeclaredFields()//得到本類中單獨定義的全部屬性
public Field[] getFields()//得到本類繼承而來的全部屬性
public Method[] getMethods()//得到一個類的全部方法
public Method getMethod(String name,Class..parameterType)//返回一個Method對象,並設置一個方法中的所有參數類型
public Class[] getInterfaces() //得到一個類中鎖實現的全部接口
public String getName() //得到一個類完整的「包.類」名稱
public Package getPackage() //得到一個類的包
    
public Class getSuperclass() //得到一個類的父類
public Object newInstance() //根據Class定義的類實例化對象
public Class<?> getComponentType() //返回表示數組類型的Class
public boolean isArray() //判斷此class是否是一個數組

應用實例

Car:

package li.reflection;

public class Car {
    public String brand = "寶馬";
    public int price = 500000;
    public String color ="白色";

    @Override
    public String toString() {
        return "Car{" +
                "brand='" + brand + '\'' +
                ", price=" + price +
                ", color='" + color + '\'' +
                '}';
    }
}

Class02:

package li.reflection.class_;

import li.reflection.Car;

import java.lang.reflect.Field;

//演示Class類的常用方法
public class Class02 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        String classAllPath = "li.reflection.Car";

        //1.獲取到 Car類 對應的 Class對象
        //<?>表示不確定的Java類型
        Class<?> cls = Class.forName(classAllPath);
        
        //2.輸出cls
        System.out.println(cls);//將會顯示cls對象是哪個類的Class對象  class li.reflection.Car
        System.out.println(cls.getClass());//輸出cls的運行類型 class java.lang.Class
        
        //3.得到包名
        System.out.println(cls.getPackage().getName());//li.reflection :Class對象對應的類是在哪個包下面
        
        //4.得到全類的名稱
        System.out.println(cls.getName());//li.reflection.Car
        
        //5.通過cls創建一個對象實例
        Car car = (Car)cls.newInstance();
        System.out.println(car);//調用car.toString()
        
        //6.通過反射獲得屬性 如:brand
        Field brand = cls.getField("brand");
        System.out.println(brand.get(car));//寶馬
        
        //7.通過反射給屬性設置值
        brand.set(car,"奔馳");
        System.out.println(brand.get(car));//奔馳

        //8.遍歷得到所有的屬性(字段)
        Field[] fields = cls.getFields();
        for (Field f:fields) {
            System.out.println(f.getName());//依次輸出各個屬性字段的名稱
        }
    }
}

3.獲取Class類對象的各種方式

  1. 前提:已經知道一個類的全類名,且該類在類路徑下,可通過Class類的靜態方法forName()獲取,可能拋出ClassNotFoundException

    實例:Class cls1 = Class.forName("java.lang.Cat");

    應用場景:多用於配置文件,讀取類全路徑,加載類

  2. 前提:若已知具體的類,通過 類.class 獲取,該方式最為安全可靠,程序性能最高

    實例:Class cls2 = Cat.class;

    應用場景:多用於參數傳遞,比如通過反射得到對應構造器對象

  3. 前提:已某個類的實例,調用該實例的getClass()方法獲取Class對象

    實例:Class cls3 = 對象.getClass();//運行類型

    應用場景:通過創建好的對象,獲取Class對象

  4. 其他方式

    ClassLoader cl = 對象.getClass().getClassLoad();

    Class cls4 = cl.loadClass("類的全類名");

  5. 基本數據類型byte,short,int,long,double,float,boolean.char, 按如下方式得到Class類對象

    Class cls = 基本數據類型.class

  6. 基本數據類型對應的包裝類,可以通過.TYPE得到Class類對象

    Class cls = 包裝類.TYPE

例子:

package li.reflection.class_;

import li.reflection.Car;

//演示得到Class對象的各種方式
public class getClass_ {
    public static void main(String[] args) throws ClassNotFoundException {

        //1.Class.forName
        String classAllPath = "li.reflection.Car";//這裡一般是通過配置文件獲取全路徑
        Class cls1 = Class.forName(classAllPath);
        System.out.println(cls1);//class li.reflection.Car

        //2.類名.class ,多用於參數傳遞
        Class cls2 = Car.class;
        System.out.println(Car.class);//class li.reflection.Car

        //3.對象.getClass() ,應用場景,有對象實例
        Car car = new Car();
        Class cls3 = car.getClass();
        System.out.println(cls3);//class li.reflection.Car

        //4.通過類加載器(4種)來獲取到類的 Class對象
        //(1)先得到car對象的類加載器(每個對象都有一個類加載器)
        ClassLoader classLoader = car.getClass().getClassLoader();
        //(2)通過類加載器得到Class對象
        Class cls4 = classLoader.loadClass(classAllPath);
        System.out.println(cls4);//class li.reflection.Car

        //cls1,cls2,cls3,cls4其實是同一個Class對象
        System.out.println(cls1.hashCode());//1554874502
        System.out.println(cls2.hashCode());//1554874502
        System.out.println(cls3.hashCode());//1554874502
        System.out.println(cls4.hashCode());//1554874502

        //5.基本數據類型按如下方式得到Class類對象
        Class<Integer> integerClass = int.class;
        Class<Character> characterClass = char.class;
        Class<Boolean> booleanClass = boolean.class;
        System.out.println(integerClass);//int
        System.out.println(characterClass);//char
        System.out.println(booleanClass);//boolean

        //6.基本數據類型對應的8種包裝類,可以通過 .TYPE得到Class類對象
        Class<Integer> type1 = Integer.TYPE;
        Class<Character> type2 = Character.TYPE;
        System.out.println(type1);

        System.out.println(integerClass.hashCode());//1846274136
        System.out.println(type1.hashCode());//1846274136
        
    }
}

4.哪些類有Class對象

  1. 外部類,成員內部類,靜態內部類,局部內部類,匿名內部類
  2. interface:接口
  3. 數組
  4. enum:枚舉
  5. annotation:註解
  6. 基本數據類型
  7. void

例子:

package li.reflection.class_;

import java.io.Serializable;

//演示哪些類有Class對象
public class allTypeClass {
    public static void main(String[] args) {

        Class<String> cls1 = String.class;//外部類
        Class<Serializable> cls2 = Serializable.class;//接口
        Class<Integer[]> cls3 = Integer[].class;//數組
        Class<float[][]> cls4 = float[][].class;//二維數組
        Class<Deprecated> cls5 = Deprecated.class;//註解
        //Thread類中的枚舉State--用來表示線程狀態
        Class<Thread.State> cls6 = Thread.State.class;//枚舉
        Class<Long> cls7 = long.class;//基本數據類型
        Class<Void> cls8 = void.class;//void類型
        Class<Class> cls9 = Class.class;//Class類也有

        System.out.println(cls1);//class java.lang.String
        System.out.println(cls2);//interface java.io.Serializable
        System.out.println(cls3);//class [Ljava.lang.Integer;
        System.out.println(cls4);//class [[F
        System.out.println(cls5);//interface java.lang.Deprecated
        System.out.println(cls6);//class java.lang.Thread$State
        System.out.println(cls7);//long
        System.out.println(cls8);//void
        System.out.println(cls9);//class java.lang.Class
    }
}

5.類加載

image-20220928163817837

  • 基本說明:

    反射機制是java實現動態語言的關鍵,也就是通過反射實現類動態加載

    1. 靜態加載:編譯時加載相關的類,如果沒有則報錯,依賴性太強

      靜態加載的類,即使沒有用到也會加載,並且進行語法的校驗

    2. 動態加載:運行時加載相關的類,如果運行時不用該類,即使不存在該類,也不會報錯,降低了依賴性

  • 類加載的時機:

    1. 當創建對象時(new)//靜態加載
    2. 當子類被加載時 //靜態加載
    3. 調用類中的靜態成員時 //靜態加載
    4. 通過反射 //動態加載

例子:靜態加載和動態加載

import java.lang.reflect.*;
import java.util.*;

public class classLoad_ {
    public static void main(String[] args) throws Exception {
        Scanner scanner = new Scanner(System.in);
        System.out.println("請輸入key");
        String key = scanner.next();
        switch (key) {
            case "1":
                Dog dog = new Dog();//靜態加載,依賴性很強
                dog.cry();
                break;
            case "2":
                //反射 -->動態加載
                Class cls = Class.forName("Person"); //加載Person[動態加載]
                Object o = cls.newInstance();
                Method m = cls.getMethod("hi");
                m.invoke(o);
                System.out.println("ok");
                break;
            default:
                System.out.println("do nothing...");
        }
    }
}

//因為new Dog()是靜態加載,因此必須編寫Dog
//Person類是動態加載,所以即使沒有編寫Person類也不會報錯,只有當動態加載該類時,(有問題)才會報錯
class Dog{
    public void cry(){
        System.out.println("小狗在哭泣..");
    }
}

在沒有編寫Dog類時,即使在switch選擇中,不一定會運行到new dog對象的case1,但是程序仍然報錯了,因為靜態加載的類,即使沒有用到,也會加載,並且進行語法的校驗

image-20220928171802611

在編寫了Dog類對象後,可以看到編譯通過:

image-20220928172042301

運行程序:可以看到,即使沒有編寫Person類,但是運行時沒有用到,就不會報錯

image-20220928172525548

使用到Person類,報錯:(運行時加載)

image-20220928172725660

6.類的加載過程

  • 類加載過程圖

image-20220928182453805

  • 類加載各階段完成的任務
    • 加載階段:將類的class文件讀入內存,並為之創建一個java.lang.Class對象。此過程由類加載器完成。
    • 連接階段:將類的二進制數據合併到jre中
    • 初始化階段:JVM負責對類進行初始化,這裡主要是指靜態成員

6.1.1加載階段

image-20220928183631407

JVM 在該階段的主要目的是,將位元組碼從不同的數據源(可能是class文件,也可能是jar包,甚至網絡)轉化為二進制位元組流加載到內存中,並聲稱一個代表該類的java.lang.Class對象

image-20220928183033453

6.1.2連接階段-驗證

image-20220928183709788

  1. 目的是為了確保Class文件的位元組流中包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身的安全
  2. 包括:文件格式驗證(是否以 魔數 oxcafebabe開頭)、元數據驗證、位元組碼驗證和符號引用驗證

image-20220928184408532

  1. 可以考慮使用 -Xverify:none 參數關閉大部分的類驗證措施,縮短虛擬機類加載的時間

6.1.3連接階段-準備

image-20220928183734760

JVM會在該階段對靜態變量,分配內存並默認初始化(對應的數據類型的默認初始值,如0,0L,null,false等)。這些變量所使用的內存都將在方法區中進行分配

例如:

package li.reflection.classload_;

//我們說明一個類加載的鏈接階段-準備
public class ClassLoad02 {
    public static void main(String[] args) {

    }
}

class A {
    //屬性-成員變量-字段
    //一個類加載的鏈接階段中的準備階段 屬性是如何處理的
    //1. n1 是實例變量,不是靜態變量,因此在準備階段,是不會分配內存的
    //2. n2 是靜態變量,分配內存 n2,且默認初始化為 0,而不是20
    //3. n3 是static final,是常量,它和靜態變量不一樣,因為一旦賦值就不變,n3 = 30
    public int n1 = 10;
    public static int n2 = 20;
    public static final int n3 = 30;
}

6.1.4連接階段-解析

image-20220928183750334

虛擬機將常量池內的符號引用替換為直接應用的過程

個人理解 java虛擬機中的符號引用和直接引用_maerdym的博客-CSDN博客

6.1.5初始化階段

image-20220928183806300

  1. 到初始化階段,才真正開始執行類中定義的Java程序代碼,此階段是執行<clinit>()方法的過程
  2. <clinit>()方法是 由編譯器按語句在源文件中出現的順序,依次自動收集類中的 所有靜態變量 的賦值動作 和 靜態代碼塊中的語句,並進行合併。–>例子1
  3. 虛擬機會保證一個類的<clinit>()方法在多線程環境中被正確地加鎖、同步,如果多線程同時去初始化一個類,那麼只會有一個線程去執行這個類的<clinit>()方法,其他線程都需要阻塞等待,直到活動線程執行<clinit>()方法完畢。

例子1:演示類加載的初始化階段

package li.reflection.classload_;

//演示類加載的初始化階段
public class ClassLoad03 {
    public static void main(String[] args) {
        //分析:
        /**
         * 1.加載B類,並生成 B的Class對象
         * 2.鏈接 :將num默認初始化為 0
         * 3.初始化階段:
         *    3.1依次 自動收集類中的 所有靜態變量的賦值動作 和 靜態代碼塊中的語句,併合並
         *      收集:
         *      clinit(){
         *          System.out.println("B的靜態代碼塊被執行");
         *          num = 300;
         *          num = 100;
         *      }
         *      合併:num =100;
         */

        //直接使用類的靜態屬性也會導致類的加載
        System.out.println(B.num);//100

    }
}

class B {
    static {
        System.out.println("B的靜態代碼塊被執行");
        num = 300;
    }

    static int num = 100;

    public B() {
        System.out.println("B的構造器被執行");
    }
}

image-20220928193333330

例子2:

在例子1中的程序里創建一個B類對象,打上斷點,debug源碼:

image-20220928195002395

可以看到在底層中,使用了對象鎖synchronized (getClassLoadingLock(name)) :

image-20220928194154742

也就是說,加載類的時候,是有類的同步控制機制。

正因為有這個機制,才能保證某個類在內存中,只有一份Class對象。

Tags: