作者:輕狂書生FS
https://blog.csdn.net/LookForDream_/
我們在實(shí)際的工作中,項(xiàng)目使用的框架中會經(jīng)常遇到注解和反射。但是我們大部分同學(xué)都僅僅限于知道這是注解和反射,卻不太了解它們底層的原理實(shí)現(xiàn)。對這部分知識了解有點(diǎn)淺薄和片面,所以這邊文章將會深入理解什么是注解和反射。讓我們達(dá)到“知其然,知其所以然”的目的。
Annotation是JDK5.0開始引入的新技術(shù)
Annotation的作用
不是程序本身,可以對程序做出解釋(這一點(diǎn)和注釋沒有什么區(qū)別)
可以被其它程序,比如編譯器讀取
Annotation的格式
注解以 @注釋名
在代碼中存在的,還可以添加一些參數(shù)值
例如:@SuppressWarnings(value = 'unchecked')
Annotation在那里使用?
可以附加在package、class、method、field等上面,相當(dāng)于給他們添加了額外的輔助信息
通過反射機(jī)制變成實(shí)現(xiàn)對這些元數(shù)據(jù)的控制
@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類型,他們被用來提供對其它annotation類型作說明。
這些類型和它們所支持的類在 java.lang.annotation
包可以找到 @Target
、@Retention
、@Documented
、@Inherited
@Target:用于描述注解的使用范圍,即:被描述的注解可以在什么地方使用
@Retention:表示需要什么保存該注釋信息,用于描述注解的生命周期
級別范圍: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í),自動繼承了 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();
}
動態(tài)語言是一類在運(yùn)行時(shí)可以改變其結(jié)構(gòu)的語言:例如新的函數(shù),對象,甚至代碼可以被引進(jìn),已有的函數(shù)可以被刪除或是其它結(jié)構(gòu)上的變化。通俗點(diǎn)說就是在運(yùn)行時(shí)代碼可以根據(jù)某些條件改變自身結(jié)構(gòu)
主要的動態(tài)語言有:Object-c、C#、JavaScript、PHP、Python等
與動態(tài)語言相比,運(yùn)行時(shí)結(jié)構(gòu)不可變的語言就是靜態(tài)語言。例如Java、C、C++
Java不是動態(tài)語言,但是Java可以稱為“準(zhǔn)動態(tài)語言”。即Java有一定的動態(tài)性,我們可以利用反射機(jī)制來獲取類似于動態(tài)語言的 特性,Java的動態(tài)性讓編程的時(shí)候更加靈活。
搜索Java知音公眾號,回復(fù)“后端面試”,送你一份Java面試題寶典.pdf
Java Reflection:Java反射是Java被視為動態(tài)語言的關(guān)鍵,反射機(jī)制運(yùn)行程序在執(zhí)行期借助于Reflection API 去的任何類內(nèi)部的信息,并能直接操作任意對象的內(nèi)部屬性及方法。
Class c = Class.forName('java.lang.String')
在加載完類后,在堆內(nèi)存的方法區(qū)就產(chǎn)生了一個(gè)Class類型的對象(一個(gè)類只有一個(gè)Class對象),這個(gè)對象就包含了完整的類的結(jié)構(gòu)信息,我們可以通過這個(gè)對象看到類的結(jié)構(gòu),這個(gè)對象就像一面鏡子,透過這個(gè)鏡子看到類的結(jié)構(gòu),所以我們形象的稱之為:反射
tip:反射可以獲取到private修飾的成員變量和方法
在運(yùn)行時(shí)判斷任意一個(gè)對象所屬類
在運(yùn)行時(shí)構(gòu)造任意一個(gè)類的對象
在運(yùn)行時(shí)判斷任意一個(gè)類所具有的成員變量和方法
在運(yùn)行時(shí)獲取泛型信息
在運(yùn)行時(shí)調(diào)用任意一個(gè)對象的成員變量和方法
在運(yùn)行時(shí)候處理注解
生成動態(tài)代理
…
優(yōu)點(diǎn):可以實(shí)現(xiàn)動態(tài)創(chuàng)建對象和編譯,體現(xiàn)出很大的靈活性
缺點(diǎn):對性能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什么并且它滿足我們的要求,這類操作總是慢于直接執(zhí)行相同的操作。也就是說new創(chuàng)建和對象,比反射性能更高
java.lang.Class:代表一個(gè)類
java.lang.reflect.Method:代表類的方法
java.lang.reflect.Field:代表類的成員變量
java.lang.reflect.Constructor:代表類的構(gòu)造器
…
我們下面通過Class.forName來獲取一個(gè)實(shí)例對象
/**
* 反射Demo
*
* @author: 輕狂書生FS
*/
public class ReflectionDemo {
public static void main(String[] args) throws ClassNotFoundException {
// 通過反射獲取類的Class對象
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è)對象,我們輸出對應(yīng)對象的hashcode碼,會發(fā)現(xiàn)
1173230247
1173230247
1173230247
它們的hashcode碼是一樣的,這就說明了:
一個(gè)類在內(nèi)存中只有一個(gè)Class對象
一個(gè)類被加載后,類的整體結(jié)構(gòu)都會被封裝在Class對象中
在Object類中定義了以下的方法,此方法將被所有子類繼承
public final Class getClass()
以上方法的返回值的類型是一個(gè)Class類,此類是Java反射的源頭,實(shí)際上所謂反射從程序的運(yùn)行結(jié)果來看也很好理解,即:可以通過對象反射求出類的名稱。
也就是說,我們通過對象來獲取到它的Class,相當(dāng)于逆過程
通過對照鏡子我們可以得到的信息:某個(gè)類的屬性,方法和構(gòu)造器,某個(gè)類到底實(shí)現(xiàn)了那些接口。對于每個(gè)類而言,JRE都為其保留一個(gè)不變的Class類型對象,一個(gè)CLass對象包含了特定某個(gè)結(jié)構(gòu)的有關(guān)信息
Class本身也是一個(gè)類
Class對象只能由系統(tǒng)建立對象
一個(gè)加載的類在JVM中只會有一個(gè)Class實(shí)例
一個(gè)Class對象對應(yīng)的是一個(gè)加載到JVM中的一個(gè).class文件
每個(gè)類的實(shí)例都會記得自己是由哪個(gè)Class實(shí)例所生成
通過Class可以完整地得到一個(gè)類中所有被加載的結(jié)構(gòu)
Class類是Reflection的根源,針對任何你想動態(tài)加載、運(yùn)行的類、唯有先獲得相應(yīng)的Class對象
ClassforName(String name):返回指定類name的Class對象
newInstance():調(diào)用缺省構(gòu)造函數(shù),返回Class對象的一個(gè)實(shí)例
getName():返回此Class對象所表示的實(shí)體(類,接口,數(shù)組或void)的名稱
getSuperClass():返回當(dāng)前Class對象的父類Class對象
getinterfaces():返回當(dāng)前對象的接口
getClassLoader():返回該類的類加載器
getConstructors():返回一個(gè)包含某些Constructor對象的數(shù)組
getMethod(String name, Class… T):返回一個(gè)Method對象,此對象的形參類型為paramsType
getDeclaredFields():返回Field對象的一個(gè)數(shù)組
若已知具體的類,通過類的class屬性獲取,該方法最為安全可靠,程序性能最高
Class clazz = Person.class;
已知某個(gè)類的實(shí)例,調(diào)用該實(shí)例的getClass()方法獲取Class對象
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:通過對象獲得
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:通過類名獲?。ㄗ顬楦咝В?/span>
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:外部類,成員(成員內(nèi)部類,靜態(tài)內(nèi)部類),局部內(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對象
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)存分為以下三部分
堆
存放new的對象和數(shù)組
可以被所有的線程共享,不會存放別的對象引用
棧
存放基本變量(會包含這個(gè)基本類型的具體數(shù)值)
引用對象的變量(會存放這個(gè)引用在對堆里面的具體地址)
方法區(qū)
可以被所有線程共享
包含了所有的class和static變量
當(dāng)程序主動使用某個(gè)類時(shí),如果該類還未被加載到內(nèi)存中,則系統(tǒng)會通過如下三個(gè)步驟對該類進(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
對象。
鏈接:將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ī)常量池的符號引用(常量名)替換為直接引用(地址)的過程
初始化:
執(zhí)行類構(gòu)造器方法的過程,類構(gòu)造器 方法是由編譯期自動收集類中所有類變量的賦值動作和靜態(tài)代碼塊中的語句合并產(chǎn)生的。(類構(gòu)造器是構(gòu)造類信息的,不是構(gòu)造該類對象的構(gòu)造器)
當(dāng)初始化一個(gè)類的時(shí)候,如果發(fā)現(xiàn)其父類還沒有初始化完成,則需要先觸發(fā)其父類的初始化
虛擬機(jī)會保證一個(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)存,會產(chǎn)生一個(gè)類對應(yīng)Class對象
鏈接,鏈接結(jié)束 m = 0
初始化:
<clinit>() {
syso('A類靜態(tài)方法')
m = 300;
m = 100;
}
當(dāng)虛擬機(jī)啟動,先初始化main方法所有在類
new 一個(gè)類的對象
調(diào)用類的靜態(tài)成員(除了 final常量)和靜態(tài)方法
使用 java.lang.reflect包的方法對類進(jìn)行反射調(diào)用
當(dāng)初始化一個(gè)類,如果其父類沒有被初始化,則會先初始化它的父類
當(dāng)訪問一個(gè)靜態(tài)域時(shí),只有真正的申明這個(gè)域的類才會被初始化,如:當(dāng)通過子類引用父類的靜態(tài)變量,不會導(dǎo)致子類初始化
通過數(shù)組定義類引用,不會觸發(fā)此類的初始化
引用常量不會觸發(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
對象,作為方法區(qū)中類數(shù)據(jù)的訪問入口。
類緩存:標(biāo)準(zhǔn)的JavaSE類加載器可以按要求查找類,但是一旦某個(gè)類被加載到類加載器中,它將維持加載(緩存)一段時(shí)間。不過JVM垃圾回收機(jī)制可以回收這些Class對象
類加載器作用是用來把類(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);
// 測試JDK內(nèi)置類是誰加載的
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知音公眾號,回復(fù)“后端面試”,送你一份Java面試題寶典.pdf
通過反射能夠獲取運(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 < fieldAll.length; 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);
}
}
如果我們想定義一個(gè):java.lang.string 包,我們會發(fā)現(xiàn)無法創(chuàng)建
因?yàn)轭愒诩虞d的時(shí)候,會逐級往上
也就是說當(dāng)前的系統(tǒng)加載器,不會馬上的創(chuàng)建該類,而是將該類委派給 擴(kuò)展類加載器,擴(kuò)展類加載器在委派為根加載器,然后引導(dǎo)類加載器去看這個(gè)類在不在能訪問的路徑下,發(fā)現(xiàn) sring包已經(jīng)存在了,所以就無法進(jìn)行,也就是我們無法使用自己自定義的string類,而是使用初始化的stirng類
當(dāng)一個(gè)類收到了類加載請求,他首先不會嘗試自己去加載這個(gè)類,而是把這個(gè)請求委派給父類去完成,每一個(gè)層次類加載器都是如此,因此所有的加載請求都應(yīng)該傳送到啟動類加載其中,只有當(dāng)父類加載器反饋?zhàn)约簾o法完成這個(gè)請求的時(shí)候(在它的加載路徑下沒有找到所需加載的Class),子類加載器才會嘗試自己去加載。
采用雙親委派的一個(gè)好處是比如加載位于rt.jar 包中的類java.lang.Object,不管是哪個(gè)加載器加載這個(gè)類,最終都是委托給頂層的啟動類加載器進(jìn)行加載,這樣就保證了使用不同的類加載器最終得到的都是同樣一個(gè)Object 對象
創(chuàng)建類的對象:通過調(diào)用Class對象的newInstance()方法
類必須有一個(gè)無參數(shù)的構(gòu)造器
類的構(gòu)造器的權(quán)限需要足夠
如果沒有無參構(gòu)造器就不能創(chuàng)建對象?
只要在操作的時(shí)候明確的調(diào)用類中的構(gòu)造器,并將參數(shù)傳遞進(jìn)去之后,才可以實(shí)例化操作。
步驟如下:
通過Class類的getDeclaredConstructor(Class … parameterTypes)取得本類的指定形參類型的構(gòu)造器
向構(gòu)造器的形參中,傳遞一個(gè)對象數(shù)組進(jìn)去,里面包含了構(gòu)造器中所需的各個(gè)參數(shù)
通過Constructor實(shí)例化對象
通過反射,調(diào)用類中的方法,通過Method類完成。
通過Class類的getMethod方法取得一個(gè)Method對象,并設(shè)置此方法操作是所需要的參數(shù)類型
之后使用Object invoke進(jìn)行調(diào)用,并向方法中傳遞要設(shè)置的obj對象的參數(shù)信息
Object invoke(Object obj, Object … args)
Object對應(yīng)原方法的返回值,若原方法無返回值,此時(shí)返回null
若原方法為靜態(tài)方法,此時(shí)形參Object 可以為null
若原方法形參列表為空,則Object[] args 為 null
若原方法聲明private,則需要在調(diào)用此invoke() 方法前,顯示調(diào)用方法對象的setAccessible(true)方法,將可訪問private的方法
Method和Field、Constructor對象都有setAccessible()方法
setAccessible作用是啟動和禁用訪問安全檢查的開關(guān)
參數(shù)值為true則指示反射對象再使用時(shí)應(yīng)該取消Java語言訪問檢查
提高反射效率,如果代碼中必須使用反射,而這句代碼需要頻繁被嗲用,那么設(shè)置成true
使得原本無法訪問的私有成員也可以訪問
參數(shù)值為false則指示反射的對象應(yīng)該實(shí)行Java語言訪問檢查
完整代碼:
/**
* 通過反射獲取對象
*
* @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è)對象,newInstance調(diào)用的是無參構(gòu)造器,如果沒有無參構(gòu)造器的話,本方法會出錯(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方法,傳入對象 和 參數(shù)
setName.invoke(user3, '小白');
System.out.println(user3);
System.out.println('============');
Field age = clazz.getDeclaredField('age');
// 關(guān)閉權(quán)限檢測,這樣才能直接修改字段,因?yàn)?nbsp;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}
下面我們編寫代碼來具體試一試,使用反射的時(shí)候和不適用反射,在執(zhí)行方法時(shí)的性能對比
/**
* 反射性能
*
* @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í)例化對象后,調(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<String, User> map, List<User> list) {
System.out.println('test01');
}
public Map<String, User> 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<java.lang.String, com.moxi.interview.study.annotation.User>
class java.lang.String
class com.moxi.interview.study.annotation.User
# java.util.List<com.moxi.interview.study.annotation.User>
class com.moxi.interview.study.annotation.User
###################
class java.lang.String
class com.moxi.interview.study.annotation.User
通過反射能夠獲取到 類、方法、字段。。。等上的注解
getAnnotation
getAnnotations
ORM即為:Object relationship Mapping,對象關(guān)系映射
類和表結(jié)構(gòu)對應(yīng)
屬性和字段對應(yīng)
對象和記錄對應(yīng)
下面使用代碼,模擬ORM框架的簡單使用
/**
* 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對象
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());
}
}