还在对java类、类的加载一知半解?这篇文章相信会解决你80%的困惑
目錄
什么是Class類
Class類的常用方法
獲取Class類的實(shí)例
哪些類型可以有Class對象?
類加載內(nèi)存分析
類加載的過程
類的加載與ClassLoader的理解
什么時(shí)候會(huì)發(fā)生類初始化?
類如何加載的
類加載器的作用
什么是Class類
? ? 在Object類中定義了以下的方法,此方法將被所有子類繼承:
? ? public final Class getClass();
? ? 以上的方法返回值的類型是一個(gè)Class類,此類是Java反射的源頭,實(shí)際上所謂反射從程序的運(yùn)行結(jié)果來看也很好理解,即:可以通過對象反射求出類的名稱。
? ? 注意:一個(gè)類只有唯一對應(yīng)的一個(gè)Class類!
public class Test01 {public static void main(String[] args) throws ClassNotFoundException {//通過反射獲取類的class對象Class c1 = Class.forName("com.myUtils.reflectUtils.learn.User");System.out.println(c1);//class com.myUtils.reflectUtils.learn.User//一個(gè)類在內(nèi)存中只有一個(gè)Class對象,一個(gè)類被加載,類的整個(gè)結(jié)構(gòu)都會(huì)被封裝在Class對象中。Class c2 = Class.forName("com.myUtils.reflectUtils.learn.User");Class c3 = Class.forName("com.myUtils.reflectUtils.learn.User");Class c4 = Class.forName("com.myUtils.reflectUtils.learn.User");System.out.println(c2.hashCode());//1973336893System.out.println(c3.hashCode());//1973336893System.out.println(c4.hashCode());//1973336893} }class User{private Integer id;private String name;private Integer age;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +", age=" + age +'}';} }? ? 對象照鏡子后可以得到的信息:某個(gè)類的屬性、方法和構(gòu)造器、某個(gè)類到底實(shí)現(xiàn)了哪些接口。對于每個(gè)類而言,JRE都為其保留一個(gè)不變的Class類型的對象。一個(gè)Class對象包含了特定某個(gè)結(jié)構(gòu)(class/interface/enum/annotation/primitive type/void/[])的有關(guān)信息。
? ? 1.Class本身也是一個(gè)類。
? ? 2.Class對象只能由系統(tǒng)建立對象。
? ? 3.一個(gè)加載的類在JVM中只會(huì)有一個(gè)Class實(shí)例。
? ? 4.一個(gè)Class對象對應(yīng)的是一個(gè)加載到JVM中的一個(gè).class文件。
? ? 5.每個(gè)類的實(shí)例都會(huì)記得自己是由哪個(gè)Class實(shí)例所生成。
? ? 6.通過Class可以完整地得到一個(gè)類中的所有被加載的結(jié)構(gòu)。
? ? 7.Class類是Reflection的根源,針對任何你想動(dòng)態(tài)加載、運(yùn)行的類,唯有先獲得相應(yīng)的Class對象。
Class類的常用方法
| static Class forName(String name) | 返回指定類名name的Class對象 |
| Object newInstance() | 調(diào)用缺省構(gòu)造函數(shù),返回Class對象的一個(gè)實(shí)例 |
| getName() | 返回此Class對象所表示的實(shí)體(類、接口、數(shù)組類或void)的名稱 |
| Class getSuperClass() | 返回當(dāng)前Class對象的父類的Class對象 |
| Class[] getinterfaces() | 獲取當(dāng)前Class對象的接口 |
| ClassLoader getClassLoader() | 返回該類的加載器 |
| Constructor[] getConstructors() | 返回一個(gè)包含某些Constructor對象的數(shù)組 |
| Method getMethod(String name, Class... T) | 返回一個(gè)Method對象,此對象的形參類型為paramType |
| Field[] getDeclaredFields() | 返回Field對象的一個(gè)數(shù)組 |
?
?
?
?
?
?
?
?
?
?
獲取Class類的實(shí)例
1.若已知具體的類,通過類的class屬性獲取,該方法最為安全可靠,程序性能最高。
Class c = Person.class;
2.已知某個(gè)類的實(shí)例,調(diào)用該實(shí)例的getClass()方法獲取Class對象。
Class c = person.getClass();
3.已知一個(gè)類的全類名,且該類在類路徑下,可通過Class類的靜態(tài)方法forName()獲取,可能拋出ClassNotFoundException。
Class c = Class.forName("xxxx.xx.xx.Person");
4.內(nèi)置基本數(shù)據(jù)類型(的包裝類型)可以直接用類名.Type。
5.還可以利用ClassLoader獲取(后面介紹,用的不多)
public class Test02 {public static void main(String[] args) throws ClassNotFoundException {Person person = new Student();System.out.println("這個(gè)人是" + person.getName());//注意:每個(gè)類只有一個(gè)class,所以前三種獲取的class的hashcode是相同的。//方式一,通過對象獲得Class c1 = person.getClass();System.out.println(c1.hashCode());//方式二,通過forName獲得Class c2 = Class.forName("com.boot.security.server.myUtils.reflectUtils.learn.Student");System.out.println(c2.hashCode());//方式三,通過類名.class獲得Class c3 = Student.class;System.out.println(c3.hashCode());//方式四,基本內(nèi)置類型的包裝類都有一個(gè)Type屬性,只有包裝類才有Class c4 = Integer.TYPE;System.out.println(c4);//獲得父類型Class c5 = c1.getSuperclass();System.out.println(c5);} }class Person{public String name;public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +'}';} }class Student extends Person{public Student(){this.name = "學(xué)生";} }class Teacher extends Person{public Teacher(){this.name = "教師";} }哪些類型可以有Class對象?
class:外部類,成員(成員內(nèi)部類,靜態(tài)內(nèi)部類),局部內(nèi)部類,匿名內(nèi)部類。
interface:接口。
[]:數(shù)組
enum:枚舉
annotation:注解@interface
primitive type:基本數(shù)據(jù)類型
void
import java.lang.annotation.ElementType;//所有類型的class public class Test04 {public static void main(String[] args) {Class c1 = Object.class;//類Class c2 = Comparable.class;//接口Class c3 = String[].class;//一維數(shù)組Class c4 = int[][].class;//二維數(shù)組Class c5 = Override.class;//注解Class c6 = ElementType.class;//枚舉Class c7 = Integer.class;//包裝數(shù)據(jù)類型Class c8 = void.class;//voidClass c9 = Class.class;//ClassClass c10 = int.class;//基本數(shù)據(jù)類型System.out.println(c1);System.out.println(c2);System.out.println(c3);System.out.println(c4);System.out.println(c5);System.out.println(c6);System.out.println(c7);System.out.println(c8);System.out.println(c9);System.out.println(c10);//只要元素類型與維度一樣,就是同一個(gè)class,跟數(shù)組長度無關(guān)。int[] a = new int[10];int[] b = new int[100];System.out.println(a.getClass().hashCode());System.out.println(b.getClass().hashCode());} }類加載內(nèi)存分析
Java內(nèi)存:
? ? 堆:存放new的對象和數(shù)組;可以被所有的線程共享,不會(huì)存放別的對象引用。
? ? 棧:存放基本變量類型(會(huì)包含這個(gè)基本類型的具體數(shù)值);引用對象的變量(會(huì)存放這個(gè)引用在堆里面的具體地址)。
? ? 方法區(qū):可以被所有的線程共享;包含了所有的class和static變量。
類加載的過程
? ? 當(dāng)程序主動(dòng)使用某個(gè)類時(shí),如果該類還未被加載到內(nèi)存中,則系統(tǒng)會(huì)通過如下三個(gè)步驟來對該類進(jìn)行初始化。
類的加載與ClassLoader的理解
加載:將class文件字節(jié)碼內(nèi)容加載到內(nèi)存中,并將這些靜態(tài)數(shù)據(jù)轉(zhuǎn)換成方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu),然后生成一個(gè)代表這個(gè)類的java.lang.Class對象。 鏈接:將Java類的二進(jìn)制代碼合并到JVM的運(yùn)行狀態(tài)之中的過程。 驗(yàn)證:確保加載的類信息符合JVM規(guī)范,沒有安全方面的問題。 準(zhǔn)備:正式為類變量(static分配內(nèi)存并設(shè)置類變量默認(rèn)初始值的階段,這些內(nèi)存都將在方法區(qū)中進(jìn)行分配) 解析:虛擬機(jī)常量池內(nèi)的符號引用(常量名)替換為直接引用(地址)的過程。 初始化: 執(zhí)行類構(gòu)造器<clinit>()方法的過程。類構(gòu)造器<clinit>()方法是由編譯器自動(dòng)收集類中所有類變量的賦值動(dòng)作和靜態(tài)代碼塊中的語句合并產(chǎn)生的。(類構(gòu)造器是構(gòu)造類信息的,不是構(gòu)造該類對象的構(gòu)造器)。 當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還沒有進(jìn)行初始化,則需要先觸發(fā)其父類的初始化。 虛擬機(jī)會(huì)保證一個(gè)類的<clinit>()方法在多線程環(huán)境中被正確加鎖和同步。 public class Test05 {public static void main(String[] args) {//打印://A的靜態(tài)代碼塊//A的無參構(gòu)造器//100A a = new A();System.out.println(A.m);/*1.加載到內(nèi)存,會(huì)產(chǎn)生一個(gè)類對應(yīng)Class對象。2.鏈接,鏈接結(jié)束后m=03.初始化<clinit>(){System.out.println("A類靜態(tài)代碼快初始化")m = 300;m = 100;//這里合并}4.最后m = 100;*/} }class A{static{System.out.println("A的靜態(tài)代碼塊");m = 200;//這里賦值沒用了}static int m = 100;public A(){System.out.println("A的無參構(gòu)造器");} }什么時(shí)候會(huì)發(fā)生類初始化?
類的主動(dòng)引用(一定會(huì)發(fā)生類的初始化)
? ? 當(dāng)虛擬機(jī)啟動(dòng),先初始化main方法所在的類。
? ? new一個(gè)類的對象。
? ? 調(diào)用類的靜態(tài)成員(除了final變量)和靜態(tài)方法。
? ? 使用java.lang.reflect包的方法對類進(jìn)行反射調(diào)用。
? ? 當(dāng)初始化一個(gè)類,如果其父類沒有被初始化,則先會(huì)初始化它的父類。
類的被動(dòng)引用(不會(huì)發(fā)生類的初始化)
? ? 當(dāng)訪問一個(gè)靜態(tài)域時(shí),只有真正聲明這個(gè)域的類才會(huì)被初始化。如:當(dāng)通過子類引用父類的靜態(tài)變量,不會(huì)導(dǎo)致子類初始化。
? ? 通過數(shù)組定義類引用,不會(huì)觸發(fā)此類的初始化。
? ? 引用常量不會(huì)觸發(fā)此類的初始化(常量在鏈接階段就存入調(diào)用類的常量池中了)
//類什么時(shí)候會(huì)初始化 public class HelloWorld {static {System.out.println("Main被加載");}public static void main(String [] args) {//主動(dòng)引用,1.加載main,2.加載父類,3.加載子類Son son = new Son();//反射也會(huì)產(chǎn)生主動(dòng)引用,1.加載main,2.加載父類,3.加載子類Class c = Class.forName("com.my.re.Son");//不會(huì)產(chǎn)生類的引用的方法,1.加載main,2.加載父類,3.打印2(這里不加載子類)System.out.pringln(Son.b);Son[] arr = new Son[2];//這個(gè)既不會(huì)初始化子類,也不會(huì)初始化父類System.out.pringln(Son.M);//這個(gè)既不會(huì)初始化子類,也不會(huì)初始化父類} }class Father{static int b = 2;static{System.out.println("父類被加載");} }class Son extends Father{static {System.out.println("子類被加載");m = 200;}static int m = 100;static final int M = 1; }類如何加載的
? ? 類加載器將class文件字節(jié)碼內(nèi)容加載到內(nèi)存中,并將這些靜態(tài)數(shù)據(jù)轉(zhuǎn)換成方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu),然后在堆中生成一個(gè)代表這個(gè)類的java.lang.Class對象,作為方法區(qū)中類數(shù)據(jù)的訪問入口。
? ? 類緩存:標(biāo)準(zhǔn)的JavaSE類加載器可以按要求查找類,但一旦某個(gè)類被加載到類加載器中,它將維持加載(緩存)一段時(shí)間。不過JVM垃圾回收機(jī)制可以回收這些Class對象。
類加載器的作用
? ? 類加載器作用是用來把類(class)裝載進(jìn)內(nèi)存的。JVM規(guī)范定義了如下類型的類加載器。
? ? 引導(dǎo)類加載器(Bootstap Classloader):用C++編寫的,是JVM自帶的類加載器,負(fù)責(zé)Java平臺(tái)核心庫,用來裝載核心類庫。該類加載器無法直接獲取。
? ? 擴(kuò)展類加載器(Extension Classloader):負(fù)責(zé)jre/lib/ext目錄下的jar包或-D java.ext.dirs指定目錄下的jar包裝入工作庫。
? ? 系統(tǒng)類加載器(System Classloader/Application Classloader):負(fù)責(zé)java =classpath或-D java.class.path所指定的目錄下的類與jar包裝入工作,是最常用的加載器。
public class Test05 {public static void main(String[] args) throws ClassNotFoundException {//獲取系統(tǒng)類加載器ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();System.out.println(systemClassLoader);//獲取系統(tǒng)類加載器的父類加載器——擴(kuò)展類加載器ClassLoader parent = systemClassLoader.getParent();System.out.println(parent);//獲取擴(kuò)展類加載器的父類加載器——根加載器(C/C++)ClassLoader parent1 = parent.getParent();System.out.println(parent1);//測試當(dāng)前類是哪個(gè)加載器加載的ClassLoader classLoader = Class.forName("com.myUtils.reflectUtils.learn.Test05").getClassLoader();System.out.println(classLoader);//測試jdk內(nèi)置的類是哪個(gè)加載器加載的classLoader = Class.forName("java.lang.Object").getClassLoader();System.out.println(classLoader);//獲取系統(tǒng)類加載器加載的路徑System.out.println(System.getProperty("java.class.path"));} }雙親委派機(jī)制
?? ?當(dāng)某個(gè)類加載器需要加載某個(gè).class文件時(shí),它首先把這個(gè)任務(wù)委托給他的上級類加載器,遞歸這個(gè)操作,如果上級的類加載器沒有加載,自己才會(huì)去加載這個(gè)類。
? ? 加載優(yōu)先順序:引導(dǎo)類加載器(Bootstap Classloader)=>擴(kuò)展類加載器(Extension Classloader)=>系統(tǒng)類加載器(System Classloader/Application Classloader)。
總結(jié)
以上是生活随笔為你收集整理的还在对java类、类的加载一知半解?这篇文章相信会解决你80%的困惑的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux如何登陆oracle?如何停止
- 下一篇: Java反射,从0开始