關(guān)于Java你不知道的那些事之Java注解和反射
點(diǎn)擊上方?好好學(xué)java?,選擇?星標(biāo)?公眾號(hào)
重磅資訊、干貨,第一時(shí)間送達(dá)
重磅資訊、干貨,第一時(shí)間送達(dá)
今日推薦:硬剛一周,3W字總結(jié),一年的經(jīng)驗(yàn)告訴你如何準(zhǔn)備校招!
個(gè)人原創(chuàng)100W+訪問量博客:點(diǎn)擊前往,查看更多
個(gè)人原創(chuàng)100W+訪問量博客:點(diǎn)擊前往,查看更多
作者:輕狂書生FS
https://blog.csdn.net/LookForDream_/
前言
我們?cè)趯?shí)際的工作中,項(xiàng)目使用的框架中會(huì)經(jīng)常遇到注解和反射。但是我們大部分同學(xué)都僅僅限于知道這是注解和反射,卻不太了解它們底層的原理實(shí)現(xiàn)。對(duì)這部分知識(shí)了解有點(diǎn)淺薄和片面,所以這邊文章將會(huì)深入理解什么是注解和反射。讓我們達(dá)到“知其然,知其所以然”的目的。
什么是注解
Annotation是JDK5.0開始引入的新技術(shù)
Annotation的作用
不是程序本身,可以對(duì)程序做出解釋(這一點(diǎn)和注釋沒有什么區(qū)別)
可以被其它程序,比如編譯器讀取
Annotation的格式
注解以?
@注釋名?在代碼中存在的,還可以添加一些參數(shù)值例如:
@SuppressWarnings(value = "unchecked")Annotation在那里使用?
可以附加在package、class、method、field等上面,相當(dāng)于給他們添加了額外的輔助信息
通過反射機(jī)制變成實(shí)現(xiàn)對(duì)這些元數(shù)據(jù)的控制
內(nèi)置注解
@Override:定義在?
java.lang.Override中,此注釋只適用于修飾方法,表示一個(gè)方法聲明打算重寫超類中的另一個(gè)方法聲明@Deprecated:定義在
java.lang.Deprecated中,此注釋可以用于修飾方法,屬性,類,表示不鼓勵(lì)程序員使用這樣的元素,通常是因?yàn)樗芪kU(xiǎn),或者存在更好的選擇@SuppressWarnings:定義在
java.lang.SuppressWarnings中,用來抑制編譯時(shí)的警告信息,與前面的兩個(gè)注釋不同,你需要額外添加一個(gè)參數(shù)才能正確使用,這些參數(shù)都是已經(jīng)定義好了的,我們選擇性的使用就好了。@SuppressWarnings(“all”)
@SuppressWarnings(“unchecked”)
@SuppressWarnings(value={“unchecked”, “deprecation”})
…
元注解
元注解的作用就是負(fù)責(zé)注解其它注解,Java定義了4個(gè)標(biāo)準(zhǔn)的meta-annotation類型,他們被用來提供對(duì)其它annotation類型作說明。
這些類型和它們所支持的類在?java.lang.annotation包可以找到?@Target?、@Retention、@Documented、@Inherited
@Target:用于描述注解的使用范圍,即:被描述的注解可以在什么地方使用
@Retention:表示需要什么保存該注釋信息,用于描述注解的生命周期
級(jí)別范圍:Source < Class < Runtime
@Document:說明該注解被包含在java doc中
@Inherited:說明子類可以集成父類中的注解
示例
/**
?*?元注解
?*
?*?@author:?輕狂書生FS
?*/
@MyAnnotation
public?class?MateAnnotationDemo?{
}
/**
?*?定義一個(gè)注解
?*/
@Target(value={ElementType.METHOD,?ElementType.TYPE})??//?target表示我們注解應(yīng)用的范圍,在方法上,和類上有效
@Retention(RetentionPolicy.RUNTIME)???// Retention:表示我們的注解在什么時(shí)候還有效,運(yùn)行時(shí)候有效
@Documented???//?表示說我們的注解是否生成在java?doc中
@Inherited???//?表示子類可以繼承父類的注解
@interface?MyAnnotation?{
}
自定義注解
使用?@interface自定義注解時(shí),自動(dòng)繼承了?java.lang.annotation.Annotation接口
@interface 用來聲明一個(gè)注解,格式:public @interface 注解名 {定義內(nèi)容
其中的每個(gè)方法實(shí)際上是申明了一個(gè)配置參數(shù)
方法的名稱j就是參數(shù)的類型
返回值類型就是參數(shù)的類型(返回值只能是基本數(shù)據(jù)類型,Class,String,enum)
通過default來申明參數(shù)的默認(rèn)值
如果只有一個(gè)參數(shù)成員,一般參數(shù)名為 value
注解元素必須要有值,我們定義元素時(shí),經(jīng)常使用空字符串或者0作為默認(rèn)值
/**
?*?自定義注解
?*
?*?@author:?輕狂書生FS
?*/
public?class?MateAnnotationDemo?{
????//?注解可以顯示賦值,如果沒有默認(rèn)值,我們就必須給注解賦值
????@MyAnnotation(schools?=?{"大學(xué)"})
????public?void?test(){
????}
}
/**
?*?定義一個(gè)注解
?*/
@Target(value={ElementType.METHOD,?ElementType.TYPE})??//?target表示我們注解應(yīng)用的范圍,在方法上,和類上有效
@Retention(RetentionPolicy.RUNTIME)???// Retention:表示我們的注解在什么時(shí)候還有效,運(yùn)行時(shí)候有效
@Documented???//?表示說我們的注解是否生成在java?doc中
@Inherited???//?表示子類可以繼承父類的注解
@interface?MyAnnotation?{
????//?注解的參數(shù):參數(shù)類型?+?參數(shù)名()
????String?name()?default?"";
????int?age()?default?0;
????//?如果默認(rèn)值為-1,代表不存在
????int?id()?default?-1;
????String[]?schools();
}
反射機(jī)制
動(dòng)態(tài)語(yǔ)言與靜態(tài)語(yǔ)言
動(dòng)態(tài)語(yǔ)言
動(dòng)態(tài)語(yǔ)言是一類在運(yùn)行時(shí)可以改變其結(jié)構(gòu)的語(yǔ)言:例如新的函數(shù),對(duì)象,甚至代碼可以被引進(jìn),已有的函數(shù)可以被刪除或是其它結(jié)構(gòu)上的變化。通俗點(diǎn)說就是在運(yùn)行時(shí)代碼可以根據(jù)某些條件改變自身結(jié)構(gòu)
主要的動(dòng)態(tài)語(yǔ)言有:Object-c、C#、JavaScript、PHP、Python等
靜態(tài)語(yǔ)言
與動(dòng)態(tài)語(yǔ)言相比,運(yùn)行時(shí)結(jié)構(gòu)不可變的語(yǔ)言就是靜態(tài)語(yǔ)言。例如Java、C、C++
Java不是動(dòng)態(tài)語(yǔ)言,但是Java可以稱為“準(zhǔn)動(dòng)態(tài)語(yǔ)言”。即Java有一定的動(dòng)態(tài)性,我們可以利用反射機(jī)制來獲取類似于動(dòng)態(tài)語(yǔ)言的 特性,Java的動(dòng)態(tài)性讓編程的時(shí)候更加靈活。
Java反射機(jī)制概述
什么是反射
Java Reflection:Java反射是Java被視為動(dòng)態(tài)語(yǔ)言的關(guān)鍵,反射機(jī)制運(yùn)行程序在執(zhí)行期借助于Reflection API 去的任何類內(nèi)部的信息,并能直接操作任意對(duì)象的內(nèi)部屬性及方法。
Class?c?=?Class.forName("java.lang.String")
在加載完類后,在堆內(nèi)存的方法區(qū)就產(chǎn)生了一個(gè)Class類型的對(duì)象(一個(gè)類只有一個(gè)Class對(duì)象),這個(gè)對(duì)象就包含了完整的類的結(jié)構(gòu)信息,我們可以通過這個(gè)對(duì)象看到類的結(jié)構(gòu),這個(gè)對(duì)象就像一面鏡子,透過這個(gè)鏡子看到類的結(jié)構(gòu),所以我們形象的稱之為:反射

tip:反射可以獲取到private修飾的成員變量和方法
反射的應(yīng)用
在運(yùn)行時(shí)判斷任意一個(gè)對(duì)象所屬類
在運(yùn)行時(shí)構(gòu)造任意一個(gè)類的對(duì)象
在運(yùn)行時(shí)判斷任意一個(gè)類所具有的成員變量和方法
在運(yùn)行時(shí)獲取泛型信息
在運(yùn)行時(shí)調(diào)用任意一個(gè)對(duì)象的成員變量和方法
在運(yùn)行時(shí)候處理注解
生成動(dòng)態(tài)代理
…
Java反射的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):可以實(shí)現(xiàn)動(dòng)態(tài)創(chuàng)建對(duì)象和編譯,體現(xiàn)出很大的靈活性
缺點(diǎn):對(duì)性能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什么并且它滿足我們的要求,這類操作總是慢于直接執(zhí)行相同的操作。也就是說new創(chuàng)建和對(duì)象,比反射性能更高
反射相關(guān)的主要API
java.lang.Class:代表一個(gè)類
java.lang.reflect.Method:代表類的方法
java.lang.reflect.Field:代表類的成員變量
java.lang.reflect.Constructor:代表類的構(gòu)造器
…
理解Class類并獲取Class實(shí)例
Class類
我們下面通過Class.forName來獲取一個(gè)實(shí)例對(duì)象
/**
?*?反射Demo
?*
?*?@author:?輕狂書生FS
?*/
public?class?ReflectionDemo?{
????public?static?void?main(String[]?args)?throws?ClassNotFoundException?{
????????//?通過反射獲取類的Class對(duì)象
????????Class?c1?=?Class.forName("com.moxi.interview.study.annotation.User");
????????Class?c2?=?Class.forName("com.moxi.interview.study.annotation.User");
????????Class?c3?=?Class.forName("com.moxi.interview.study.annotation.User");
????????System.out.println(c1.hashCode());
????????System.out.println(c2.hashCode());
????????System.out.println(c3.hashCode());
????}
}
/**
?*?實(shí)體類:pojo,entity
?*/
class?User?{
????private?String?name;
????private?int?id;
????private?int?age;
????public?User()?{
????}
????public?String?getName()?{
????????return?name;
????}
????public?void?setName(String?name)?{
????????this.name?=?name;
????}
????public?int?getId()?{
????????return?id;
????}
????public?void?setId(int?id)?{
????????this.id?=?id;
????}
????public?int?getAge()?{
????????return?age;
????}
????public?void?setAge(int?age)?{
????????this.age?=?age;
????}
????@Override
????public?String?toString()?{
????????return?"User{"?+
????????????????"name='"?+?name?+?'\''?+
????????????????",?id="?+?id?+
????????????????",?age="?+?age?+
????????????????'}';
????}
}
上面我們通過反射獲取了三個(gè)對(duì)象,我們輸出對(duì)應(yīng)對(duì)象的hashcode碼,會(huì)發(fā)現(xiàn)
1173230247
1173230247
1173230247
它們的hashcode碼是一樣的,這就說明了:
一個(gè)類在內(nèi)存中只有一個(gè)Class對(duì)象
一個(gè)類被加載后,類的整體結(jié)構(gòu)都會(huì)被封裝在Class對(duì)象中
在Object類中定義了以下的方法,此方法將被所有子類繼承
public?final?Class?getClass()
以上方法的返回值的類型是一個(gè)Class類,此類是Java反射的源頭,實(shí)際上所謂反射從程序的運(yùn)行結(jié)果來看也很好理解,即:可以通過對(duì)象反射求出類的名稱。
也就是說,我們通過對(duì)象來獲取到它的Class,相當(dāng)于逆過程
通過對(duì)照鏡子我們可以得到的信息:某個(gè)類的屬性,方法和構(gòu)造器,某個(gè)類到底實(shí)現(xiàn)了那些接口。對(duì)于每個(gè)類而言,JRE都為其保留一個(gè)不變的Class類型對(duì)象,一個(gè)CLass對(duì)象包含了特定某個(gè)結(jié)構(gòu)的有關(guān)信息
Class本身也是一個(gè)類
Class對(duì)象只能由系統(tǒng)建立對(duì)象
一個(gè)加載的類在JVM中只會(huì)有一個(gè)Class實(shí)例
一個(gè)Class對(duì)象對(duì)應(yīng)的是一個(gè)加載到JVM中的一個(gè).class文件
每個(gè)類的實(shí)例都會(huì)記得自己是由哪個(gè)Class實(shí)例所生成
通過Class可以完整地得到一個(gè)類中所有被加載的結(jié)構(gòu)
Class類是Reflection的根源,針對(duì)任何你想動(dòng)態(tài)加載、運(yùn)行的類、唯有先獲得相應(yīng)的Class對(duì)象
Class類常用的方法
ClassforName(String name):返回指定類name的Class對(duì)象
newInstance():調(diào)用缺省構(gòu)造函數(shù),返回Class對(duì)象的一個(gè)實(shí)例
getName():返回此Class對(duì)象所表示的實(shí)體(類,接口,數(shù)組或void)的名稱
getSuperClass():返回當(dāng)前Class對(duì)象的父類Class對(duì)象
getinterfaces():返回當(dāng)前對(duì)象的接口
getClassLoader():返回該類的類加載器
getConstructors():返回一個(gè)包含某些Constructor對(duì)象的數(shù)組
getMethod(String name, Class… T):返回一個(gè)Method對(duì)象,此對(duì)象的形參類型為paramsType
getDeclaredFields():返回Field對(duì)象的一個(gè)數(shù)組
獲取對(duì)象實(shí)例的方法
若已知具體的類,通過類的class屬性獲取,該方法最為安全可靠,程序性能最高
Class clazz = Person.class;
已知某個(gè)類的實(shí)例,調(diào)用該實(shí)例的getClass()方法獲取Class對(duì)象
Class clazz = person.getClass()
已經(jīng)一個(gè)類的全類名,且該類在類路徑下,可以通過Class類的靜態(tài)方法forName()獲取,HIA可能拋出ClassNotFoundException
Class clazz = Class.forName(“demo01.Sutdent”)
內(nèi)置數(shù)據(jù)類型可以直接通過 類名.Type
還可以利用ClassLoader
/**
?*?Class類創(chuàng)建的方式
?*
?*?@author:?輕狂書生FS
?*/
class?Person?{
????public?String?name;
????public?Person()?{
????}
????public?Person(String?name)?{
????????this.name?=?name;
????}
????@Override
????public?String?toString()?{
????????return?"Person{"?+
????????????????"name='"?+?name?+?'\''?+
????????????????'}';
????}
}
class?Student?extends?Person{
????public?Student()?{
????????this.name?=?"學(xué)生";
????}
}
class?Teacher?extends?Person?{
????public?Teacher()?{
????????this.name?=?"老師";
????}
}
public?class?ClassCreateDemo?{
????public?static?void?main(String[]?args)?throws?ClassNotFoundException?{
????????Person?person?=?new?Student();
????????System.out.println("這個(gè)人是:"?+?person.name);
????????//?方式1:通過對(duì)象獲得
????????Class?c1?=?person.getClass();
????????System.out.println("c1:"?+?c1.hashCode());
????????//方式2:通過forName獲得
????????Class?c2?=?Class.forName("com.moxi.interview.study.annotation.Student");
????????System.out.println("c2:"?+?c2.hashCode());
????????//?方式3:通過類名獲取(最為高效)
????????Class?c3?=?Student.class;
????????System.out.println("c3:"?+?c3.hashCode());
????????//?方式4:基本內(nèi)置類型的包裝類,都有一個(gè)Type屬性
????????Class?c4?=?Integer.TYPE;
????????System.out.println(c4.getName());
????????//?方式5:獲取父類類型
????????Class?c5?=?c1.getSuperclass();
????}
}
哪些類型可以有Class對(duì)象
class:外部類,成員(成員內(nèi)部類,靜態(tài)內(nèi)部類),局部?jī)?nèi)部類,匿名內(nèi)部類
interface:接口
[]:數(shù)組
enum:枚舉
annotation:注解@interface
primitive type:基本數(shù)據(jù)類型
void
/**
?*?獲取Class的方式
?*
?*?@author:?輕狂書生FS
?*/
public?class?GetClassDemo?{
????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;?//?void,空數(shù)據(jù)類型
????????Class?c9?=?Class.class;?//?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);
????}
}
最后運(yùn)行結(jié)果為:
class?java.lang.Object
interface?java.lang.Comparable
class?[Ljava.lang.String;
class?[[I
interface?java.lang.Override
class?java.lang.annotation.ElementType
class?java.lang.Integer
void
class?java.lang.Class
同時(shí)需要注意,只要類型和維度一樣,那就是同一個(gè)Class對(duì)象
int?[]?a?=?new?int[10];
int?[]?b?=?new?int[10];
System.out.println(a.getClass().hashCode());
System.out.println(b.getClass().hashCode());
這兩個(gè)的hashcode是一樣的
Java內(nèi)存分析
java內(nèi)存分為以下三部分
堆
存放new的對(duì)象和數(shù)組
可以被所有的線程共享,不會(huì)存放別的對(duì)象引用
棧
存放基本變量(會(huì)包含這個(gè)基本類型的具體數(shù)值)
引用對(duì)象的變量(會(huì)存放這個(gè)引用在對(duì)堆里面的具體地址)
方法區(qū)
可以被所有線程共享
包含了所有的class和static變量
類的加載與ClassLoader的理解
類加載過程
當(dāng)程序主動(dòng)使用某個(gè)類時(shí),如果該類還未被加載到內(nèi)存中,則系統(tǒng)會(huì)通過如下三個(gè)步驟對(duì)該類進(jìn)行初始化:

加載:將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?對(duì)象。鏈接:將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ī)常量池的符號(hào)引用(常量名)替換為直接引用(地址)的過程
初始化:
執(zhí)行類構(gòu)造器方法的過程,類構(gòu)造器 方法是由編譯期自動(dòng)收集類中所有類變量的賦值動(dòng)作和靜態(tài)代碼塊中的語(yǔ)句合并產(chǎn)生的。(類構(gòu)造器是構(gòu)造類信息的,不是構(gòu)造該類對(duì)象的構(gòu)造器)
當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還沒有初始化完成,則需要先觸發(fā)其父類的初始化
虛擬機(jī)會(huì)保證一個(gè)類的方法在多相差環(huán)境中被正確的加鎖和同步
下面一段代碼,分別說明了static代碼塊,以及子類和父類構(gòu)造方法的執(zhí)行流程
/**
?*?類加載流程
?*
?*?@author:?輕狂書生FS
?*/
class?SuperA?{
????static?{
????????System.out.println("父類靜態(tài)代碼塊初始化");
????}
????public?SuperA()?{
????????System.out.println("父類構(gòu)造函數(shù)初始化");
????}
}
class?A?extends?SuperA{
????static?{
????????System.out.println("靜態(tài)代碼塊初始化");
????????m?=?300;
????}
????static?int?m?=?100;
????public?A()?{
????????System.out.println("A類的無參構(gòu)造方法");
????}
}
public?class?ClassLoaderDemo?{
????public?static?void?main(String[]?args)?{
????????A?a?=?new?A();
????????System.out.println(a.m);
????}
}
最后的結(jié)果為:
父類靜態(tài)代碼塊初始化
靜態(tài)代碼塊初始化
父類構(gòu)造函數(shù)初始化
A類的無參構(gòu)造方法
100
說明靜態(tài)代碼塊都是執(zhí)行的,并且父類優(yōu)先
類加載步驟
加載到內(nèi)存,會(huì)產(chǎn)生一個(gè)類對(duì)應(yīng)Class對(duì)象
鏈接,鏈接結(jié)束 m = 0
初始化:
()?{
?syso("A類靜態(tài)方法")
?m?=?300;
?m?=?100;
}

什么時(shí)候發(fā)生類初始化
類的主動(dòng)引用(一定發(fā)生初始化)
當(dāng)虛擬機(jī)啟動(dòng),先初始化main方法所有在類
new 一個(gè)類的對(duì)象
調(diào)用類的靜態(tài)成員(除了 final常量)和靜態(tài)方法
使用 java.lang.reflect包的方法對(duì)類進(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)用類的常量池了)
類加載器的作用
類加載的作用:將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對(duì)象,作為方法區(qū)中類數(shù)據(jù)的訪問入口。類緩存:標(biāo)準(zhǔn)的JavaSE類加載器可以按要求查找類,但是一旦某個(gè)類被加載到類加載器中,它將維持加載(緩存)一段時(shí)間。不過JVM垃圾回收機(jī)制可以回收這些Class對(duì)象

類加載器作用是用來把類(Class)裝載進(jìn)內(nèi)存的,JVM規(guī)范定義了如下類型的類的加載器

代碼如下:
/**
?*?類加載器的種類
?*
?*?@author:?輕狂書生FS
?*?@create:?2020-09-29-11:51
?*/
public?class?ClassLoaderTypeDemo?{
????public?static?void?main(String[]?args)?{
????????//當(dāng)前類是哪個(gè)加載器
????????ClassLoader?loader?=?ClassLoaderTypeDemo.class.getClassLoader();
????????System.out.println(loader);
????????//?獲取系統(tǒng)類加載器
????????ClassLoader?classLoader?=?ClassLoader.getSystemClassLoader();
????????System.out.println(classLoader);
????????//?獲取系統(tǒng)類加載器的父類加載器?->?擴(kuò)展類加載器
????????ClassLoader?parentClassLoader?=?classLoader.getParent();
????????System.out.println(parentClassLoader);
????????//?獲取擴(kuò)展類加載器的父類加載器?->?根加載器(C、C++)
????????ClassLoader?superParentClassLoader?=?parentClassLoader.getParent();
????????System.out.println(superParentClassLoader);
????????//?測(cè)試JDK內(nèi)置類是誰(shuí)加載的
????????ClassLoader?loader2?=?Object.class.getClassLoader();
????????System.out.println(loader2);
????}
}
運(yùn)行結(jié)果:我們發(fā)現(xiàn),根加載器我們無法獲取到
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@45ee12a7
null
null
獲取類加載器能夠加載的路徑
//?如何獲取類加載器可以加載的路徑
System.out.println(System.getProperty("java.class.path"));
最后輸出結(jié)果為:
????????//?如何獲取類加載器可以加載的路徑
????????System.out.println(System.getProperty("java.class.path"));
????????/*
????????E:\Software\JDK1.8\Java\jre\lib\charsets.jar;
????????E:\Software\JDK1.8\Java\jre\lib\deploy.jar;
????????E:\Software\JDK1.8\Java\jre\lib\ext\access-bridge-64.jar;
????????E:\Software\JDK1.8\Java\jre\lib\ext\cldrdata.jar;
????????E:\Software\JDK1.8\Java\jre\lib\ext\dnsns.jar;
????????E:\Software\JDK1.8\Java\jre\lib\ext\jaccess.jar;
????????E:\Software\JDK1.8\Java\jre\lib\ext\jfxrt.jar;
????????E:\Software\JDK1.8\Java\jre\lib\ext\localedata.jar;
????????E:\Software\JDK1.8\Java\jre\lib\ext\nashorn.jar;
????????E:\Software\JDK1.8\Java\jre\lib\ext\sunec.jar;
????????E:\Software\JDK1.8\Java\jre\lib\ext\sunjce_provider.jar;
????????E:\Software\JDK1.8\Java\jre\lib\ext\sunmscapi.jar;
????????E:\Software\JDK1.8\Java\jre\lib\ext\sunpkcs11.jar;
????????E:\Software\JDK1.8\Java\jre\lib\ext\zipfs.jar;
????????E:\Software\JDK1.8\Java\jre\lib\javaws.jar;
????????E:\Software\JDK1.8\Java\jre\lib\jce.jar;
????????E:\Software\JDK1.8\Java\jre\lib\jfr.jar;
????????E:\Software\JDK1.8\Java\jre\lib\jfxswt.jar;
????????E:\Software\JDK1.8\Java\jre\lib\jsse.jar;
????????E:\Software\JDK1.8\Java\jre\lib\management-agent.jar;
????????E:\Software\JDK1.8\Java\jre\lib\plugin.jar;
????????E:\Software\JDK1.8\Java\jre\lib\resources.jar;
????????E:\Software\JDK1.8\Java\jre\lib\rt.jar;
????????C:\Users\Administrator\Desktop\LearningNotes\校招面試\JUC\Code\target\classes;
????????C:\Users\Administrator\.m2\repository\org\projectlombok\lombok\1.18.10\lombok-1.18.10.jar;
????????C:\Users\Administrator\.m2\repository\cglib\cglib\3.3.0\cglib-3.3.0.jar;
????????C:\Users\Administrator\.m2\repository\org\ow2\asm\asm\7.1\asm-7.1.jar;
????????E:\Software\IntelliJ?IDEA\IntelliJ?IDEA?2019.1.2\lib\idea_rt.jar
?????????*/
我們能夠發(fā)現(xiàn),類在加載的時(shí)候,都是有自己的加載區(qū)域的,而不是任何地方的類都能夠被加載。
搜索Java知音公眾號(hào),回復(fù)“后端面試”,送你一份Java面試題寶典.pdf
獲取運(yùn)行時(shí)候類的完整結(jié)構(gòu)
通過反射能夠獲取運(yùn)行時(shí)類的完整結(jié)構(gòu)
實(shí)現(xiàn)的全部接口
所繼承的父類
全部的構(gòu)造器
全部的方法
全部的Field
注解
/**
?*?獲取運(yùn)行時(shí)類信息
?*?@author:?輕狂書生FS
?*/
public?class?GetClassInfo?{
????public?static?void?main(String[]?args)?throws?ClassNotFoundException,?NoSuchFieldException,?NoSuchMethodException?{
????????Class?clazz?=?Class.forName("com.moxi.interview.study.annotation.User");
????????//?獲取類名字
????????System.out.println(clazz.getName());?//?包名?+?類名
????????System.out.println(clazz.getSimpleName());?//?類名
????????//?獲取類屬性
????????System.out.println("================");
????????//?只能找到public屬性
????????Field?[]?fields?=?clazz.getFields();
????????//?找到全部的屬性
????????Field?[]?fieldAll?=?clazz.getDeclaredFields();
????????for?(int?i?=?0;?i?????????????System.out.println(fieldAll[i]);
????????}
????????//?獲取指定屬性的值
????????Field?name?=?clazz.getDeclaredField("name");
????????//?獲取方法
????????Method?[]?methods?=?clazz.getDeclaredMethods();?//?獲取本類和父類的所有public方法
????????Method?[]?methods2?=?clazz.getMethods();?//?獲取本類所有方法
????????//?獲得指定方法
????????Method?method?=?clazz.getDeclaredMethod("getName",?null);
????????//?獲取方法的時(shí)候,可以把參數(shù)也丟進(jìn)去,這樣因?yàn)楸苊夥椒ㄖ剌d,而造成不知道加載那個(gè)方法
????????Method?method2?=?clazz.getDeclaredMethod("setName",?String.class);
????}
}
雙親委派機(jī)制
如果我們想定義一個(gè):java.lang.string 包,我們會(huì)發(fā)現(xiàn)無法創(chuàng)建
因?yàn)轭愒诩虞d的時(shí)候,會(huì)逐級(jí)往上
也就是說當(dāng)前的系統(tǒng)加載器,不會(huì)馬上的創(chuàng)建該類,而是將該類委派給 擴(kuò)展類加載器,擴(kuò)展類加載器在委派為根加載器,然后引導(dǎo)類加載器去看這個(gè)類在不在能訪問的路徑下,發(fā)現(xiàn) sring包已經(jīng)存在了,所以就無法進(jìn)行,也就是我們無法使用自己自定義的string類,而是使用初始化的stirng類
當(dāng)一個(gè)類收到了類加載請(qǐng)求,他首先不會(huì)嘗試自己去加載這個(gè)類,而是把這個(gè)請(qǐng)求委派給父類去完成,每一個(gè)層次類加載器都是如此,因此所有的加載請(qǐng)求都應(yīng)該傳送到啟動(dòng)類加載其中,只有當(dāng)父類加載器反饋?zhàn)约簾o法完成這個(gè)請(qǐng)求的時(shí)候(在它的加載路徑下沒有找到所需加載的Class),子類加載器才會(huì)嘗試自己去加載。
采用雙親委派的一個(gè)好處是比如加載位于rt.jar 包中的類java.lang.Object,不管是哪個(gè)加載器加載這個(gè)類,最終都是委托給頂層的啟動(dòng)類加載器進(jìn)行加載,這樣就保證了使用不同的類加載器最終得到的都是同樣一個(gè)Object 對(duì)象

有了Class對(duì)象,我們能夠做什么?
創(chuàng)建類的對(duì)象:通過調(diào)用Class對(duì)象的newInstance()方法
類必須有一個(gè)無參數(shù)的構(gòu)造器
類的構(gòu)造器的權(quán)限需要足夠
如果沒有無參構(gòu)造器就不能創(chuàng)建對(duì)象?
只要在操作的時(shí)候明確的調(diào)用類中的構(gòu)造器,并將參數(shù)傳遞進(jìn)去之后,才可以實(shí)例化操作。
步驟如下:
通過Class類的getDeclaredConstructor(Class … parameterTypes)取得本類的指定形參類型的構(gòu)造器
向構(gòu)造器的形參中,傳遞一個(gè)對(duì)象數(shù)組進(jìn)去,里面包含了構(gòu)造器中所需的各個(gè)參數(shù)
通過Constructor實(shí)例化對(duì)象
調(diào)用指定方法
通過反射,調(diào)用類中的方法,通過Method類完成。
通過Class類的getMethod方法取得一個(gè)Method對(duì)象,并設(shè)置此方法操作是所需要的參數(shù)類型
之后使用Object invoke進(jìn)行調(diào)用,并向方法中傳遞要設(shè)置的obj對(duì)象的參數(shù)信息
Invoke方法
Object invoke(Object obj, Object … args)
Object對(duì)應(yīng)原方法的返回值,若原方法無返回值,此時(shí)返回null
若原方法為靜態(tài)方法,此時(shí)形參Object 可以為null
若原方法形參列表為空,則Object[] args 為 null
若原方法聲明private,則需要在調(diào)用此invoke() 方法前,顯示調(diào)用方法對(duì)象的setAccessible(true)方法,將可訪問private的方法
setAccessible方法
Method和Field、Constructor對(duì)象都有setAccessible()方法
setAccessible作用是啟動(dòng)和禁用訪問安全檢查的開關(guān)
參數(shù)值為true則指示反射對(duì)象再使用時(shí)應(yīng)該取消Java語(yǔ)言訪問檢查
提高反射效率,如果代碼中必須使用反射,而這句代碼需要頻繁被嗲用,那么設(shè)置成true
使得原本無法訪問的私有成員也可以訪問
參數(shù)值為false則指示反射的對(duì)象應(yīng)該實(shí)行Java語(yǔ)言訪問檢查

完整代碼:
/**
?*?通過反射獲取對(duì)象
?*
?*?@author:?輕狂書生FS
?*/
public?class?GetObjectByReflectionDemo?{
????public?static?void?main(String[]?args)?throws?ClassNotFoundException,?IllegalAccessException,?InstantiationException,?NoSuchMethodException,?InvocationTargetException,?NoSuchFieldException?{
????????//?獲取Class
????????Class?clazz?=?Class.forName("com.moxi.interview.study.annotation.User");
????????//?構(gòu)造一個(gè)對(duì)象,newInstance調(diào)用的是無參構(gòu)造器,如果沒有無參構(gòu)造器的話,本方法會(huì)出錯(cuò)
//????????User?user?=?(User)clazz.newInstance();
????????//?獲取class的有參構(gòu)造器
????????Constructor?constructor?=?clazz.getDeclaredConstructor(String.class,?int.class,?int.class);
????????User?user2?=?(User)?constructor.newInstance("小溪",?10,?10);
????????System.out.println(user2);
????????//?通過反射調(diào)用普通構(gòu)造方法
????????User?user3?=?(User)clazz.newInstance();
????????//?獲取setName?方法
????????Method?setName?=?clazz.getDeclaredMethod("setName",?String.class);
????????//?執(zhí)行setName方法,傳入對(duì)象?和?參數(shù)
????????setName.invoke(user3,?"小白");
????????System.out.println(user3);
????????System.out.println("============");
????????Field?age?=?clazz.getDeclaredField("age");
????????//?關(guān)閉權(quán)限檢測(cè),這樣才能直接修改字段,因?yàn)?set方法不能直接操作私有變量
????????age.setAccessible(true);
????????age.set(user3,?10);
????????System.out.println(user3);
????}
}
運(yùn)行結(jié)果
User{name='小溪',?id=10,?age=10}
User{name='小白',?id=0,?age=0}
============
User{name='小白',?id=0,?age=10}
反射性能對(duì)比
下面我們編寫代碼來具體試一試,使用反射的時(shí)候和不適用反射,在執(zhí)行方法時(shí)的性能對(duì)比
/**
?*?反射性能
?*
?*?@author:?輕狂書生FS
?*/
public?class?ReflectionPerformance?{
????/**
?????*?普通方式調(diào)用
?????*/
????public?static?void?test01()?{
????????User?user?=?new?User();
????????long?startTime?=?System.currentTimeMillis();
????????for?(int?i?=?0;?i?1000000000;?i++)?{
????????????user.getName();
????????}
????????long?endTime?=?System.currentTimeMillis();
????????System.out.println("普通方式執(zhí)行10億次getName的時(shí)間:"?+?(endTime?-?startTime)?+?"?ms");
????}
????/**
?????*?反射方式調(diào)用
?????*/
????public?static?void?test02()?throws?Exception?{
????????Class?clazz?=?Class.forName("com.moxi.interview.study.annotation.User");
????????Method?getName?=?clazz.getDeclaredMethod("getName",?null);
????????User?user?=?(User)?clazz.newInstance();
????????long?startTime?=?System.currentTimeMillis();
????????for?(int?i?=?0;?i?1000000000;?i++)?{
????????????getName.invoke(user,?null);
????????}
????????long?endTime?=?System.currentTimeMillis();
????????System.out.println("反射方式執(zhí)行10億次getName的時(shí)間:"?+?(endTime?-?startTime)?+?"?ms");
????}
????/**
?????*?反射方式調(diào)用,關(guān)閉權(quán)限檢查
?????*/
????public?static?void?test03()?throws?Exception?{
????????Class?clazz?=?Class.forName("com.moxi.interview.study.annotation.User");
????????Method?getName?=?clazz.getDeclaredMethod("getName",?null);
????????User?user?=?(User)?clazz.newInstance();
????????long?startTime?=?System.currentTimeMillis();
????????getName.setAccessible(true);
????????for?(int?i?=?0;?i?1000000000;?i++)?{
????????????getName.invoke(user,?null);
????????}
????????long?endTime?=?System.currentTimeMillis();
????????System.out.println("反射方式執(zhí)行10億次getName的時(shí)間:"?+?(endTime?-?startTime)?+?"?ms");
????}
????public?static?void?main(String[]?args)?throws?Exception?{
????????test01();
????????test02();
????????test03();
????}
}
運(yùn)行結(jié)果:
普通方式執(zhí)行10億次getName的時(shí)間:3?ms
反射方式執(zhí)行10億次getName的時(shí)間:2554?ms
反射方式執(zhí)行10億次getName的時(shí)間:1365?ms
我們上面分別是執(zhí)行了 10億次 getName的方法,從里面可以看出,通過直接實(shí)例化對(duì)象后,調(diào)用getName耗時(shí)最短,同時(shí)關(guān)閉了 權(quán)限檢查后的比不關(guān)閉能提高一倍的性能。
反射操作泛型
Java采用泛型擦除機(jī)制來引入泛型,Java中的泛型僅僅是給編譯器Java才使用的,確保數(shù)據(jù)的安全性和免去強(qiáng)制類型轉(zhuǎn)換的問題,但是一旦編譯完成后,所有的泛型有關(guān)的類型全部被擦除
為了通過反射操作這些類型,Java新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType幾種類型來代表不能被歸一到Class類中的類型但是有何原始類型齊名的類型。
ParameterizedType:表示一種參數(shù)化類型,比如Collection
GenericArrayType:表示一種元素類型是參數(shù)化類型或者類型變量的數(shù)組類型
TypeVariable:是各種類型變量的公共父接口
WildcardType:代表一種通配符類型的表達(dá)式
下面我們通過代碼來獲取方法上的泛型,包括參數(shù)泛型,以及返回值泛型
/**
?*?通過反射獲取泛型
?*
?*?@author:?輕狂書生FS
?*/
public?class?GenericityDemo?{
????public?void?test01(Map?map,?List?list) ?{
????????System.out.println("test01");
????}
????public?Map?test02()? {
????????System.out.println("test02");
????????return?null;
????}
????public?static?void?main(String[]?args)?throws?Exception{
????????Method?method?=?GenericityDemo.class.getMethod("test01",?Map.class,?List.class);
????????//?獲取所有的泛型,也就是參數(shù)泛型
????????Type[]?genericParameterTypes?=?method.getGenericParameterTypes();
????????//?遍歷打印全部泛型
????????for?(Type?genericParameterType?:?genericParameterTypes)?{
????????????System.out.println("?#?"?+genericParameterType);
????????????if(genericParameterType?instanceof?ParameterizedType)?{
????????????????Type[]?actualTypeArguments?=?((ParameterizedType)?genericParameterType).getActualTypeArguments();
????????????????for?(Type?actualTypeArgument?:?actualTypeArguments)?{
????????????????????System.out.println(actualTypeArgument);
????????????????}
????????????}
????????}
????????//?獲取返回值泛型
????????Method?method2?=?GenericityDemo.class.getMethod("test02",?null);
????????Type?returnGenericParameterTypes?=?method2.getGenericReturnType();
????????//?遍歷打印全部泛型
????????if(returnGenericParameterTypes?instanceof?ParameterizedType)?{
????????????Type[]?actualTypeArguments?=?((ParameterizedType)?returnGenericParameterTypes).getActualTypeArguments();
????????????for?(Type?actualTypeArgument?:?actualTypeArguments)?{
????????????????System.out.println(actualTypeArgument);
????????????}
????????}
????}
}
得到的結(jié)果
?#?java.util.Map
class?java.lang.String
class?com.moxi.interview.study.annotation.User
?#?java.util.List
class?com.moxi.interview.study.annotation.User
###################
class?java.lang.String
class?com.moxi.interview.study.annotation.User
反射操作注解
通過反射能夠獲取到 類、方法、字段。。。等上的注解
getAnnotation
getAnnotations
ORM對(duì)象關(guān)系映射
ORM即為:Object relationship Mapping,對(duì)象關(guān)系映射
類和表結(jié)構(gòu)對(duì)應(yīng)
屬性和字段對(duì)應(yīng)
對(duì)象和記錄對(duì)應(yīng)

下面使用代碼,模擬ORM框架的簡(jiǎn)單使用
/**
?*?ORMDemo
?*
?*?@author:?輕狂書生FS
?*/
@TableKuang("db_student")
class?Student2?{
????@FieldKuang(columnName?=?"db_id",?type="int",?length?=?10)
????private?int?id;
????@FieldKuang(columnName?=?"db_age",?type="int",?length?=?10)
????private?int?age;
????@FieldKuang(columnName?=?"db_name",?type="varchar",?length?=?10)
????private?String?name;
????public?Student2()?{
????}
????public?Student2(int?id,?int?age,?String?name)?{
????????this.id?=?id;
????????this.age?=?age;
????????this.name?=?name;
????}
????public?int?getId()?{
????????return?id;
????}
????public?void?setId(int?id)?{
????????this.id?=?id;
????}
????public?int?getAge()?{
????????return?age;
????}
????public?void?setAge(int?age)?{
????????this.age?=?age;
????}
????public?String?getName()?{
????????return?name;
????}
????public?void?setName(String?name)?{
????????this.name?=?name;
????}
????@Override
????public?String?toString()?{
????????return?"Student2{"?+
????????????????"id="?+?id?+
????????????????",?age="?+?age?+
????????????????",?name='"?+?name?+?'\''?+
????????????????'}';
????}
}
/**
?*?自定義注解:類名的注解
?*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface?TableKuang?{
????String?value();
}
/**
?*?自定義注解:屬性的注解
?*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface?FieldKuang?{
????String?columnName();
????String?type();
????int?length()?default?0;
}
public?class?ORMDemo?{
????public?static?void?main(String[]?args)?throws?Exception{
????????//?獲取Student?的?Class對(duì)象
????????Class?c1?=?Class.forName("com.moxi.interview.study.annotation.Student2");
????????//?通過反射,獲取到全部注解
????????Annotation?[]?annotations?=?c1.getAnnotations();
????????for?(Annotation?annotation?:?annotations)?{
????????????System.out.println(annotation);
????????}
????????//?獲取注解的value值
????????TableKuang?tableKuang?=?(TableKuang)c1.getAnnotation(TableKuang.class);
????????String?value?=?tableKuang.value();
????????System.out.println(value);
????????//?獲得類指定的注解
????????Field?f?=?c1.getDeclaredField("name");
????????FieldKuang?fieldKuang?=?f.getAnnotation(FieldKuang.class);
????????System.out.println(fieldKuang.columnName());
????????System.out.println(fieldKuang.type());
????????System.out.println(fieldKuang.length());
????}
}
商城小程序演示圖
商城管理后端演示圖
源碼地址獲取方法,老規(guī)矩啦!
識(shí)別下方二維碼,關(guān)注后回復(fù)【A107】
即可獲取下載鏈接
