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:張三的個人信息
                    }
                }
            }
        }

    }
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容