Java基礎知識(二)

反射機制

Java中處理基本數據類型,如intchar等,其他均為引用類型。

每個引用類型也是Java中的一個對象,稱為類對象,用以記錄該類的信息:包括類名、包名、父類、實現的接口、所有方法、字段等。 通過該引用類型創建的實例,稱為類的實例對象。

Java對類對象的加載是動態的,只有當JVM第一讀取到該類Class(包括Interface)的信息時,才會將該類對象加載到內存中,并將該Class的名稱與類對象進行關聯。

反射:通過類型的名稱,構建類對象,獲取該類的信息。

public class ReflectTest {
    public static void main(String[] args) {
        String str = "hello";
        ///獲取類對象
        ///1
        Class cls = str.getClass();
        ///2
        cls = String.class;
        ///3
        try {
            cls = Class.forName("java.lang.String");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        String simpleName = cls.getSimpleName();
        String packageName = cls.getPackageName();
        String name = cls.getName();
        System.out.println(simpleName);//String
        System.out.println(packageName);///java.lang
        System.out.println(name);//java.lang.String
        System.out.println(cls);///class java.lang.String
    }
}
  • 反射調用示例:
///將要反射的類
package learn.cls;
public class Person extends  Object {
    public int age;
    String name;

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

    public void introduce(String from) {
        System.out.printf("我叫%s,今年%d歲,來自%s%n",name,age,from);
    }
    public String getInfo() {
        return name + " : " +age + "歲";
    }
}
///反射調用
public class ReflectTest {
    public static void main(String[] args) {
        ///反射創建實例和調用方法
        try {
            Class cls = Class.forName("learn.cls.Person");
            ///調用指定的構造函數
            Object instance = cls.getDeclaredConstructor(String.class).newInstance("張三");
            ///反射字段
            Field field = cls.getDeclaredField("age");
            field.set(instance,18);
            ///獲取該字段的值
            Integer age = field.getInt(instance);
            System.out.println(age);///18
            ///反射方法
            Method method = cls.getDeclaredMethod("introduce", String.class);
            ///方法調用
            method.invoke(instance,"上海");///我叫張三,今年18歲,來自上海
            ///反射調用獲取返回值
            Method method1 = cls.getDeclaredMethod("getInfo");
            String info = (String) method1.invoke(instance);
            System.out.println(info);///張三 : 18歲
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}
///上述反射調用等同于下述代碼
Person person = new Person("張三");
person.age = 18;
Integer age = person.age;
System.out.println(age);
person.introduce("上海");
String info = person.getInfo();
System.out.println(info);

動態代理

動態代理:JDK提供的動態創建接口對象的方式。定義了接口,但是并不去編寫實現類,而是直接通過JDK提供的一個Proxy.newProxyInstance()創建了一個接口對象。這種沒有實現類但是在運行期動態創建了一個接口對象的方式,我們稱為動態代碼。

  • 示例1:
///接口
interface Game {
    void play(String toy);
    String category();
}

public class DynamicDelegateTest {
    public static void main(String[] args) {
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
               ///等同接口方法的實現
                if (method.getName().equals("play")) {
                    String out = "let us play " + args[0];
                    System.out.println(out);
                } else if (method.getName().equals("category")) {
                    return "游戲";
                }
                return null;
            }
        };
       ///不寫實現類,直接創建接口對象
        Game game = (Game) Proxy.newProxyInstance(Game.class.getClassLoader(), new Class[]{Game.class},invocationHandler);
        game.play("basketball");///let us play basketball
        String str = game.category();//游戲
        System.out.println(str);
    }
}
///上述代碼的本質
public class DynamicDelegateTest implements Game {
    public InvocationHandler invocationHandler;
    public  DynamicDelegateTest(InvocationHandler handler){
        this.invocationHandler = handler;
    }
    @Override
    public void play(String toy) {
        try {
            this.invocationHandler.invoke(this, getClass().getDeclaredMethod("play", String.class), new Object[]{toy});
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
    @Override
    public String category() {
        String result = null;
        try {
           result = (String)this.invocationHandler.invoke(this, getClass().getDeclaredMethod("category"), null);
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return  result;
    }
}

示例2:動態代理實現接口方法的監聽

public class DynamicDelegateTest {
    
    public static void main(String[] args) {
        Game game = new Game() {
            @Override
            public void play(String toy) {
                System.out.println("玩游戲中...");
            }

            @Override
            public String category() {
                return "游戲";
            }
        };
        ///game:最終執行方法的對象 proxyGame:實現接口方法監聽生效的對象
        Game proxyGame = (Game) proxyObject(game);
        ///啟用接口方法監聽
        proxyGame.play("籃球");
        ///輸出:
        /*
        方法:play,調用前
        玩游戲中...
        方法:play,調用后
        */
    }

    public static Object proxyObject(Game target) {
        Class[] interfaces = {Game.class};
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.printf("方法:%s,調用前%n", method.getName());
                ///對接口方法進行監聽后,由被代理的對象去執行
                Object objc = method.invoke(target, args);
                System.out.printf("方法:%s,調用后%n", method.getName());
                return objc;
            }
        };
        return Proxy.newProxyInstance(Game.class.getClassLoader(), interfaces, handler);
    }
}

Java注解

注解的概念

Java中的注解是一種可以放在類、字段、方法、參數前的特殊”注釋“。
Java中的注釋會被編譯器忽略,注解則可以被編譯器打包進.class文件,因此注解是一種用作標注的元數據。

Java中使用@interface來定義注解。注解的參數類似無參方法。聲明注解參數時,推薦使用default關鍵字為其設定默認值;常用的注解參數命名為value,可在注解參數只有一個時,省略其參數名稱。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD,ElementType.METHOD})
public @interface Range {
    int min() default 0;
    int max() default 100;
    int value() default 50;
}

元注解

可以修飾其他注解的注解稱為元注解。Java標準庫已經定義了一些元注解,我們只需要使用元注解,通常不需要自己去編寫元注解。

  1. @Target:定義注解可以被用于源碼的哪些位置。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    //注解參數為數組,可以定義多個位置
    ElementType[] value();
}
///該注解的參數類型:`ElementType`
public enum ElementType {
    TYPE,///類、接口、標注接口,枚舉、record 聲明
    FIELD,///類的字段聲明、包括枚舉常量
    METHOD,///方法聲明
    PARAMETER,///形式參數聲明
    CONSTRUCTOR,///構造函數聲明
    LOCAL_VARIABLE,///本地變量聲明
    ANNOTATION_TYPE, ///注解類型聲明
    PACKAGE,///Package聲明
    TYPE_PARAMETER,///類型參數聲明
    TYPE_USE,///使用類型
    MODULE,///模塊聲明
    RECORD_COMPONENT;///record部分
}

2.@Retention: 定義注解的生命周期
如果在一個注釋接口聲明中沒有Retention注釋,保留策略默認為RetentionPolicy.CLASS

/*If no Retention annotation is present on an annotation interface declaration, the retention policy defaults to RetentionPolicy.CLASS.*/
public @interface Retention {
    RetentionPolicy value();
}
///該注解的參數類型:`RetentionPolicy `
public enum RetentionPolicy {
    ///Annotations are to be discarded by the compiler.
    ///修飾的注解被編譯器舍棄
    SOURCE, ///編譯器
    /// Annotations are to be recorded in the class file by the compiler
    ///  but need not be retained by the VM at run time
   ///修飾的注解將被編譯器記錄到`java`類的`.class`文件中,但在運行時不需要被`JVM`保留
    CLASS,///class文件

    /// Annotations are to be recorded in the class file by the compiler and
    /// retained by the VM at run time, so they may be read reflectively.
    /// 修飾的注解將被編譯器記錄到`java`類的`.class`文件中,并且運行時會被`JVM`保留
    RUNTIME///運行期
}
  1. @Repeatable:注釋其聲明的注釋接口是可重復的。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    Class<? extends Annotation> value();
}
///可重復使用注解的定義
@Repeatable(value = Ranges.class)
@Target(ElementType.TYPE)
public @interface Range {
    int min() default 0;
    int max() default 100;
    int value() default 50;
}

@Target(ElementType.TYPE)
@interface Ranges {
    Range[] value();
}
///具體使用
@Range(min = 10,max = 200,value = 120)
@Range(80)
@Range(value = 19)
class Test {
}
  1. @ Inherited
    使用@Inherited定義子類是否可繼承父類定義的注解。@Inherited僅針對@Target(ElementType.TYPE)類型的注解有效,并且僅針對class的繼承,對interface的繼承無效:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
///示例
@Inherited
@Target(ElementType.TYPE)
public @interface Report {
    int type() default 0;
    String level() default "info";
    String value() default "";
}
///父類使用了這個被`@Inherited`修飾的注解
@Report(type=1)
public class Person {
}
///子類默認也繼承了該父類的注解
public class Student extends Person {
}

注解的定義

第一步:使用@interface定義注解

public @interface Report {
}

第二步:定義注解的參數、默認值

public @interface Report {
    int type() default 0;
    String level() default "info";
    String value() default "";
}

把最常用的參數定義為value(),推薦所有參數都盡量設置默認值。
第三步:用元注解配置注解

@Target(ElementType.TYPE)///注解類、接口、枚舉等
@Retention(RetentionPolicy.RUNTIME)///運行期保留注解
public @interface Report {
    int type() default 0;
    String level() default "info";
    String value() default "";
}

其中,必須設置@Target@Retention,@Retention一般設置為RUNTIME,因為我們自定義的注解通常要求在運行期讀取。一般情況下,不必寫@Inherited@Repeatable

注解的處理

注解定義后也是一種class,所有的注解都繼承自java.lang.annotation.Annotation,因此,讀取注解,需要使用反射API

  • Java中提供的與注解相關的反射API

///-------判斷注解是否存在------

///判斷類是否有注解
AnnotationTest.class.isAnnotationPresent(Range.class);
///判斷類的構造函數是否有注解
AnnotationTest.class.getDeclaredConstructor().isAnnotationPresent(Range.class);
///判斷某個方法是否有注解
Method method = AnnotationTest.class.getMethod("getSomeUsefulInfo");
method.isAnnotationPresent(Range.class);
///判斷某個字段是否有注解
Field field = AnnotationTest.class.getField("userType");
field.isAnnotationPresent(Range.class);

///-------讀取注解信息------

///獲取類的注解
AnnotationTest.class.getAnnotation(Range.class);
try {
    ///獲取類的構造函數的注解
    AnnotationTest.class.getDeclaredConstructor().getAnnotation(Range.class);
    ///獲取某個方法的注解
    AnnotationTest.class.getMethod("getSomeUsefulInfo").getAnnotation(Range.class);
    //獲取某個字段的注解
    Field field = AnnotationTest.class.getField("userType");
    field.getAnnotation(Range.class);
 } catch (NoSuchMethodException e) {
      e.printStackTrace();
 } catch (NoSuchFieldException e) {
      e.printStackTrace();
}
  • Java注解的示例
    1.定義兩個注解:RouterGetFuncInfo,分別用來注解類和方法
///注解類,定義類的實例對象需要執行的操作
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Router {
    String value() default "";
    String operation() default "getInfo";
}

///注解方法,并設置方法調用的默認參數
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface GetFuncInfo {
    String value() default "";
}
  1. 注解的類和方法
@Router(operation = "getSomeUsefulInfo",value = "markedClass")
public class AnnotationTest {
    
    public String param0;
    public String param1;
    public int param2;
    
    @GetFuncInfo("張三的個人信息")
    public void getSomeUsefulInfo(String param0){
        System.out.printf("getSomeUsefulInfo:%s",param0);
    }
    
    public void functionWithSomeParameters(String p1, int p2) {
        System.out.println(p1);
        System.out.println(p2);
    }
}

3.處理注解

public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        ///定義操作
        String operate = "getSomeUsefulInfo";
        ///掃描`Router`注解的類,此處假設已知
        String classStr = "annotation.AnnotationTest";
        Class cls = Class.forName(classStr);

        if (cls.isAnnotationPresent(Router.class)){
            ///獲取類的注解
            Router router = (Router)cls.getAnnotation(Router.class);
            System.out.println(router);///@annotation.Router(value="markedClass", operation="getSomeUsefulInfo")
            ///根據注解的操作,找到對應的方法
            String operateStr = router.operation();
            for (Method method: cls.getMethods()) {
                if (method.getName().equals(operateStr)) {
                    ///獲取方法的注解
                    if (method.isAnnotationPresent(GetFuncInfo.class)){
                        GetFuncInfo funcInfo = method.getAnnotation(GetFuncInfo.class);
                        System.out.println(funcInfo);///@annotation.GetFuncInfo("\u5f20\u4e09\u7684\u4e2a\u4eba\u4fe1\u606f")
                        ///讀取注解參數
                        String param = funcInfo.value();
                        ///創建對象實例
                        Object clsObj = cls.getDeclaredConstructor().newInstance();
                        ///調用方法,傳入注解參數
                        method.invoke(clsObj,param); ///getSomeUsefulInfo:張三的個人信息
                    }
                }
            }
        }

    }
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容