2.Class類
2.1基本介紹

-
Class類也是類,因此也繼承Object類
-
Class類物件不是new出來的,而是系統創建的
-
對于某個類的Class類物件,在記憶體中只有一份,因為類只加載一次
-
每個類的實體都會記得自己是由哪個Class實體所生成
-
通過Class物件可以得到一個類的完整結構(通過一系列API)
-
Class物件是存放在堆的
-
類的位元組碼二進制資料,是放在方法區的,有的地方稱為類的元資料(包括 方法代碼,變數名,方法名,訪問權限等)
當我們加載完類之后,除了會在堆里生成一個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出來的,而是系統創建的:
- 在
Cat cat = new Cat();處打上斷點,點擊force step into,可以看到

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

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類物件的各種方式
-
前提:已經知道一個類的全類名,且該類在類路徑下,可通過Class類的靜態方法forName()獲取,可能拋出ClassNotFoundException
實體:
Class cls1 = Class.forName("java.lang.Cat");應用場景:多用于組態檔,讀取類全路徑,加載類
-
前提:若已知具體的類,通過 類.class 獲取,該方式最為安全可靠,程式性能最高
實體:
Class cls2 = Cat.class;應用場景:多用于引數傳遞,比如通過反射得到對應構造器物件
-
前提:已某個類的實體,呼叫該實體的getClass()方法獲取Class物件
實體:
Class cls3 = 物件.getClass();//運行型別應用場景:通過創建好的物件,獲取Class物件
-
其他方式
ClassLoader cl = 物件.getClass().getClassLoad();Class cls4 = cl.loadClass("類的全類名"); -
基本資料型別
byte,short,int,long,double,float,boolean.char, 按如下方式得到Class類物件Class cls = 基本資料型別.class -
基本資料型別對應的包裝類,可以通過
.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物件
- 外部類,成員內部類,靜態內部類,區域內部類,匿名內部類
- interface:介面
- 陣列
- enum:列舉
- annotation:注解
- 基本資料型別
- 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.類加載

-
基本說明:
反射機制是java實作動態語言的關鍵,也就是通過反射實作類動態加載
-
靜態加載:編譯時加載相關的類,如果沒有則報錯,依賴性太強
靜態加載的類,即使沒有用到也會加載,并且進行語法的校驗
-
動態加載:運行時加載相關的類,如果運行時不用該類,即使不存在該類,也不會報錯,降低了依賴性
-
-
類加載的時機:
- 當創建物件時(new)//靜態加載
- 當子類被加載時 //靜態加載
- 呼叫類中的靜態成員時 //靜態加載
- 通過反射 //動態加載
例子:靜態加載和動態加載
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,但是程式仍然報錯了,因為靜態加載的類,即使沒有用到,也會加載,并且進行語法的校驗
在撰寫了Dog類物件后,可以看到編譯通過:

運行程式:可以看到,即使沒有撰寫Person類,但是運行時沒有用到,就不會報錯
使用到Person類,報錯:(運行時加載)
6.類的加載程序
- 類加載程序圖

- 類加載各階段完成的任務
- 加載階段:將類的class檔案讀入記憶體,并為之創建一個java.lang.Class物件,此程序由類加載器完成,
- 連接階段:將類的二進制資料合并到jre中
- 初始化階段:JVM負責對類進行初始化,這里主要是指靜態成員
6.1.1加載階段
JVM 在該階段的主要目的是,將位元組碼從不同的資料源(可能是class檔案,也可能是jar包,甚至網路)轉化為二進制位元組流加載到記憶體中,并聲稱一個代表該類的java.lang.Class物件
6.1.2連接階段-驗證
- 目的是為了確保Class檔案的位元組流中包含的資訊符合當前虛擬機的要求,并且不會危害虛擬機自身的安全
- 包括:檔案格式驗證(是否以 魔數 oxcafebabe開頭)、元資料驗證、位元組碼驗證和符號參考驗證
- 可以考慮使用 -Xverify:none 引數關閉大部分的類驗證措施,縮短虛擬機類加載的時間
6.1.3連接階段-準備
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連接階段-決議
虛擬機將常量池內的符號參考替換為直接應用的程序
個人理解 java虛擬機中的符號參考和直接參考_maerdym的博客-CSDN博客
6.1.5初始化階段
- 到初始化階段,才真正開始執行類中定義的Java程式代碼,此階段是執行
<clinit>()方法的程序 <clinit>()方法是 由編譯器按陳述句在源檔案中出現的順序,依次自動收集類中的 所有靜態變數 的賦值動作 和 靜態代碼塊中的陳述句,并進行合并,-->例子1- 虛擬機會保證一個類的
<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的構造器被執行");
}
}
例子2:
在例子1中的程式里創建一個B類物件,打上斷點,debug原始碼:
可以看到在底層中,使用了物件鎖synchronized (getClassLoadingLock(name)) :
也就是說,加載類的時候,是有類的同步控制機制,
正因為有這個機制,才能保證某個類在記憶體中,只有一份Class物件,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/509764.html
標籤:Java
