day43-反射02

来源:https://www.cnblogs.com/liyuelian/archive/2022/09/28/16739387.html
-Advertisement-
Play Games

2.Class類 2.1基本介紹 Class類也是類,因此也繼承Object類 Class類對象不是new出來的,而是系統創建的 對於某個類的Class類對象,在記憶體中只有一份,因為類只載入一次 每個類的實例都會記得自己是由哪個Class實例所生成 通過Class對象可以得到一個類的完整結構(通過一 ...


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對象。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 裝飾器 (1)什麼是裝飾器: 器指的是工具,可以定義成函數 裝飾指的是為其他事務添加額外的東西來點綴 上面兩者合到一起: 裝飾器指的是定義一個函數,該函數用來為其他函數添加額外的功能 函數裝飾器分為: 無參裝飾器和有參裝飾兩種,二者的實現原理一樣,都是’函數嵌套+閉包+函數對象’的組合使用的產物。 ...
  • 自動化流水線在CI/CD(持續集成/持續交付或持續部署)的實踐中發揮著核心作用。本文將對什麼是CI/CD流水線、如何構建CI/CD流水線進行討論。 *持續集成:Continuous Integration *持續交付:Continuous Delivery *持續部署:Continuous Depl ...
  • 當前,全球汽車產業正在經歷從傳統工業向數字化轉型的大變革,智能化、數字化、信息化正在成為汽車電子行業轉型發展的必由之路。“軟體定義汽車”(Software Defined Vehicles,SDV)概念的提出,說明軟體在汽車產品中承擔的角色越來越重要。隨著汽車軟體的量級和複雜度不斷提高,汽車廠商對嵌 ...
  • 一、背景 使用SpringWebFlux的WebFilter時,由於不熟悉或一些思考疏忽,容易出現未知的異常。記錄一下排查與解決方案,給大家分享一下。 二、問題 2.1 問題描述 在測試介面方法時,出現的錯誤信息如下(對一些項目路徑做了修改): java.lang.IllegalStateExcep ...
  • 我們知道,要對數據求和,寫sql很簡單:select sum(exp) from table_name我們在用mybatisplus做求和計算的時候,mybatisplus的Wrapper不支持sum函數。這種情況下,我們就無法使用lambda表達式了,只能以字元串的形式寫"sum(xxx)", l ...
  • nacos 依賴 mysql 先安裝mysql ,這裡使用的是8+版本,原因在於原本的 5.7 版本中並沒有對 m1 的良好支持,如果啟動會有報錯說查詢不到對應版本信息(雖然可以通過自定義 mirror 實現) mysql 配置參考(docker-compose): mysql: image: my ...
  • 參考自《硬體架構的藝術》。 思路:產生具有50%占空比的奇數分頻時鐘,最簡單的方式是以期望輸出頻率的一半(即輸出周期的兩倍)生成兩個正交相位時鐘,這兩個正交時鐘之間有90°的相位差(即相差四分之一個周期),然後將這兩個時鐘異或,就得到了奇數的50%占空比時鐘。 本次內容針對的是3分頻。具體的思路按照 ...
  • 在內核編程中字元串有兩種格式`ANSI_STRING`與`UNICODE_STRING`,這兩種格式是微軟推出的安全版本的字元串結構體,也是微軟推薦使用的格式,通常情況下`ANSI_STRING`代表的類型是`char *`也就是ANSI多位元組模式的字元串,而`UNICODE_STRING`則代表的... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...