主要參考了以下三個鏈接,按參考比重由大到小排列,都是很好的博文,
強烈推薦第一篇,我學習Class的思路都是按照第一篇文章來的,
我這篇文章也主要是通過第一篇文章總結出來的,一些例子和原話直接截圖原文了
爲了自己鞏固記憶,梳理總結多篇文章的知識點,特發此文
https://blog.csdn.net/mcryeasy/article/details/52344729
https://blog.csdn.net/chenge_j/article/details/72676467
https://blog.csdn.net/weixin_44650929/article/details/88680228
一.概念
1.從某種意義上來說,java有兩種對象,一種是實例對象,一種是Class對象
2.每個類的運行時的類型信息就是用Class對象表示的
3.實例對象是根據Class對象創建的
4.多態就是基於RTTI(運行時類型識別Run-Time Type Identification)實現的,而RTTI是java使用Class對象執行的
5.每個類都有一個Class對象,每編譯一個新類就產生一個Class對象
6.基本類型,數組,關鍵字void都有Class對象
7.Class對象對應着java.lang.Class類
8.如果說類是對 對象的抽象和集合,那麼Class就是對 類的抽象和集合
9.Class類沒有公共構造方法,Class對象是在類加載的時候由java虛擬機及通過調用類加載器中的defineClass方法自動構造的,因此不能顯式聲明一個Class對象
10.Class對象和其他對象一樣,我們可以獲取並操作它的引用
11.一旦類被加載到內存上,不論是怎樣創建的class對象(new,還是調用靜態成員,還是Class.forName),它們返回的都是指向同一個java堆地址上的Class引用,jvm不會創建兩個相同類型的Class對象,即,內存上,一個類的Class對象是唯一的
但是嚴格來說:
(下面這個圖片是鏈接中的原文,但是我還沒有接觸過類加載器,沒有親眼見過這種情況)
二.一個類被加載到內存並供使用需要經歷以下三個階段
1.加載
由類加載器(ClassLoader)執行
類加載器首先檢查這個類的Class對象是否已被加載,如果尚未加載,默認的類加載器就會通過一個類的全限定名來獲取其定義的二進制字節流(Class字節碼)
字節碼被加載時,會被驗證確保其沒有被破壞,不含不良java代碼,
然後根據字節碼在java堆中生成一個代表這個類的java.lang.Class對象
一旦某個類的Class對象被載入內存,就可以用它來創建這個類的所有對象
2.鏈接
在鏈接階段將驗證Class文件中的字節流包含的信息是否符合當前虛擬機的要求,
爲靜態域分配存儲空間並設置類變量的初始值(默認的零值),
如果必需的話,將常量池中的符號引用轉化爲直接引用
符號引用參見:https://www.cnblogs.com/shinubi/articles/6116993.html
3.初始化
此階段才真正開始執行類中定義的java程序代碼.
執行該類的靜態初始器和靜態初始塊,
如果該類有父類,則優先對其父類進行初始化
需要注意的是:
所有的類都是在第一次使用時,動態加載到JVM中的(懶加載)
因此java程序在它開始運行之前並非被完全加載,各個類都是在必需時才加載
這與許多傳統語言不通,動態加載使能的行爲,在c++等靜態加載語言中是很難實現的
靜態代碼塊 僅在 類被第一次加載時纔會執行
類被加載的兩種情況:
當程序首次引用對類的靜態成員(靜態方法或者非 常量靜態域),會加載這個類,
或者首次使用new創建類對象的時候也會被當做對類的靜態成員的引用,會加載這個類
(靜態成員,參考鏈接:https://blog.csdn.net/qq_41431457/article/details/85337939)
三.獲取Class對象的三種方式and觀察類被加載的情況
1.Class.forName("類的全限定名")
Class.forName("全限定名")是Class類的靜態成員,forName的時候如果發現類還沒有被加載,JVM就會調用類加載器去加載這個類,並返回加載後的Class對象.如果找不到這個類,會拋出ClassNotFoundException
Class.forName的好處是,不需要爲了獲取Class引用而持有該類型的對象,只要有全限定名即可.
new也是,如果發現類沒有加載過,會立即加載類
比如下面new了兩次cat(或者一次new,一次用Class.forName),但是cat中的靜態代碼塊只執行了一次
package com.cloudiip.classtest;
class Dog {
static {
System.out.println("dog---------");
}
}
class Cat {
static {
System.out.println("cat---------");
}
}
public class Test{
@org.junit.Test
public void test(){
System.out.println("main----------");
new Cat();
System.out.println("cat第一次結束----------");
new Dog();
System.out.println("dog第一次結束-------------------------");
try {
Class catClass=Class.forName("com.cloudiip.classtest.Cat");
} catch (ClassNotFoundException e) {
System.out.println("用Class.forName獲取cat失敗----------");
}
System.out.println("cat第二次結束----------");
}
}
2.實例對象.getClass()
如果已經有該類型的對象,可以通過該對象.getClass()來獲取Class引用
這個方法是Object中的,返回的是表示該對象實際類型的Class引用
這個方法肯定不會觸發類的第一次加載,因爲實例對象都有了,類肯定已經被加載過了
3.類.class
使用類字面常量是一種不需要try,catch的,更簡單安全的方式
不僅可以應用於普通的類,也可以應用於接口,數組及基本數據類型
用.class來創建對Class對象的引用時,不會自動地初始化該Class對象(這點和Class.forName,new 對象不同)
類對象的初始化階段被延遲了,靜態成員被首次引用時才執行
需要注意的是:
基本數據類型.class和其包裝類的.class不同
Class c1=Cat.class;
Class c2=int.class;
Class c3=Integer.class;
Class c4=Integer.TYPE;
.class的例子見下,Dog.class的時候沒有加載類,調用Dog中的靜態常量s1也沒有加載類,但是調用s2這個非常量靜態域的時候才加載類,執行了靜態代碼塊
package com.cloudiip.classtest;
class Dog {
static final String s1 = "Dog_s1";
static String s2 = "Dog_s2";
static {
System.out.println("Loading Dog");
}
}
class Cat {
static String s1 = "Cat_s1";
static {
System.out.println("Loading Cat");
}
}
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
System.out.println("----Star Dog----");
Class dog = Dog.class;
System.out.println("------");
System.out.println(Dog.s1);
System.out.println("------");
System.out.println(Dog.s2);
System.out.println("---start Cat---");
Class cat = Class.forName("com.cloudiip.classtest.Cat");
System.out.println("-------");
System.out.println(Cat.s1);
System.out.println("finish main");
}
}
上面Dog中s1這樣被static final修飾的字段,被稱爲 編譯時常量,因爲在編譯期就把結果放進常量池中了,
調用這個字段的時候是不會對Dog類進行初始化的
但是,如果只是將一個域設置成static或者final,還不足以確保這種效果,如同Dog中的s2被調用後,會強制Dog進行類的初始化
使用javap -c -v Dog.class 對Dog的字節碼反編譯後能看到,由於s1被static final修飾,而s2只有static,因此反編譯文件中,s1多了一個ConstantValue,表示這個值被寫到了常量池
static final的特例:
下面的staticFinal2,沒有加載到常量區,因此調用這個常量的時候會觸發該類初始化
四.泛型Class引用
向Class引用添加泛型語法的原因僅僅是爲了提供編譯期類型檢查
Class引用表示的就是它指向的對象的確切類型,而該對象就是Class類的一個對象
javaSE5中,允許對Class引用所指向的Class對象的類型進行限定,也就是說可以用Class對象使用泛型語法.通過泛型語法,可以讓編譯器強制指向額外的類型檢查
雖然int.class和Integer.class指向的不是同一個Class對象引用,但是它們是基本類型和包裝類的關係,int可以自動包裝成Integer,所以編譯可以通過
而Double不是Integer,也不能包裝成Integer,因此編譯無法通過
Class<Integer> c1=int.class; System.out.println(c1); c1=Integer.class; System.out.println(c1); //c1=Double.class; 編譯報錯 |
泛型中的類型也不能持有其子類的引用
Class<Number> c1=Number.class; System.out.println(c1); //c1=Integer.class; 編譯報錯 |
但是爲了使用泛化的Class放鬆限制,我們可以使用通配符,它是java泛型的一部分
通配符?------表示任何事物
Class<?> c1=Integer.class; c1=Double.class; |
? 可以與extends結合使用 表示子類
Class<? extends Number> c1=Number.class; System.out.println(c1); c1=Double.class; System.out.println(c1); c1=Integer.class; System.out.println(c1); |
? 可以用super結合使用 表示父類(父類的父類也可以)
Class<? super Integer> c1=Integer.class; System.out.println(c1); c1=Number.class; System.out.println(c1); c1=Object.class; System.out.println(c1); |
五.Class類的方法
1.方法一覽
import java.lang.reflect.Field;
interface I1 {
}
interface I2 {
}
class Cell{
public int mCellPublic;
}
class Animal extends Cell{
private int mAnimalPrivate;
protected int mAnimalProtected;
int mAnimalDefault;
public int mAnimalPublic;
private static int sAnimalPrivate;
protected static int sAnimalProtected;
static int sAnimalDefault;
public static int sAnimalPublic;
}
class Dog extends Animal implements I1, I2 {
private int mDogPrivate;
public int mDogPublic;
protected int mDogProtected;
private int mDogDefault;
private static int sDogPrivate;
protected static int sDogProtected;
static int sDogDefault;
public static int sDogPublic;
}
public class Test {
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
Class<Dog> dog = Dog.class;
//類名打印
System.out.println(dog.getName()); //com.cry.Dog
System.out.println(dog.getSimpleName()); //Dog
System.out.println(dog.getCanonicalName());//com.cry.Dog
//接口
System.out.println(dog.isInterface()); //false
for (Class iI : dog.getInterfaces()) {
System.out.println(iI);
}
/*
interface com.cry.I1
interface com.cry.I2
*/
//父類
System.out.println(dog.getSuperclass());//class com.cry.Animal
//創建對象
Dog d = dog.newInstance();
//字段
for (Field f : dog.getFields()) {
System.out.println(f.getName());
}
/*
mDogPublic
sDogPublic
mAnimalPublic
sAnimalPublic //父類中的公共的靜態變量也能打印出來
mCellPublic //父類的父類的公共字段也打印出來了
*/
System.out.println("---------");
for (Field f : dog.getDeclaredFields()) {
System.out.println(f.getName());
}
/** 只有自己類聲明的字段,四種權限都可
mDogPrivate
mDogPublic
mDogProtected
mDogDefault
sDogPrivate
sDogProtected
sDogDefault
sDogPublic
*/
}
}
2.getName、getCanonicalName與getSimpleName的區別
package com.cry; public class Test { private class inner{ } public static void main(String[] args) throws ClassNotFoundException { //普通類 System.out.println(Test.class.getSimpleName()); //Test System.out.println(Test.class.getName()); //com.cry.Test System.out.println(Test.class.getCanonicalName()); //com.cry.Test //內部類 System.out.println(inner.class.getSimpleName()); //inner System.out.println(inner.class.getName()); //com.cry.Test$inner System.out.println(inner.class.getCanonicalName()); //com.cry.Test.inner //數組 System.out.println(args.getClass().getSimpleName()); //String[] System.out.println(args.getClass().getName()); //[Ljava.lang.String; System.out.println(args.getClass().getCanonicalName()); //java.lang.String[] //我們不能用getCanonicalName去加載類對象,必須用getName //Class.forName(inner.class.getCanonicalName()); 報錯 Class.forName(inner.class.getName()); } } |
六.源碼分析
//前一個Class表示這是一個類的聲明,第二個Class是類的名稱, <T>表示這是一個泛型類,並實現了四種接口。 public final class Class<T> implements java.io.Serializable,GenericDeclaration,Type, AnnotatedElement { //定義了三個靜態變量 private static final int ANNOTATION= 0x00002000; private static final int ENUM = 0x00004000; private static final int SYNTHETIC = 0x00001000;
//定義了一個名爲registerNatives()的本地方法,並在靜態塊中調用: private static native void registerNatives(); static { registerNatives(); }
// 私有構造函數,只能由JVM調用,創建該類實例 private Class(ClassLoader loader) { classLoader = loader; } /*如果Class對象是一個Java類,返回class full_classname,即class 包名.類名; 比如上面例子的List,返回的就是class java.util.List; 如果是接口,將class改成interface; 如果是void類型,則返回void; 如果是基本類型,返回基本類型。*/ public String toString() { return (isInterface() ?"interface " : (isPrimitive() ? "" : "class")) + getName(); } |