Java注解和反射


注解

什么是注解

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

  • Annotation的作用:

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

    • 注解是以"@注釋名"在代碼中存在的,還可以添加一些參數值,例如:@SuppressWarnings(value="unchecked")。
  • Annotation在哪里使用?

    • 可以附加在package、class、method、field等上面,相當於給他們添加了額外的輔助信息,我們可以通過反射機制編程實現對這些元數據的訪問

內置注解

  • @Override:定義在java.lang.Override中,此注解只適用於修飾方法,表示一個方法聲明打算重寫超類中的另一個方法聲明
  • @Deprecated:定義在java.lang.Deprecated中,此注解可以用於修飾方法,屬性,類,表示不鼓勵程序員使用這樣的元素,通常是因為它很危險或者存在更好的選擇
  • @SuppressWarnings:定義在java.lang.SuppressWarnings中,用來抑制編譯時的警告信息
    • 與前兩個注解有所不同,你需要添加一個參數才能正確使用,這些參數都是已經定義好的,我們選擇性的使用就好了。
    • @SuppressWarnings("all")
    • @SuppressWarnings("unchecked")\
    • SuppressWarnings(value={"unchecked","deprecation"})
    • 等等......

元注解

  • 元注解的作用就是負責注解其他注解,Java定義了4個標准的meta_annotation類型,他們被用來提供對其他annotation類型作用說明。
  • 這些類型和它們鎖支持的類在java.lang.annotation包中可以找到.(@Target,@Retention,@Documented,@Inherited)
    • @Target:用於描述注解的使用范圍(即:被描述的注解可以使用在什么地方)
    • @Retention:表示需要在什么級別保存在注釋信息,用於描述注解的生命周期
      • (SOURCE < CLASS < RUNTIME)
    • @Document:說明該注解被包含在javadoc中
    • @Inherited:說明子類可以集成父類中的該注解

自定義注解

  • 使用@interface 自定義注解時,自動繼承了java.lang.annotation.Annotation接口
  • 分析:
    • @interface用來聲明一個注解,格式: public @ interface 注解名 {定義內容}
    • 其中的每個方法實際上是聲明了一個配置參數
    • 方法的名稱就是參數的名稱
    • 返回值類型就是參數的類型(返回值只能是基本類型,Class,String,enum)
    • 可以通過default來聲明參數的默認值
    • 如果只有一個參數成員,一般參數名為value
    • 注解元素必須要有值,我們定義注解元素時,經常使用空字符串,0作為默認值
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
    //注解的參數:參數類型 + 參數名 ()
    String name() default "";
    int age() default 0;
    int id();
    String[] schools() default {"清華大學", "北京大學"};
}

class Test {
    //如果沒有默認值,我們就必須給注解賦值
    @MyAnnotation(id = 2006)
    public void test() {
    }
}

反射

反射機制的概述

動態語言

  • 是一類在運行時可以改變其結構的語言:例如新的函數、對象、甚至代碼可以被引進,已有的函數可以被杉樹或是其他結構上的變化。通俗點說就是在運行時代碼可以根據某些條件改變自身結構。
  • 主要動態語言:Object-C、C#、JavaScript、PHP、Python

靜態語言

  • 與動態語言相對應的,運行時結構不可變的語言就是靜態語言。如:Java、C、C++

  • Java不是動態語言,但Java可以稱之為"准動態語言"。即Java有一定的動態性,我們可以利用反射機制獲取類似動態語言的特性。Java的動態性讓編程的時候更加靈活!

  • Reflection(反射)是Java被視為動態語言的關鍵,反射機制運行程序在執行期借助於Reflection API取得任何類的內部信息,並能直接操作任意對象的內部屬性及方法。

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

  • 加載完類之后,在堆內存的方法區中就產生了一個Class類型的對象(一個類只能有一個Class對象),這個對象就包含了完整的類的結構信息。我們可以通過這個對象看到類的結構。這個對象就像一面鏡子,透過這個鏡子看到的類的結構,所以,我們形象的稱之為:反射

Java反射機制提供的功能

  • 在運行時判斷任意一個對象所屬的類
  • 在運行時構造任意一個類的對象
  • 在運行時判斷任意一個類所具有的成員變量和方法
  • 在運行時獲取泛型信息
  • 在運行時調用任意一個對象的成員變量和方法
  • 在運行時處理注解
  • 生成動態代理
  • ......

Java反射優點和缺點

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

反射相關的主要API

  • java.lang.Class:代表一個類
  • java.lang.reflect.Methode:代表類的方法
  • java.lang.reflect.Field:代表類的成員變量
  • java.lang.reflect.Constructor:代表類的構造器
  • ...... .......
package com.ycy.demo02;

/**
 * @author YeCaiYu
 * @date 2020-05-04 9:43
 */
public class Test02 {
    public static void main(String[] args) throws ClassNotFoundException {
        //通過反射獲取類的Class對象
        Class c1 = Class.forName("com.ycy.demo02.User");

        System.out.println(c1);

        Class c2 = Class.forName("com.ycy.demo02.User");
        Class c3 = Class.forName("com.ycy.demo02.User");
        Class c4 = Class.forName("com.ycy.demo02.User");
        //一個類在內存中只有一個Class對象
        //一個類被加載后,類的整個結構都會被封裝在Class對象中
        System.out.println(c1.hashCode());
        System.out.println(c2.hashCode());
        System.out.println(c3.hashCode());
        System.out.println(c4.hashCode());

    }
}

//實體類:pojo , entity
class User {
    private String name;
    private int id;
    private int age;

    public User() {
    }

    public User(String name, int id, int age) {
        this.name = name;
        this.id = id;
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", id=" + id +
                ", age=" + age +
                '}';
    }

    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;
    }
}

Class類

在Object類中定義了以下的方法,此方法被所有子類集成

public final Class getClass()

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

對象照鏡子后可以得到的信息:某個類的屬性、方法和構造器、某個類到底實現了那些接口。對於每個類而言,JRE都為其保留一個不變的Class類型的對象。一個Class對象包含了特定某個結構(class/interface/enum/annotation/primitive type/void/[])的有關信息。

  • Class本身也是一個類
  • Class對象只能由系統建立對象
  • 一個加載的類在JVM中只會有一個Class實例
  • 一個Class對象對應的是一個加載到JJVM中的一個.class文件
  • 每個類的實例都會記得自己是由哪個Class實例所生成
  • 通過Class可以完整地得到一個類中所有被加載的結構
  • Class類是Reflection的根源,針對任何你想動態加載、運行的類,唯獨先獲得相應Class對象
方法名 功能說明
static ClassforName(String name) 返回指定類名name的Class對象
Object newInstance() 調用缺省構造函數,返回Class對象的一個實例
getName() 返回此Class對象所表示的實體(類、接口、數組類或void)的名稱
Class getSuperClass() 返回當前Class對象的父類的Class對象
Class[] getinterfaces() 獲取當前Class對象的接口
Constructor[] getConstructors() 返回一個包含某些Constructor對象的數組
ClassLoader getClassLoader() 返回該類的加載器
Method getMethod(String name.Class.. T) 返回一個Method對象,此對象的形參類型為paramType
Field[] getDeclaredFields() 返回Field對象的一個數組
package com.ycy.demo02;

/**
 * @author YeCaiYu
 * @date 2020-05-04 10:05
 */
//測試Class類的創建有哪些
public class Test03 {
    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());
        //方式二: forName獲取
        Class c2 = Class.forName("com.ycy.demo02.Student");
        System.out.println(c2.hashCode());
        //方式三: 通過類名.class獲取
        Class 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 {
    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 = "老師";
    }
}

類的加載


類初始化

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

獲取類的運行時結構

package com.ycy.demo02;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
 * @author YeCaiYu
 * @date 2020-05-04 10:54
 */
public class Test05 {
    //獲取類的信息
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
        Class c1 = Class.forName("com.ycy.demo02.User");

        //獲取類的名字
        System.out.println(c1.getName());//獲取包名 + 類名
        System.out.println(c1.getSimpleName()); //獲取類名

        //獲取類的屬性
        Field[] fields = c1.getFields();//只能找到public屬性
        for (Field field : fields) {
            System.out.println(field);
        }
        System.out.println("++++++++++++++++++++++");
        fields = c1.getDeclaredFields();//能找到全部的屬性
        for (Field field : fields) {
            System.out.println(field);
        }
        //獲取指定屬性的值
        Field name = c1.getDeclaredField("name");
        System.out.println(name);

        //獲取類的方法
        System.out.println("+++++++++++++++++++++++");
        Method[] methods = c1.getMethods();//獲得本類及其父類全部public方法
        for (Method method : methods) {
            System.out.println(method);
        }
        System.out.println("+++++++++++++++++++++++");
        methods = c1.getDeclaredMethods();//獲取本類的所有方法
        for (Method method : methods) {
            System.out.println(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(constructor);
        }
        System.out.println("********************************");
        constructors = c1.getDeclaredConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }
        //獲得指定構造器
        Constructor constructor = c1.getConstructor(String.class, int.class, int.class);
        System.out.println("指定:" + constructor);

    }
}

調用執行的方法

通過反射,調用類中的方法,通過Method類完成。

  • 通過Class類的getMethod(String name,Class.. parameterTypes)方法取得一個Method對象,並設置此方法操作時所需要的的參數類型。
  • 之后使用Object invoke(Object obj,Object[] args)進行調用,並向方法中傳遞設置的obj對象的參數信息

若原方法聲明為private,則需要在調用此invoke()方法前,顯式調用對象的setAccessible(true)方法,將可訪問private


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM