天天看點

【Java筆記】(十一):注解與反射什麼是注解内置注解元注解自定義注解反射機制什麼是反射反射提供的功能反射初體驗Class類擷取Class的幾種方式哪些類型可以有Class對象主動引用與被動引用反射擷取泛型資訊反射擷取注解資訊

什麼是注解

注解與反射是所有架構的底層,例如Mybatis、Spring等等,注解(Annotation)是給程式看的,注釋(Comment)是給人看的,注解是Java基礎中最簡單的一章,不用感覺很難。

注解的作用

注解由JDK5.0引入,它不是程式本身,但是可以對程式做出解釋,可以被其他程式(例如:編譯器)讀取,注解不是必須的,但有時候因為程式需要是以要寫

注解的格式

@注解名

的形式存在,也可以添加一些參數值,例如:

@SuppressWarnings(value="unchecked")

注解在哪裡使用

内置注解

  • @Override重寫
  • @Deprecated方法廢棄
  • @SuppressWarnings(value=“all”)鎮壓警告資訊

元注解

  • @Target表示注解可以用在什麼地方
  • @Retention表示需要在什麼級别儲存該注釋資訊,用于描述注解生命周期,SOURCE<CLASS<RUNTIME(常用)
  • @Documented說明該注解将被包含在javadoc中
  • @Inherited說明子類可以繼承父類中的注解
import java.lang.annotation.*;

//測試元注解
public class Test {
    @MyAnnotation
    public void test() {
    }
}

//定義一個注解,這裡METHOD是放在方法上的注解,TYPE可以放在類上
//@Target(value = ElementType.METHOD)
@Target(value = {ElementType.METHOD,ElementType.TYPE})
//注解運作時有效
@Retention(value = RetentionPolicy.RUNTIME)
//将注解生成在javadoc中
@Documented
//子類可以繼承父類的注解
@Inherited
@interface MyAnnotation{

}
           

自定義注解

使用

@interface

自定義注解的時候,自動繼承了

java.lang.annotation.Annotation

接口

  • @interface用來聲明一個注解,格式: public @interface 注解名{定義内容}
  • 其中的每一個方法實際上是聲明了—個配置參數
  • 方法的名稱就是參數的名稱.
  • 傳回值類型就是參數的類型(傳回值隻能是基本類型,Class,String,enum)
  • 可以通過default來聲明參數的預設值
  • 如果隻有一個參數成員,一般參數名為va|ue
  • 注解元素必須要有值,我們定義注解元素時,經常使用空字元串,0作為預設值
import java.lang.annotation.*;

public class Test {
    //注解可以顯示指派,如果沒有預設值則必須指派
    @MyAnnotation(name = "hello")
    public void test() {
    }
}

@Target(value = {ElementType.METHOD,ElementType.TYPE})
@Retention(value = RetentionPolicy.RUNTIME)
@interface MyAnnotation{
    //注解的參數:參數類型+參數名+()
    String name() default "";
    int age() default 0;
    int id() default -1;//如果預設值為-1則表示不存在,和indexof異曲同工
}
           

反射機制

反射讓java變成了動态語言

動态語言

是一類在運作時可以改變其結構的語言:例如新的函數、對象、甚至代碼可以被引進,已有的函數可以被删除或是其他結構上的變化。通俗點說就是在運作時代碼可以根據某些條件改變自身結構。

主要動态語言: Object-C、C#、 JavaScript、PHP、 Python等

靜态語言

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

什麼是反射

Reflection(反射)是Java被視為動态語言的關鍵,反射機制允許程式在執行期借助于 Reflection ap取得任何類的内部資訊,并能直接操作任意對象的内部屬性及方法。

加載完類之後,在堆內存的方法區中就産生了一個Class類型的對象(一個類隻有一個Cass對象),這個對象就包含了完整的類的結構資訊。我們可以通過這個對象看到類的結構。這個對象就像一面鏡子,透過這個鏡子看到類的結構,是以,我們形象的稱之為:反射。反射會引起類的主動引用,即類似

new

了個對象

【Java筆記】(十一):注解與反射什麼是注解内置注解元注解自定義注解反射機制什麼是反射反射提供的功能反射初體驗Class類擷取Class的幾種方式哪些類型可以有Class對象主動引用與被動引用反射擷取泛型資訊反射擷取注解資訊

優點:

可以實作動态建立對象和編譯,展現出很大的靈活性

缺點:

對性能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什麼并且它滿足我們的要求。這類操作總是

慢于

直接執行相同的操作,比new出來慢了幾十倍。

反射提供的功能

  • 在運作時判斷任意一個對象所屬的類
  • 在運作時構造任意一個類的對象
  • 在運作時判斷任意一個類所具有的成員變量和方法
  • 在運作時擷取泛型資訊
  • 在運作時調用任意一個對象的成員變量和方法
  • 在運作時處理注解
  • 生成動态代理
  • 等等

反射初體驗

public class Test {
    public static void main(String[] args) throws ClassNotFoundException {
        Class c1 = Class.forName("User");//類的包路徑
        System.out.println(c1);

        //一個類在記憶體中隻有一個class對象
        //一個類被加載後,類的整個結構都會被封裝在Class對象中
        Class c2 = Class.forName("User");
        Class c3 = Class.forName("User");
        Class c4 = Class.forName("User");
        System.out.println(c2.hashCode());
        System.out.println(c3.hashCode());
        System.out.println(c4.hashCode());
    }

}

class User{
    String name;
    int age;
    int id;
}
           

Class類

在Object類中定義了以下方法:

以上的方法傳回值的類型是一個C|ass類,此類是Java反射的源頭,實際上所謂反射從程式的運作結果來看也很好了解,即:可以通過對象反射求出類的名稱

【Java筆記】(十一):注解與反射什麼是注解内置注解元注解自定義注解反射機制什麼是反射反射提供的功能反射初體驗Class類擷取Class的幾種方式哪些類型可以有Class對象主動引用與被動引用反射擷取泛型資訊反射擷取注解資訊

常用的方法:

【Java筆記】(十一):注解與反射什麼是注解内置注解元注解自定義注解反射機制什麼是反射反射提供的功能反射初體驗Class類擷取Class的幾種方式哪些類型可以有Class對象主動引用與被動引用反射擷取泛型資訊反射擷取注解資訊

擷取Class的幾種方式

public class Test {
    public static void main(String[] args) throws ClassNotFoundException {
        Person person = new Student();
        System.out.println("這個人是"+person.name);
        //方式一獲得
        Class c1 = person.getClass();
        System.out.println(c1.hashCode());
        //方式二
        Class c2 = Class.forName("Student");
        System.out.println(c2.hashCode());
        //方式三
        Class c3 = Student.class;
        System.out.println(c3.hashCode());
        //方式四,基本内置類型的包裝類都有一個TYPE屬性
        Class c4 = Integer.TYPE;
        System.out.println(c4.hashCode());
        //獲得父類類型
        Class c5 = c1.getSuperclass();
        String name = c5.getName();
        System.out.println(c5);
        //以後還可以用ClassLoader
    }
}
class Person{
    public String name;
}
class Student extends Person{
    Student() {
        this.name = "學生";
    }
}
           

哪些類型可以有Class對象

  • class:外部類,成員(成員内部類,靜态内部類),局部内部類,匿名内部類。
  • interface:接口
  • []:數組
  • enum:枚舉
  • annotation:注解@ interface
  • primitive type:基本資料類型
  • void

主動引用與被動引用

主動引用

java類的初始化階段,虛拟機規範嚴格規定了5種情況必須立即對類進行初始化。

  • 遇到new、getstatic、putstatic或invokestatic這4條位元組碼指令時,如果類沒有進行過初始化,則需要先觸發初始化。
  • 使用java.lang.reflect包的方法對類進行反射調用的時候,如果類沒有進行過初始化,則需要先觸發其初始化。
  • 當初始化一個類時,如果發現其父類沒有進行過初始化,則需要先觸發其父類的初始化。
  • 當虛拟機啟動時,使用者需要制定一個要執行的主類(包含main()方法的那個類),虛拟機會先初始化這個類。
  • 當使用JDK1.7的動态語言支援時,如果一個java.lang.invoke.MethodHandle執行個體最後的解析結果REF_geStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且這個方法句柄所對應的類沒有進行過初始化,則需要先觸發其初始化。

被動引用

除了上述5種場景,其他所有類的方式都不會觸發初始化,稱為被動引用。例如:

  • 子類調用父類的靜态方法,不會執行個體化子類
  • 通過數組定義類引用,不會觸發類的初始化
  • 通過調用常量不會觸發此類的初始化,因為常量在連結階段已經存入調用類的常量池中了

反射擷取泛型資訊

反射擷取注解資訊

import java.lang.annotation.*;
import java.lang.reflect.Field;

public class Test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class<?> c1 = Class.forName("User");
        //反射擷取注解
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
        //獲得注解的value值
        MyTable table = c1.getAnnotation(MyTable.class);//首先擷取指定注解
        System.out.println(table.value());
        //獲得類指定的注解
        Field f = c1.getDeclaredField("name");
        MyField annotation = f.getAnnotation(MyField.class);
        System.out.println(annotation.columnName());
        System.out.println(annotation.type());
        System.out.println(annotation.length());
    }
}

@MyTable("db_User")
class User{
    @MyField(columnName = "db_user",type = "varchar",length = 12)
    public String name;
    @MyField(columnName = "db_user",type = "int",length = 10)
    public int age;
}


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyTable{
    String value();
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyField{
    String columnName();
    String type();
    int length();
}