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