一.注解
1. 注解入門
-
Annotation是從JDK5.0開始引入的新技術
-
Annotation的作用:
- 不是程式本身,可以對程式做出解釋(這一點和注釋(comment)沒什么區別)
- 可以被其他程式(比如:編譯器等)讀取
-
Annotation的格式:
-
注解是以“@注解名”在代碼中存在的,還可以添加一些引數值,例如:
@SuppressWarnings(value="https://www.cnblogs.com/ZengYoujiu/p/unchecked")
-
-
Annotation在哪里使用?
- 可以附加在package,class,method,field等上面,相當于給他們添加了額外的輔助資訊,我們可以通過反射機制編程實作對這些元資料的訪問
2. 內置注解
-
@Override
? 定義在java.lang.Override中,此注解只適用于修辭方法,表示一個方法宣告打算重寫超類中的另一個方法宣告,
-
@Deprecated
? 定義在java.lang.Deprecated中,此注解可以用于修辭方法,屬性,類,表示不鼓勵程式員使用這樣的元素,通常是因為它很危隙訓者存在更好的選擇,
-
@SuppressWarnings
定義在java.lang.SuppressWarnings中,用來抑制編譯時的警告資訊,
與前兩個注解有所不同,你需要添加一個引數才能正確使用,這些引數都是已經定義好了的,我們選擇性地使用就好了,例如:
@SuppressWarnings("all") @SuppressWarnings("unchecked") @SuppressWarnings(value=https://www.cnblogs.com/ZengYoujiu/p/{"unchecked","deprecation"})//什么是注解 public class Test01 extends Object{ //@Override 重寫的注解 @Override public String toString(){ return super.toString(); } //@SuppressWarnings("all") 抑制所有警告資訊 @SuppressWarnings("all") public static void main(String[] args) { test(); } //@Deprecated 不推薦程式員使用,但是可以使用,或者存在更好的方式 @Deprecated public static void test(){ System.out.println("Deprecated"); } }
3. 元注解
-
元注解的作用就是負責注解其他注解,Java定義了4個標準的meta-annotation型別,它們被用來提供對其他annotation型別作說明,
-
這些型別和它們所支持的類在java.lang.annotation包中可以找到(@Target,@Retention,@Documented,@Inherited)
-
@Target:用于描述注解的使用范圍(即被描述的注解可以用在什么地方)
-
@Retention:表示需要在什么級別保存該注釋資訊,用于描述注解的生命周期
(SOURCE < CLASS < RUNTIME)
-
@Document:說明該注解將包含在javaDoc中
-
@Inherited:說明子類可以繼承父類中的該注解
-
package ReflectDemo;
//測驗元注解
import java.lang.annotation.*;
@MyAnnotation()
public class Test02 {
public void test(){
}
}
// 定義一個注解
// Target 表示我們的注解可以用在哪些地方
@Target(value = https://www.cnblogs.com/ZengYoujiu/p/{ElementType.METHOD , ElementType.TYPE})
// Retention 表示我們的注解在什么地方還有效
// runtime > class > sources
@Retention(value = RetentionPolicy.RUNTIME)
// Documented 表示是否將我們的注解生成在JavaDoc中
@Documented
// Inherited 子類可以繼承父類的注解
@Inherited
@interface MyAnnotation{
}
4. 自定義注解
? 使用@interface自定義注解時,自動繼承了java.lang.annotation.Annotation介面,
- @interface 用來宣告一個注解,格式:public @interface 注解名
- 其中的每一個方法實際上是宣告了一個配置引數
- 方法的名稱就是引數的名稱
- 回傳值的型別就是引數的型別(回傳值只能是基本型別,Class,String,enum )
- 可以通過default來宣告引數的默認值
- 如果只有一個引數成員,一般引數名為value
- 注解元素必須要有值,我們定義注解元素時,經常使用空字串、0作為默認值
//自定義注解
public class Test03 {
//注解可以顯式賦值,如果沒有默認值,我們就必須給注解賦值
@MyAnnotation2(age = 18, name = "Jack")
public void test(){}
@MyAnnotation3("John")
public void test2(){}
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
//注解的引數 : 引數型別 + 引數名 ();
String name() default "";
int age();
int id() default -1; //如果默認值為-1,代表不存在
String[] schools() default {"北京大學", "清華大學"};
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation3{
String value();
}
二.反射
1、靜態語言 VS 動態語言
動態語言:
- 是一類在運行時可以改變其結構的語言:例如新的函式、物件、甚至代碼可以被引進,已有的函式可以被洗掉或是其他結構上的變化,通俗點說就是在運行時代碼可以根據某些條件改變自身結構,
- 主要動態語言:Object-C、C#、JavaScript、PHP、Python等,
靜態語言:
- 與動態語言相對應的,運行時結構不可變的語言就是靜態語言,如Java、C、C++,
- Java不是動態語言,但Java可以稱之為“準動態語言”,即Java有一定的動態性,我們可以利用反射機制獲得類似動態語言的特性,Java的動態性讓編程的時候更加靈活,
2、Java Reflection
-
Reflection(反射)是Java被視為動態語言的關鍵,反射機制允許程式在執行期間借助于Reflection API取得任何類的內部資訊,并能直接操作任意物件的內部屬性及方法,
Class c = Class.forName("java.lang.String") -
加載完類之后,在堆記憶體的方法區中就產生了一個Class型別的物件(一個類只有一個Class物件),這個物件就包含了完整的類的結構資訊,我們可以通過這個物件看到類的結構,這個物件就像一面鏡子,透過這個鏡子看到類的結構,所以,我們形象地稱之為:反射,
正常方式: 引入需要的“包類”名稱 --> 通過new實體化 --> 取得實體化物件反射方式: 實體化物件 --> getClass()方法 --> 得到完整的“包類”名稱
3、Java反射機制提供的功能
- 在運行時判斷任意一個物件所屬的類
- 在運行時構造任意一個類的物件
- 在運行時判斷任意一個類所具有的成員變數和方法
- 在運行時獲取泛型資訊
- 在運行時呼叫任意一個物件的成員變數和方法
- 在運行時處理注解
- 生成動態代理
- ......
4、Java反射優點和缺點
優點:
- 可以實作動態創建物件和編譯,體現出很大的靈活性
缺點:
- 對性能有影響,使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什么并且它滿足我們的要求,這類操作總是慢于直接執行相同的操作,
5、反射相關的主要API
- java.lang.Class:代表一個類
- java.lang.reflect.Method:代表類的方法
- java.lang.reflect.Filed:代表類的成員變數
- java.lang.reflect.Constructor:代表類的構造器
- ......
6、 Class類
在Object類中定義了以下的方法,此方法將被所有子類繼承:
public final Class getClass(){};
以上的方法回傳值的型別是一個Class類,此類是Java反射的源頭,實際上所謂的反射從程式的運行結果來看也很好理解,即:可以通過物件反射求出類的名稱,
public class Test01 {
public static void main(String[] args) throws ClassNotFoundException {
//通過反射獲取類的Class物件
Class c1 = Class.forName("ReflectDemo.User");
System.out.println(c1);
Class c2 =Class.forName("ReflectDemo.User");
Class c3 = Class.forName("ReflectDemo.User");
//一個類在記憶體中只有一個Class物件
//一個類被加載后,類的整個結構都會被封裝在Class物件中
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());
}
}
//物體類 pojo
class User {
private int id;
private String name;
public User() {
}
public User(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
? 物件“照鏡子”后可以得到的資訊:某個類的屬性、方法和構造器、某個類到底實作了哪些介面,
? 對于每個類而言,JRE都為其保留一個不變的Class型別的物件,一個Class物件包含了特定某個結構( class / interface / enum / annotation / primitive type / void / [] )的有關資訊,
- Class本身也是一個類
- Class物件只能由系統建立物件
- 一個加載的類在JVM中只會有一個Class實體
- 一個Class物件對應的是一個加載到JVM中的一個.class檔案
- 每個類的實體都會記得自己是由哪個Class實體所生成
- 通過Class可以完整地得到一個類中的所有被加載的結構
- Class類是Reflection的根源,針對任何你想動態加載、運行的類,唯有先獲得相應的Class物件
6.1、Class類的常用方法

6.2、獲取Class類的實體(重要,面試常問)
-
若已知具體的類,通過類的class屬性獲取,該方法最為安全可靠,程式性能最高,
Class clazz = Person.class; -
已知某個類的實體,呼叫該實體的getClass()方法獲取Class物件
Class clazz = person.getClass(); -
已知一個類的全類名,且該類在類路徑下,可通過Class類的靜態方法forName()獲取,可能拋出ClassNotFoundException
Class clazz = Class.forName("demo01.Student"); -
內置基本資料型別可以直接用 類名.Type
-
還可以利用ClassLoader(了解)
public class Test02 {
public static void main(String[] args) throws ClassNotFoundException {
Person person = new Student();
System.out.println("The person is : " + person.name);
//方式一:通過物件獲得
Class c1 = person.getClass();
System.out.println(c1.hashCode());
//方式二:forName獲得
Class c2 = Class.forName("ReflectDemo.Student");
System.out.println(c2.hashCode());
//方式三:通過類名.class獲得
Class<Student> 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{
int id;
String name;
public Person() {
}
public Person(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
class Student extends Person{
public Student(){
this.name = "student";
}
}
class Teacher extends Person{
public Teacher(){
this.name = "teacher";
}
}
哪些型別可以有Class物件?
-
class:外部類,成員(成員內部類,靜態內部類),區域內部類,匿名內部類
-
interface:介面
-
[] :陣列
-
enum:列舉
-
annotation:注解@interface
-
primitive type :基本資料型別
-
void
//所有型別的Class
public class Test03 {
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; //void
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);
//只要元素型別與維度一樣,就是同一個class
int[] a = new int[10] ;
int[] b = new int[100];
System.out.println(a.getClass().hashCode());
System.out.println(b.getClass().hashCode());
}
}
6.3、類的加載和初始化
Java記憶體分配

類的加載程序(了解)
當程式主動使用某個類時,如果該類還未被加載到記憶體中,則系統會通過如下三個步驟來對該類進行初始化:

-
加載:將class檔案位元組碼內容加載到記憶體中,并將這些靜態資料轉換成方法區的運行時資料結構,然后生成一個代表這個類的java.lang.Class物件,
-
鏈接:將Java類的二進制代碼合并到JVM的運行狀態之中的程序,
- 驗證:確保加載的類資訊符合JVM規范,沒有安全方面的問題
- 準備:正式為類變數(static)分配記憶體并設定類變數默認初始值的階段,這些記憶體都將在方法區中進行分配
- 決議:虛擬機常量池內的符號參考(常量名)替換為直接參考(地址)的程序
-
初始化:
-
執行類構造器
()方法的程序,類構造器 ()方法是由編譯期自動收集類中所有類變數的賦值動作和靜態代碼塊中的陳述句合并產生的,(類構造器是構造類資訊的,不是構造該類物件的構造器) -
當初始化一個類的時候,如果發現其父類還沒有進行初始化,則需要先觸發其父類的初始化
-
虛擬機會保證一個類的
()方法在多執行緒環境中被正確加鎖和同步
-
public class Test04 {
public static void main(String[] args) {
A a = new A();
System.out.println(A.m);
}
}
/*
1.加載到記憶體,會產生一個類對應Class物件
2.鏈接,鏈接結束后 m = 0
3.初始化
<clinit>(){
System.out.println("A類靜態代碼塊初始化");
m = 300;
m = 100;
}
最終: m = 100
*/
class A{
static{
System.out.println("A類靜態代碼塊初始化");
m = 300;
}
static int m = 100;
public A(){
System.out.println("A類的無參建構式初始化");
}
}
什么時候會發生類初始化?(重要)
-
類的主動參考(一定會發生類的初始化)
- 當虛擬機啟動,先初始化main()方法所在的類
- new一個類的物件
- 呼叫類的靜態成員(除了final常量)和靜態方法
- 使用java.lang.reflect包的方法對類進行反射呼叫
- 當初始化一個類,如果其父類沒有被初始化,則先會初始化它的父類
-
類的被動參考(不會發生類的初始化)
- 當訪問一個靜態域時,只有真正宣告這個域的類才會被初始化,如:當通過子類參考父類的靜態變數,不會導致子類初始化
- 通過陣列定義類參考,不會觸發此類的初始化
- 參考常量不會觸發此類的初始化(常量在鏈接階段就存入呼叫類的常量池中了)
public class Test05 {
public static void main(String[] args) throws ClassNotFoundException {
//1.主動參考
// Son son = new Son();
//2.反射也會產生主動參考
// Class.forName("ReflectDemo.Son");
//不會產生類的參考的方法
//System.out.println(Son.b);
//Son[] sons = new Son[5];
//System.out.println(Son.M);
}
}
class Father{
static int b = 2;
static {
System.out.println("父類被加載...");
}
}
class Son extends Father{
static {
System.out.println("子類被加載...");
m = 300;
}
static int m = 100;
static final int M = 1;
}
類加載器
- 類加載器的作用:將class檔案位元組碼內容加載到記憶體中,并將這些靜態資料轉換成方法區的運行時資料結構,然后在堆中生成一個代表這個類的java.lang.Class物件,作為方法區中類資料的訪問入口,
- 類快取:標準的JavaSE類加載器可以按要求查找類,但一旦某個類被加載到類加載器中,它將維持加載(快取)一段時間,不過JVM垃圾回識訓制可以回收這些Class物件
類加載器作用是用來把類(class)裝在進記憶體的,JVM規范定義了如下型別的類的加載器:

public class Test06 {
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);
//測驗當前類是哪個加載器加載的
System.out.println(Class.forName("ReflectDemo.Test06").getClassLoader());
//測驗JDK內置的類是誰加載的
System.out.println(Class.forName("java.lang.Object").getClassLoader());
//如何獲得系統類加載器可以加載的路徑
System.out.println(System.getProperty("java.class.path"));
}
}
獲取運行時類的完整結構
? 通過反射可以獲取運行時類的完整結構:Filed、Method、Constructor、Superclass、Interface、Annotation
- 實作的全部介面
- 所繼承的父類
- 全部的構造器
- 全部的方法
- 全部的Field
- 注解
- ......
//通過反射獲得類的資訊
public class Test07 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
Class c1 = Class.forName("ReflectDemo.User");
//獲得類的名字
System.out.println(c1.getName()); //獲得包名 + 類名
System.out.println(c1.getSimpleName()); //獲得類名
//獲得類的屬性
System.out.println("========");
Field[] fields = c1.getFields(); //只能找到public屬性
for (Field field : fields) {
System.out.println(field);
}
fields = c1.getDeclaredFields(); //找到全部的屬性,包括私有屬性
for (Field field : fields) {
System.out.println(field);
}
//獲得類的方法
System.out.println("========");
Method[] methods = c1.getMethods(); //獲得本類及其父類的全部public方法
for (Method method : methods) {
System.out.println("getMethods : " + method);
}
methods = c1.getDeclaredMethods(); //獲得本類的所有方法
for (Method method : methods) {
System.out.println("getDeclaredMethods : " + method);
}
//獲得指定方法 (需要傳遞引數型別,方便有方法多載時定位方法)
Method getName = c1.getMethod("getName",null);
Method setName = c1.getMethod("setName",String.class);
System.out.println(getName);
System.out.println(setName);
//獲得構造器
System.out.println("========");
Constructor[] constructors = c1.getConstructors();
for (Constructor constructor : constructors) {
System.out.println("getConstructors : " + constructor);
}
constructors = c1.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println("getDeclaredConstructors : " + constructor);
}
//獲得指定的構造器
Constructor declaredConstructor = c1.getDeclaredConstructor(int.class,String.class );
System.out.println(declaredConstructor);
}
}
6.4、通過反射創建類的物件
(1)呼叫Class物件的newInstance()方法,要求如下:
- 類必須有一個無引數的構造器;
- 類的構造器的訪問權限需要足夠,
(2)如果沒有無參構造器或者訪問權限不足,則可以通過獲取構造器來創建物件,
通過構造器創建物件,步驟如下:
- 通過Class類的getDeclaredConstructor( Class ... parameterTypes )取得本類的指定形參型別的構造器
- 向構造器的形參中傳遞一個物件陣列進去,里面包含了構造器中所需的各個引數
- 通過Constructor實體化物件
呼叫指定的方法
? 通過反射,呼叫類中的方法,通過Method類完成
-
通過Class類的getMethod(String name, Class ... parameterTypes)方法取得一個Method物件,并設定此方法操作時所需要的引數型別;
-
使用Object invoke(Object obj, Object[] args) 進行呼叫,并向方法中傳遞要設定的obj物件的引數資訊,

Object invoke ( Object obj, Object ... args )
- Object對應原方法的回傳值,若原方法無回傳值,此時回傳null
- 若原方法為靜態方法,此時形參Object obj 可為null
- 若原方法形參串列為空,則Object[] args 為null
- 若原方法宣告為private,則需要在呼叫此invoke方法前,顯示呼叫方法物件的setAccessible(true)方法,將可訪問private的方法,
public class Test08 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
//獲得Class物件
Class c1 = Class.forName("ReflectDemo.User");
//構造一個物件
User user = (User)c1.newInstance();
System.out.println(user);
//通過構造器創建物件
System.out.println("========");
Constructor constructor = c1.getDeclaredConstructor(int.class, String.class);
System.out.println(constructor.newInstance(3, "Lily"));
//通過反射呼叫普通方法
System.out.println("========");
User user2 = (User)c1.newInstance();
//通過反射獲取一個方法
Method setName = c1.getDeclaredMethod("setName", String.class);
setName.invoke(user2,"May");
System.out.println(user2.getName());
//通過反射操作屬性
System.out.println("========");
User user3 = (User) c1.newInstance();
Field name = c1.getDeclaredField("name");
name.setAccessible(true); //不能直接操作私有屬性,我們需要關閉程式的安全監測 屬性或者方法的setAccessible(true)
name.set(user3,"Jack");
System.out.println(user3.getName());
}
}
創建物件的效率
創建物件的效率排序如下:類的實體化方式創建 >> 通過反射的方式創建(setAcccessible(true);) > 通過反射的方式創建,
6.5、反射操作泛型(了解即可)
- Java采用泛型擦除的機制來引入泛型,Java中的泛型僅僅是給編譯器javac使用的,確保資料的安全性和免去強制型別轉換問題,但是,一旦編譯完成,所有和泛型有關的型別全部擦除
- 為了通過反射操作這些型別,Java新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType幾種型別來代表不能被歸一到Class類中的型別但是又和原始型別齊名的型別
- ParameterizedType:表示一種引數化型別,比如 Collection
- GenericArrayType:表示一種元素型別是引數化型別或者型別變數的陣列型別
- TypeVariable:是各種型別變數的公共父介面
- WildcardType:代表一種通配符型別運算式
6.6、反射操作注解(重要,框架基礎)
//反射操作注解
public class Test09 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c1 = Class.forName("ReflectDemo.Stud");
//通過反射獲得注解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//獲得注解的value的值
TableAnnotation annotation = (TableAnnotation) c1.getAnnotation(TableAnnotation.class);
String value = https://www.cnblogs.com/ZengYoujiu/p/annotation.value();
System.out.println(value);
//獲得類指定的注解
Field name = c1.getDeclaredField("name");
FiledAnnotation annotation1 = name.getAnnotation(FiledAnnotation.class);
System.out.println(annotation1.columnName());
System.out.println(annotation1.type());
System.out.println(annotation1.length());
}
}
@TableAnnotation("db_student")
class Stud{
@FiledAnnotation(columnName ="db_id",type = "int", length = 10)
private int id;
@FiledAnnotation(columnName = "db_name",type = "varchar",length = 10)
private String name;
@FiledAnnotation(columnName = "db_age", type = "int" , length = 3)
private int age;
public Stud() {
}
public Stud(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Stud{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
//類名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableAnnotation{
String value();
}
//屬性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FiledAnnotation{
String columnName();
String type();
int length();
}
(補充)6.7、雙親委派機制
什么是雙親委派機制?
雙親委派機制是當類加載器需要加載某一個.class位元組碼檔案時,則首先會把這個任務委托給他的上級類加載器,遞回這個操作,如果上級沒有加載該.class檔案,自己才會去加載這個.class,這是一種任務委派模式,
雙親委派機制原理:
如果一個類加載器收到了要加載某個類的請求,它要做的首要事情不是加載,而是將這個請求委托給父類的加載器去執行;
如果父類加載器還存在其父類加載器,則進一步向上委托,依次遞回,請求最終將到達頂層的啟動類加載器;
如果父類加載器可以完成類加載任務,就成功回傳,倘若父類加載器無法完成此加載任務,子加載器才會嘗試自己去加載;
如果最后沒有任何加載器能加載,則報錯"ClassNotFoundException",
原理圖示:

本博客為博主在學習B站up主狂神關于反射和注解相關課程的學習筆記,博客中大部分內容為課上ppt內容的整理和總結,
在此附上狂神在b站的視頻鏈接(講的是真的好,幫忙宣傳)
關于雙親委派機制的內容,在此附上參考博客地址:雙親委派機制 詳解(手畫詳圖)面試高頻 你值得擁有!!!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/547758.html
標籤:Java
