2.Class類 2.1基本介紹 Class類也是類,因此也繼承Object類 Class類對象不是new出來的,而是系統創建的 對於某個類的Class類對象,在記憶體中只有一份,因為類只載入一次 每個類的實例都會記得自己是由哪個Class實例所生成 通過Class對象可以得到一個類的完整結構(通過一 ...
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連接階段-解析
虛擬機將常量池內的符號引用替換為直接應用的過程
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對象。