1. 程式人生 > 實用技巧 >java基礎第12期——反射、註解

java基礎第12期——反射、註解

一. 反射

反射: 將類的各個組成部分封裝為其他物件.

1.1 獲取class物件的方式

Class.forName("全類名"):
將位元組碼檔案載入進記憶體,返回class物件
多用於配置檔案,將類名定義在配置檔案中,讀取檔案,載入類

類名.class: 
通過類名的屬性class獲取
多用於引數的傳遞

物件.getClass():
多用於物件的獲取位元組碼的方式

注意:
以上三種方法獲得的位元組碼檔案地址相同
同一個位元組碼檔案在一次程式執行中只會載入一次.

1.1.1 程式碼演示

示例person:

package reflecting;

public class demo00_person {

    public String name;
    protected String name1;
    String name2;
    private String name3;

    //成員方法
    public static void method1(){
        System.out.println("我是空參方法");
    }
    public static void method11(String s){
        System.out.println("我是有參方法"+s);
    }


    public demo00_person() {
    }

    public demo00_person(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "demo00_person{" +
                "name='" + name + '\'' +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

使用:

public static void main(String[] args) throws ClassNotFoundException {//異常
        Class cl1 = Class.forName("reflecting.demo00_person");//從包名開始
        Class cl2 = demo00_person.class;
        Class cl3 = demo00_person.class;

        System.out.println(cl1 ==cl2);//true
        System.out.println(cl1==cl3);//true
   }

1.2 class物件的功能

1.2.1 獲取

1.成員變數:
Filed[] getFields():  獲取public修飾的成員變數
Filed getFiled(String name):  獲取public修飾的指定名稱的成員變數
Filed[] getDeclaredFields()
Filed getDeclaredField(String name)

2.構造方法:
Constructor<?>[] getConstructors()
Constructor<T> getConstructor(類<?>... parameterTypes)
Constructor<?>[] getDeclaredConstructors()
Constructor<T> getDeclaredConstructor(類<?>... parameterTypes)

3.成員方法:
Method getMethod(String name, 類<?>... parameterTypes),方法名和按宣告順序標識該方法形參型別
Method[] getMethods()
Method getDeclaredMethod(String name, 類<?>... parameterTypes)
method[] getDeclaredMethods()

4.類名
String getName()

1.2.2 使用

一.成員變數:Field物件操作:
1.設定值:
void set(Object obj, Object value)
2.獲取值:
Object get(Object obj)
3.忽略訪問許可權修飾符的安全檢查:
SetAccessible(true): 暴力反射

二. 構造方法Constructor物件
1.建立物件:
T newInstance(Object... initargs)
使用此Constructor物件表示的建構函式,使用指定的初始化引數來建立和初始化建構函式的宣告類的新例項。
2.如果建立空引數構造器,可以使用:
class.getDeclaredConstructor().newInstance()
getDeclaredConstructor()根據他的引數對該類的建構函式進行搜尋並返回對應的建構函式,
沒有引數就返回該類的無參建構函式,然後再通過newInstance進行例項化。
同樣有SetAccessible(true)暴力反射.

三. 成員方法Method物件
1. 獲取成員方法後,執行方法:
Object invoke(Object obj, Object... args), 傳入呼叫該方法的類的物件和實參
同樣有SetAccessible(true).
2. 獲取方法名
String getName()

1.2.3 程式碼演示

public static void main(String[] args) throws Exception {
        //獲取person的Class物件
        Class<demo00_person> p1 = demo00_person.class;
        //獲取成員變數
        Field[] fields = p1.getFields();
        for (Field field : fields) {
            System.out.println(field);//person寫了四個許可權,只獲取到public修飾的
        }
        //獲取Field物件:名為name的成員變數
        Field name = p1.getField("name");
        //獲取值,傳入person物件
        demo00_person person = new demo00_person();
        Object value = name.get(person);//獲得值,字串預設值null

        //設定值
        name.set(person,"bronya");
        System.out.println(person);//列印結果name="bronya"

        System.out.println("---------");

        //忽略訪問許可權的安全檢查
        Field dc = p1.getDeclaredField("name3");
        dc.setAccessible(true);//忽略許可權
        Object o = dc.get(person);
        System.out.println(o);//null

        System.out.println("---獲取構造方法---");
        Constructor<demo00_person> constructor = p1.getConstructor(String.class);
        System.out.println("構造器:"+constructor);
        //T newInstance(Object... initargs)方法建立物件
        demo00_person person1 = constructor.newInstance("板鴨");
        System.out.println("建立的物件:"+person);
        //建立空引數構造器
        demo00_person person2 = p1.getDeclaredConstructor().newInstance();
        System.out.println("空引數:"+person2);

        //獲取成員方法,傳入指定方法名,和形參型別
        Method method1 = p1.getMethod("method1");//空參的
        Method method11 = p1.getMethod("method11", String.class);//有參寫法
        //執行方法,傳入類物件和實參
        method1.invoke(p1);
        method11.invoke(p1,"666");
        //獲取所有public修飾的成員方法
        Method[] methods = p1.getMethods();
        for (Method method : methods) {
            System.out.println(method);//還包括Object類的方法
            System.out.println(
                method.getName()
            );
        }
    }

1.2.4 框架應用

寫個框架,建立任意類的物件,執行其中任意方法.
1.將要建立物件的全類名和需要執行的方法定義在配置檔案中
2.在程式中載入配置檔案
3.用反射技術載入類檔案進記憶體
4.建立物件, 執行方法

src目錄下新建一個properties配置檔案:

className = reflecting.demo00_person
methodName = method11

使用:

public class demo03_Test {
    public static void main(String[] args) throws Exception {
        //載入配置檔案,先建立Properties物件,用load方法載入
        Properties properties = new Properties();
        //獲取該位元組碼檔案對應的類載入器,
        ClassLoader classLoader = demo03_Test.class.getClassLoader();
        //呼叫getResourceAsStream()傳入配置檔名稱,獲取位元組輸入流
        InputStream ras = classLoader.getResourceAsStream("pro.properties");
        //位元組輸入流給屬性集合
        properties.load(ras);
        //獲取配置檔案中定義的資料
        String classname = properties.getProperty("className");
        String method = properties.getProperty("methodName");
        //載入該類進記憶體
        Class<?> aClass = Class.forName(classname);
        //建立類物件
        Object obj = aClass.getDeclaredConstructor().newInstance();
        //獲取方法物件,傳入方法名和引數
        Method method1 = aClass.getMethod(method,String.class);
        //執行方法,傳入類物件和實參
        method1.invoke(obj,"111");
    }
}

二. 註解

2.1 定義

註解(Annotation),也叫元資料。一種程式碼級別的說明。
它是JDK1.5及以後版本引入的一個特性,與類、介面、列舉是在同一個層次。
它可以宣告在包、類、欄位、方法、區域性變數、方法引數等的前面,用來對這些元素進行說明,註釋。
作用分類:
①編寫文件:通過程式碼裡標識的元資料生成文件【生成文件doc文件】java doc命令
②程式碼分析:通過程式碼裡標識的元資料對程式碼進行分析【使用反射】
③編譯檢查:通過程式碼裡標識的元資料讓編譯器能夠實現基本的編譯檢查【Override】

2.2 jdk內建的註解

@Override: 檢查該方法是否是繼承自父類的
@Deprecated: 該註解標註的內容已經過時(但仍能使用)
@SuppressWarnings: 告訴編譯器忽略指定的警告,不用在編譯完成後出現警告資訊。引數傳入all表示全部

2.3 自定義註解

2.3.1 格式

元註解
public @interface 名稱{}

註解本質是介面,繼承java.lang.annotation.Annotation介面.

2.3.2 元註解

元註解是用於描述註解的註解

@Target: 表示註解能夠作用的位置
        引數ElementType.
                TYPE: 表示註解可以作用到類上
                METHOD: 表示可以作用到方法
                FIELD: 可以作用於成員變數上
@Retention: 描述註解被保留的階段
        引數RetentionPolicy.
                SOURCE: 不會保留到Class位元組碼檔案中
                CLASS: 會保留到Class位元組碼檔案中
          (常用) RUNTIME: 當前被描述的註解會保留到Class位元組碼檔案中並被JVM讀取到
@Documented: 描述註解是否被抽取到api文件中
@Inherited: 描述註解是否被子類繼承

2.3.3 註解屬性

註解裡的抽象方法稱作註解屬性,

註解屬性的要求:

1.返回值型別必須為之一: 基本資料型別\String\列舉\註解\以上型別的陣列
2.使用時需要賦值, 或者定義屬性時用default初始化預設值

2.3.4 程式碼演示

定義一個列舉類:

public enum meiju {
    p1,p2,p3;
}

定義一個註解類:

public @interface an0 {
}

自定義註解, 比如用到了列舉類和註解類:

@Target({ElementType.TYPE,ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
@Documented
@Inherited
public @interface an1 {
    //定義一個註解裡的抽象方法,稱註解的屬性
    String mt3() default "111";
    int mt4();//基本資料型別
    String mt5();//String型別
    meiju mt6();//列舉型別
    an0 mt7();//註解型別
    String[] mt8();//陣列型別
}

給註解賦值:

//壓制該類全部警告
@SuppressWarnings("all")
public class demo00 {
	//表示已經過時
    @Deprecated
    public void mt1(){
        System.out.println("1");
    }
    //注意如何賦值:基本資料型別\String\列舉\註解\以上型別的陣列
    @an1(mt4 = 1,mt5 = "1",mt6 = meiju.p1,mt7 = @an0,mt8 = {"1","2","a"})
    public void mt2(){
        mt1();
    }
}

2.4 解析註解

在程式中使用(解析)註解: 用來替換配置檔案

步驟:
1. 獲取註解定義的位置的物件
2. 獲取指定的註解: getAnnotation(class) 方法
3. 呼叫註解的抽象方法獲取配置的屬性值

首先自定義一個註解:

//該註解用來描述需要執行的類名和方法名
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface demo01 {
    String className();
    String methodName();
}

我們要使用的類Annotation.demo00, 假如我們要使用其中的mt1()

//壓制該類全部警告
@SuppressWarnings("all")
public class demo00 {
	//表示已經過時
    @Deprecated
    public void mt1(){
        System.out.println("1");
    }
    //注意如何賦值:基本資料型別\String\列舉\註解\以上型別的陣列
    @an1(mt4 = 1,mt5 = "1",mt6 = meiju.p1,mt7 = @an0,mt8 = {"1","2","a"})
    public void mt2(){
        mt1();
    }
}

最後的具體操作:

@demo01(className = "Annotation.demo00",methodName = "mt1")
public class demo011 {
    public static void main(String[] args) throws Exception {
        //解析註解
        //獲取該類位元組碼檔案物件
        Class<demo011> cls = demo011.class;
        //獲取註釋物件
        demo01 an = cls.getAnnotation(demo01.class);//在記憶體中生成了該註解介面的子類實現物件
        //呼叫註解物件中定義的抽象方法獲取返回值
        String className = an.className();
        String methodName = an.methodName();

        //載入該類進記憶體
        Class<?> aClass = Class.forName(className);
        //建立類物件
        Object obj = aClass.getDeclaredConstructor().newInstance();
        //獲取方法物件,傳入方法名和引數
        Method method1 = aClass.getMethod(methodName);
        //執行方法,傳入類物件和實參
        method1.invoke(obj);
    }
}