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

註解與反射學習筆記

註解Annotation

1、什麼是註解

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

  • Annotation的作用

    • 不是程式本身 , 可以對程式作出解釋.(這一點和註釋(comment)沒什麼區別)
    • 可以被其他程式(比如:編譯器等)讀取.
  • Annotation的格式

    • 註解是以"@註釋名"在程式碼中存在的
      還可以新增一些引數值 , 例如:@SuppressWarnings(value="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={"unchecked","deprecation"})
    • 等等 .....
package com.annotation;
//測試內建註解
import java.util.ArrayList;
import java.util.List;
//所有類預設繼承Object類
public class Test1 extends Object {
    //@Override 表示方法重寫
    //--> 檢視JDK幫助文件
    //--> 測試名字不同產生的效果
    @Override
    public String toString() {
    return super.toString();
        }
    //方法過時了, 不建議使用 , 可能存在問題 , 並不是不能使用!
    //--> 檢視JDK幫助文件
    @Deprecated
    public static void stop(){
    System.out.println("測試 @Deprecated");
    }
    //@SuppressWarnings 抑制警告 , 可以傳引數
    //--> 檢視JDK幫助文件
    //檢視原始碼:發現 引數型別 和 引數名稱 , 並不是方法!
    @SuppressWarnings("all")
    public void sw(){
    List list = new ArrayList();
    }
    public static void main(String[] args) {
    stop();
    }
}

3、元註解

  • 元註解的作用就是負責註解其他註解 , Java定義了4個標準meta-annotation型別,他們被用來提供對其他annotation型別作說明 .
  • 這些型別和它們所支援的類在java.lang.annotation包中可以找到 .( @Target,@Retention ,@Documented , @Inherited )
    • @Target : 用於描述註解的使用範圍(即:被描述的註解可以用在什麼地方)
    • @Retention : 表示需要在什麼級別儲存該註釋資訊 , 用於描述註解的生命週期
      • (SOURCE < CLASS < RUNTIME)
    • @Document:說明該註解將被包含在javadoc中
    • @Inherited:說明子類可以繼承父類中的該註解
package com.annotation;
import java.lang.annotation.*;
//測試元註解
public class Test2 {
    @MyAnnotation
    public void test(){
    }
}
//定義一個註解
@Target(value = {ElementType.METHOD,ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@Inherited
@Documented
@interface MyAnnotation{
    //測試作用域 , 瞭解@Retention的概念
}

4、自定義註解

  • 使用 @interface自定義註解時 , 自動繼承了java.lang.annotation.Annotation介面
  • 分析 :
    • @ interface用來宣告一個註解 , 格式 : public @ interface 註解名 { 定義內容 }
    • 其中的每一個方法實際上是聲明瞭一個配置引數.
    • 方法的名稱就是引數的名稱.
    • 返回值型別就是引數的型別 ( 返回值只能是基本型別,Class , String , enum ).
    • 可以通過default來宣告引數的預設值
    • 如果只有一個引數成員 , 一般引數名為value
    • 註解元素必須要有值 , 我們定義註解元素時 , 經常使用空字串,0作為預設值 .
package com.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//測試自定義註解
public class Test3 {
    //顯示定義值 / 不顯示值就是預設值
    @MyAnnotation2(age = 18,name = "秦疆",id = 001,schools = {"西工大"})
    public void test() {
    }
    //只有一個引數, 預設名字一般是value.使用可省略不寫
    @MyAnnotation3("aaa")
    public void test2(){
    }
    }
    @Target(value = {ElementType.METHOD})
    @Retention(value = RetentionPolicy.RUNTIME)
    @interface MyAnnotation2{
        //引數型別 , 引數名
        String name() default "";
        int age() default 0;
        int id() default -1; //String indexOf("abc") -1 , 不存在,找不到
        String[] schools() default {"西部開源","狂神說Java"};
    }
    @Target(value = {ElementType.METHOD})
    @Retention(value = RetentionPolicy.RUNTIME)
    @interface MyAnnotation3{
         // 引數型別 引數名稱
        String value();
    }
}

5、反射讀取註解

反射機制Reflection

1、靜態 VS 動態語言

  • 動態語言

    • 是一類在執行時可以改變其結構的語言:例如新的函式、物件、甚至程式碼可以被引進,已有的函式可以被刪除或是其他結構上的變化。通俗點說就是在執行時程式碼可以根據某些條件改變自身結構。

    • 主要動態語言:Object-C、C#、JavaScript、PHP、Python等。

      //體現動態語言的程式碼
      function test() {
          var x = "var a=3;var b=5;alert(a+b)";
          eval(x);
      }
      
  • 靜態語言

    • 與動態語言相對應的,執行時結構不可變的語言就是靜態語言。如Java、C、C++。
    • Java不是動態語言,但Java可以稱之為“準動態語言”。即Java有一定的動態性,我們可以利用反射機制獲得類似動態語言的特性。Java的動態性讓程式設計的時候更加靈活!

2、Java Reflection

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

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

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

Java反射機制提供的功能

  • 在執行時判斷任意一個物件所屬的類
  • 在執行時構造任意一個類的物件
  • 在執行時判斷任意一個類所具有的成員變數和方法
  • 在執行時獲取泛型資訊
  • 在執行時呼叫任意一個物件的成員變數和方法
  • 在執行時處理註解
  • 生成動態代理
    ......

Java反射優點和缺點
優點:可以實現動態建立物件和編譯,體現出很大的靈活性 !
缺點:對效能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什麼並且它滿
足我們的要求。這類操作總是慢於 直接執行相同的操作。

3、反射相關的主要API

java.lang.Class : 代表一個類
java.lang.reflect.Method : 代表類的方法
java.lang.reflect.Field : 代表類的成員變數
java.lang.reflect.Constructor : 代表類的構造器
.......

public class Test {
    public static void main(String[] args) {
        try {
            //通過反射獲取類的Class
            //--->檢視JDK幫助文件
            Class<?> c1 = Class.forName("User");
            //一個類被載入後 , 類的整個結構資訊會被放到對應的Class物件中
            System.out.println(c1);
            //一個類只對應一個Class物件
            Class<?> c2 = Class.forName("User");
            System.out.println(c1.hashCode());
            System.out.println(c2.hashCode());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
//1. 建立一個實體類
class User{
    private int id;
    private int age;
    private String name;
    public User() {
    }
    public User(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 "User{" +
            "id=" + id +
            ", age=" + age +
            ", name=" + name +
            '}';
    }
}

4、Class類

在Object類中定義了以下的方法,此方法將被所有子類繼承

public final  Class getClass();

以上的方法返回值的型別是一個Class類,此類是Java反射的源頭,實際上所謂反射從程式的執行結果來看也很好理解,即:可以通過物件反射求出類的名稱。

物件照鏡子後可以得到的資訊:某個類的屬性、方法和構造器、某個類到底實現了哪些介面。對於每個類而言,JRE 都為其保留一個不變的 Class 型別的物件。一個 Class 物件包含了特定某個結構(class/interface/enum/annotation/primitive type/void/[])的有關資訊。

  • Class 本身也是一個類
  • Class 物件只能由系統建立物件
  • 一個載入的類在 JVM 中只會有一個Class例項
  • 一個Class物件對應的是一個載入到JVM中的一個.class檔案
  • 每個類的例項都會記得自己是由哪個 Class 例項所生成
  • 通過Class可以完整地得到一個類中的所有被載入的結構
  • Class類是Reflection的根源,針對任何你想動態載入、執行的類,唯有先獲得相應的Class物件

Class類的常用方法

獲取Class類的例項
a)若已知具體的類,通過類的class屬性獲取,該方法最為安全可靠,程式效能最高。

Class clazz = Person.class;

b)已知某個類的例項,呼叫該例項的getClass()方法獲取Class物件

Class clazz = person.getClass();

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

Class clazz = Class.forName("demo01.Student");

d)內建基本資料型別可以直接用類名.Type
e)還可以利用ClassLoader我們之後講解


//測試各種型別獲得Class物件的方式
public class Test1 {
    public static void main(String[] args) throws ClassNotFoundException {
        Person person = new Student();
        System.out.println("這個人是:"+person.name);
        //獲得class辦法一:通過物件獲得
        Class clazz1 = person.getClass();
        //獲得class辦法二:通過字串獲得(包名+類名)
        Class clazz2 = Class.forName("Student");
        //獲得class辦法三:通過類的靜態成員class獲得
        Class clazz3 = Person.class;
        //獲得class辦法四:只針對內建的基本資料型別
        Class clazz4 = Integer.TYPE;
        //獲得父類型別
        Class clazz5 = clazz2.getSuperclass();
        System.out.println(clazz1);
        System.out.println(clazz2);
        System.out.println(clazz3);
        System.out.println(clazz4);
        System.out.println(clazz5);
    }
}
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 = "學生";
    }
}
class Teacher extends Person{
    public Teacher(){
        this.name = "老師";
    }
}

哪些型別可以有Class物件?

  • class:外部類,成員(成員內部類,靜態內部類),區域性內部類,匿名內部類。
  • interface:介面
  • []:陣列
  • enum:列舉
  • annotation:註解@interface
  • primitive type:基本資料型別
  • void

5、Java記憶體分析

類載入過程

類的載入與ClassLoader的理解

  • 載入:
    將class檔案位元組碼內容載入到記憶體中,並將這些靜態資料轉換成方法區的執行時資料結構,然後生成一個代表這個類java.lang.Class物件.
  • 連結:將Java類的二進位制程式碼合併到JVM的執行狀態之中的過程。
    • 驗證:確保載入的類資訊符合JVM規範,沒有安全方面的問題
    • 準備:正式為類變數(static)分配記憶體並設定類變數預設初始值的階段,這些記憶體都將在方法區中進行分配。
    • 解析:虛擬機器常量池內的符號引用(常量名)替換為直接引用(地址)的過程。
  • 初始化:
    • 執行類構造器()方法的過程。類構造器()方法是由編譯期自動收集類中所有類變數的賦值動作和靜態程式碼塊中的語句合併產生的。(類構造器是構造類資訊的,不是構造該類物件的構造器)。
    • 當初始化一個類的時候,如果發現其父類還沒有進行初始化,則需要先觸發其父類的初始化。
    • 虛擬機器會保證一個類的()方法在多執行緒環境中被正確加鎖和同步。

什麼時候會發生類初始化?

  • 類的主動引用(一定會發生類的初始化)
    • 當虛擬機器啟動,先初始化main方法所在的類new一個類的物件
    • 呼叫類的靜態成員(除了final常量)和靜態方法
    • 使用java.lang.reflect包的方法對類進行反射呼叫
    • 當初始化一個類,如果其父類沒有被初始化,則先會初始化它的父類
  • 類的被動引用(不會發生類的初始化)
    • 當訪問一個靜態域時,只有真正宣告這個域的類才會被初始化。如:當通過子類引用父類的靜態變數,不會導致子類初始化
    • 通過陣列定義類引用,不會觸發此類的初始化
    • 引用常量不會觸發此類的初始化(常量在連結階段就存入呼叫類的常量池中了)

類載入器的作用

  • 類載入的作用:將class檔案位元組碼內容載入到記憶體中,並將這些靜態資料轉換成方法區的執行時資料結構,然後在堆中生成一個代表這個類的java.lang.Class物件,作為方法區中類資料的訪問入口。
  • 類快取:標準的JavaSE類載入器可以按要求查詢類,但一旦某個類被載入到類載入器中,它將維持載入(快取)一段時間。不過JVM垃圾回收機制可以回收這些Class物件
  • 類載入器作用是用來把類(class)裝載進記憶體的。JVM 規範定義瞭如下型別的類的載入器