1. 程式人生 > 其它 >關於註解與反射學習筆記

關於註解與反射學習筆記

技術標籤:反射java

該筆記在B站觀看狂神說JavaCore所記錄

註解:(Java.Annotation)(註釋:給人看;註解:註釋+解釋,解釋由程式解釋,給程式看)

什麼是註解?

Annotation是從JDK5.0開始引入的新技術.

Annotation的作用:

​ 不是程式本身, 可以對程式作出解釋.(這一點和註釋(comment)沒什麼區別).

可以被其他程式(比如:編譯器等)讀取.

Annotation的格式:

​ 註解是以"@註釋名"在程式碼中存在的, 還可以新增一些引數值, 例如:@SuppressWarnings(Values=“unchecked”)

Annotation在哪裡使用?

​ 可以附加在package, class, method, field等上面, 相當於給他們添加了額外的輔助資訊, 我們可以通過反射機制 程式設計實現對這些元資料的訪問.

內建註解:(例如)

@SuppressWarnings(“all”) 消除所有警告

@overside 重寫註解

元註解:

元註解的作用:

負責註解其他註解,java定義了4個標準的meta-annotation型別,他們被用來提供對其他annotation型別作說明.

例如:

@Target:用於描述註解的使用範圍(即:被描述的註解可以用在什麼地方).

//定義一個註解
@Target(value =
{ElementType.METHOD, ElementType.TYPE}) @interface MyAnnotation{}

@Retention:表示需要在什麼級別儲存該註釋資訊,用於描述註解的生命週期(SOURCE< CLASS< RUNTIME).

@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation{}

@Document:說明該註解將被包含在javadoc中.

@Documented
@interface MyAnnotation{}

@Inherited:說明子類可以繼承父類中的該註解.

@Inherited
@interface MyAnnotation{}
自定義註解:

使用 @interface 自定義註解時,自動繼承了 java.lang.annotation.Annotation 介面

分析:

​ @interface 用來宣告一個註解,格式:public @interface 註解名{ 定義內容 }

​ 其中的每一個方法實際上是聲明瞭一個配置引數.

​ 方法名稱就是引數名稱.

​ 返回值型別就是引數的型別(返回值只能是基本型別,Class, String, Enum).

​ 可以通過 default 來宣告引數的預設值.

​ 如果只有一個引數成員,一般引數名為 value.

​ 註解元素必須要有值,我們定義註解元素時,經常使用空字串,0 作為預設值.

@MyAnnotation3("value")
public class Test03 {    
    //註解可以顯示賦值,如果沒有預設值,我們必須給註解賦值    
    @MyAnnotation2(name = "唐宇翔", age = 100)    
    public void b(){    }
}
@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation2{    
    // 註解的引數 : 引數型別 + 引數名();    
    String name() default "";    
    int age() default 1;    
    int id() default -1;//如果預設值為-1 ,代表不存在    
    String[] schools() default {"Redis", "Java"};
}
@Target(value = {ElementType.METHOD, ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation3{//預設value,可以省略不寫value = ,  直接寫引數值    
    String value();
}

反射機制(Java.Reflection)

Reflection(反射)是Java被視為動態語言的關鍵,反射機制允許程式在執行期藉助於Reflection API取得任何類的內部資訊,並能直接操作任意物件的內部屬性及方法。

	Class c = Class.forName("java.lang.String")

載入完類之後,在堆記憶體的方法區中就產生了一個Class型別的物件(一個類只有一個Class物件),這個物件就包含了完整的類的結構資訊。我們可以通過這個物件看到類的結構。這個物件就像一面鏡子,透過這個鏡子看到類的結構,所以我們形象的稱之為: 反射。

反射的優點:

可以實現動態建立物件和編譯,體現出很大的靈活性.

反射的缺點:

對效能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什麼並且它能滿足我們的要求。這類操作總是慢於直接執行相同的操作。

獲取Class類的例項

  1. 若已知具體的類,通過類的class屬性獲取,該方法最可靠,程式效能最高。

    Class clazz = Person.class;
    
  2. 已知某個類的例項,呼叫該例項的 getClass() 方法獲取Class物件

    Class clazz = Person.getClass();
    
  3. 已知一個類的全類名,且該類在類路徑下,可通過Class類的靜態方法 forName() 獲取,可能丟擲ClassNotFoundException.

    Class clazz = Class.forName("demo01.Student");
    
  4. 內建基本資料型別可以直接用 類名.Type

  5. 可以利用ClassLoader

哪些型別可以有Class物件?

class:外部類,成員(成員內部類,靜態內部類),區域性內部類,匿名內部類。

interface:介面

[]:陣列

enum:列舉

annotation:註解@interface

primitive type:基本資料型別

void

類的載入與ClassLoader的理解:

載入:將class檔案位元組碼內容載入到記憶體中,並將這些靜態資料轉換成方法區的執行時資料結構,然後生成一個 代表這個類的 java.lang.Class物件.

連結:將Java類的二進位制程式碼合併到 JVM 的執行狀態之中的過程。

​ 驗證:確保載入的類資訊符合 JVM 規範,沒有安全方面的問題

​ 準備:正式為類變數(static)分配記憶體並設定類變數預設初始值的階段,這些記憶體都將在方法區中進行分配。

​ 解析:虛擬機器常量池內的符號引用(常量名)替換為直接引用(地址)的過程。

初始化:

​ 執行類構造器()方法的過程。類構造器()方法是由編譯期自動收集類中所有類變數的賦值動 作和靜態程式碼塊中的語句合併產生的。(類構造器是構造類的資訊,不是構造該類物件的構造器)。

​ 當初始化一個類的時候,如果發現其父類還沒有進行初始化,則需要先觸發其父類的初始化。

​ 虛擬機器會保證一個類的()方法在多執行緒環境中被正確加鎖和同步。

什麼時候會發生類初始化?
類的主動引用(一定會發生類的初始化)

​ 當虛擬機器啟動,先初始化main方法所在類

​ new 一個類的物件

​ 呼叫類的靜態成員(除了final常量)和靜態方法

​ 使用 java.lang.reflect 包的方法對類進行反射呼叫

​ 當初始化一個類,如果其父類沒有被初始化,則會先初始化它的父類

類的被動引用(不會發生類的初始化)

​ 當訪問一個靜態域時,只有真正宣告這個域的類才會被初始化。如:當通過子類引用父類的靜態變數,不會導 致子類初始化

​ 通過陣列定義類引用,不會觸發此類的初始化

​ 引用常量不會觸發此類的初始化(常量在連結階段就存入呼叫類的常量池中了)

反射例項方法:
User user = new User();
Class clazz = user.getClass();
Field[] fields = clazz.getDeclaredFields();//獲得物件類所有欄位
fields = clazz.getFields();//獲得物件類全部public欄位
Method[] methods = clazz.getMethods();//獲得物件類及物件類父類全部public 方法
methods = clazz.getDeclaredMethods();//獲得物件類全部方法
Method method = clazz.getMethod("method", int.class);//v1:方法名,v2:形參型別
Constructor[] constructor = clazz.getConstructors();//獲得物件類public方法
constructor = clazz.getDeclaredConstructors();//獲得物件類全部方法
Constructor c= clazz.getConstructor(int.class);//獲得指定型別構造方法
建立類的物件:呼叫Class物件的newInstance()方法

​ 類必須有一個無參構造器

​ 類的構造器的訪問許可權需要足夠

如果沒有無參構造器,只要在操作的時候明確呼叫類中的構造器,並將引數傳遞進去就可以例項化操作

​ 通過Class類的getDeclaredConstructor(Class … parameterTypes)取得本類的指定形參型別的構造器

​ 向構造器的形參中傳遞一個物件陣列進去,裡面包含了構造器中所需的各個引數

​ 通過Constructor例項化物件

//通過反射動態的建立物件
Class cl = Class.forName("com.Mainpackage.test.pojo.User");
//構造一個物件,本質是呼叫了類的無參構造器
User user = (User) cl.newInstance();
System.out.println(user);
//通過構造器建立物件
Constructor constructor = cl.getDeclaredConstructor(String.class, int.class, String.class);
User user2 = (User)constructor.newInstance("mingzi", 22, "123");
System.out.println(user2);
呼叫指定的方法:

通過反射,呼叫類中的方法,通過Method類完成。

​ 通過Class類的getMethod(String name, Class…parameterTypes)方法取得一個Method物件,並設定此方法操作時所需要的引數型別。

​ 之後使用Object invoke(Object obj, Object[] args)進行呼叫,並向方法中傳遞要設定的obj物件的引數資訊。

​ Object invoke(Object obj, Objec … args)

​ Object對應原方法的返回值,若原方法無返回值,此時返回null

​ 若原方法為靜態方法,此時形參Object obj可為null

​ 若原方法形參列表為空,則Object[] args 為null

​ 若原方法宣告為private,則需要在呼叫此invoke()方法前,顯式呼叫方法物件的setAccessible(true) 方法,講課訪問private 的方法。

//通過反射動態的建立物件
Class cl = Class.forName("com.Mainpackage.test.pojo.User");
User user3 = (User) cl.newInstance();//通過反射呼叫普通方法
Method method = cl.getMethod("setName", String.class);//通過反射獲取一個方法
method.invoke(user3, "唐宇翔");//invoke:啟用, v1:物件的例項,v2:方法的實參
System.out.println(user3.getName());
User user4 = (User) cl.newInstance();//通過反射操作屬性
Field field = cl.getDeclaredField("name");
field.setAccessible(true);//不能直接操作私有屬性,需要關閉程式的安全監測,屬性或方法的setAccessible(true).
field.set(user4, "tyx");
System.out.println(user4.getName());
setAccessile

Method 和 Field、Constructor物件都有setAccessible()方法。

setAccessible的作用是啟動和禁用訪問安全檢查的開關。

引數值為true則指示反射的物件在使用時應該取消Java語言訪問檢查

​ 提高反射的效率。如果程式碼中必須用反射,該句程式碼需被頻繁呼叫,請設定為true。

​ 使得原本無法訪問的私有成員也可以訪問。

引數值為false則指示反射的物件應該實施Java語言訪問檢查

效能對比分析:

建立物件呼叫成員方法:最快

通過反射呼叫類的成員方法:最慢

關閉檢查,通過反射呼叫類的成員方法:比上者快

總結:如果反射頻繁呼叫方法,建議關閉安全檢查。

ORM

什麼是ORM?

Object relationship Mapping --> 物件關係對映