天天看點

JavaSE(10):反射機制和注解一、反射機制二、注解

文章目錄

  • 一、反射機制
    • 1.1 基本概念
    • 1.2 擷取位元組碼檔案
    • 1.3 路徑問題
    • 1.4 資源綁定器
    • 1.5 Field相關操作
      • 1.5.1 擷取Filed
      • 1.5.2 反編譯Field
      • 1.5.3 通過反射機制通路對象屬性(重點)
    • 1.6 可變長參數
    • 1.6 Method相關操作
      • 1.6.1 反射Method
      • 1.6.2 反編譯Method
      • 1.6.3 通過反射機制調用一個對象的方法(重點)
    • 1.7 Constructor相關操作
      • 1.7.1 反編譯Constructor
      • 1.7.2 反射機制調用構造方法
    • 1.8 擷取父類和父接口
    • 1.9 類加載器
  • 二、注解
    • 2.1 基礎
    • 2.2 注解中的屬性
    • 2.3 反射注解
    • 2.4 注解在開發中的作用

一、反射機制

1.1 基本概念

1 通過反射機制可以操作位元組碼檔案(.class檔案),雖然代碼變複雜了,但是很靈活。

2 包:java.lang.reflect.*

  • 重要類

1 java.lang.Class:代表整個位元組碼。

2 java.lang.reflect.Method:代表位元組碼中的方法位元組碼。

3 java.lang.reflect.Constructor:代表位元組碼中的構造方法位元組碼。

4 java.lang.reflect.Field:代表位元組碼中的屬性位元組碼(靜态變量+執行個體變量)。

1.2 擷取位元組碼檔案

  • 三種方法

1 Class c = Class.forName(“完整類名帶包名”);

該方法是一個靜态方法,參數是一個字元串,這個字元串要是一個完整類名。

2 Class c = 對象.getClass();

java中每一個對象都有一個getClass()方法。

3 Class c = 任何類型.class;

java語言中任何一種類型,包括基本資料類型,它都有.class屬性。

  • 擷取位元組碼的用處

1 擷取到位元組碼後,可以通過Class的newInstance()方法來執行個體化對象。這樣就可以做到在不改變代碼的情況下,做到不同對象的執行個體化。

2 一些進階架構比如ssh,ssm的底層實作原理就是反射機制。

3 注意:newInstance()方法内部實際上調用了無參數構造方法,必須保證無參構造存在才可以。

  • 隻執行靜态代碼塊

1 如果你隻是希望一個類的靜态代碼塊執行,其它代碼一律不執行,你可以使用:Class.forName(“完整類名”);

2 這個方法的執行會導緻類加載,類加載時,靜态代碼塊執行。

3 後面JDBC技術的時候我們會使用到這個。

  • 示例
1 以下代碼是靈活的,代碼不需要改動,可以修改配置檔案,配置檔案修改之後,可以建立出不同的執行個體對象。
// 建立classinfo.properties配置檔案。之後想要執行個體化其他對象,隻需修改此檔案即可。
className=java.lang.String
           
import java.io.FileReader;
import java.util.Properties;
public class ReflectTest02 {
    public static void main(String[] args) throws Exception {
        // 通過IO流讀取classinfo.properties檔案
        FileReader reader = new FileReader("chapter05/src/classinfo.properties");
        // 建立屬性類對象Map
        Properties pro = new Properties();
        // 加載
        pro.load(reader);
        // 關閉流
        reader.close();
        // 通過key擷取value
        String className = pro.getProperty("className");
        // 通過反射機制執行個體化(建立)對象
        Class c = Class.forName(className);
        Object obj = c.newInstance();
        System.out.println(obj);
    }
}
           

1.3 路徑問題

1 對于路徑會存在這樣一個問題,把程式換到另一個地方進行運作時,有時候路徑就無效了。

2 有一種通用的方式,但是這種方式需要檔案再類路徑下,類路徑就是src檔案。

  • 示例

1 Thread.currentThread() 目前線程對象

2 getContextClassLoader() 是線程對象的方法,可以擷取到目前線程的類加載器對象。

3 getResource() 【擷取資源】這是類加載器對象的方法,目前線程的類加載器預設從類的根路徑下加載資源。

4 Thread.currentThread().getContextClassLoader().getResource(“classinfo.properties”) 直接以流(InputStream)的形式傳回。

//建立一個classinfo.properties檔案在src下。
public class AboutPath {
    public static void main(String[] args) {
    	// 擷取絕對路徑,該檔案需要在src檔案下。
        String path = Thread.currentThread().getContextClassLoader()
                .getResource("classinfo.properties").getPath();
        System.out.println(path);
    }
}
           

1.4 資源綁定器

1 java.util包下提供了一個資源綁定器,便于擷取屬性配置檔案中的内容。

2 資源綁定器,隻能綁定xxx.properties檔案。并且這個檔案必須在類路徑下。檔案擴充名也必須是properties,并且在寫路徑的時候,路徑後面的擴充名不能寫。

  • 示例
import java.util.ResourceBundle;
public class ResourceBoundleTest {
    public static void main(String[] args) {
        ResourceBundle bundle = ResourceBundle.getBundle("classinfo");
        String className = bundle.getString("className");
        System.out.println(className);
    }
}
           

1.5 Field相關操作

隻要看懂重點即可。

1.5.1 擷取Filed

Class studentClass = Class.forName(“com.malidong.reflect.Student”);

1擷取簡單類名

String simpleName = studentClass.getSimpleName();

2 擷取完整類名

String className = studentClass.getName();

3 擷取類中所有的public修飾的Field

Field[] fields = studentClass.getFields();

4 擷取所有的Field

Field[] fs = studentClass.getDeclaredFields();

5 擷取屬性的修飾符清單

int i = field.getModifiers(); // 傳回的修飾符是一個數字,每個數字是修飾符的代号!!!

6 可以将這個“代号”數字轉換成“字元串”嗎?

String modifierString = Modifier.toString(i);

7 擷取屬性的類型

Class fieldType = field.getType();

String fName = fieldType.getName();

String fName = fieldType.getSimpleName();

field.getName()

1.5.2 反編譯Field

通過反射機制,反編譯一個類的屬性Field。就是把類的屬性全部列印出來。
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class ReflectTest05 {
    public static void main(String[] args) throws Exception{
        // 建立這個是為了拼接字元串。
        StringBuilder s = new StringBuilder();
        Class studentClass = Class.forName("java.lang.Thread");
        s.append(Modifier.toString(studentClass.getModifiers()) + " class " + studentClass.getSimpleName() + " {\n");
        Field[] fields = studentClass.getDeclaredFields();
        for(Field field : fields){
            s.append("\t");
            s.append(Modifier.toString(field.getModifiers()));
            s.append(" ");
            s.append(field.getType().getSimpleName());
            s.append(" ");
            s.append(field.getName());
            s.append(";\n");
        }
        s.append("}");
        System.out.println(s);
    }
}
           

1.5.3 通過反射機制通路對象屬性(重點)

給屬性指派set,擷取屬性的值get。

将架構采用,很少使用。

import java.lang.reflect.Field;
public class ReflectTest06 {
    public static void main(String[] args) throws Exception{
        // 我們不使用反射機制,怎麼去通路一個對象的屬性呢?
        Student s = new Student();
        // 給屬性指派
        s.no = 1111; 
        // 讀屬性值
        System.out.println(s.no);
        // 使用反射機制,怎麼去通路一個對象的屬性。(set get)
        Class studentClass = Class.forName("com.malidong.reflect.Student");
        Object obj = studentClass.newInstance(); // obj就是Student對象。(底層調用無參數構造方法)
        Field noFiled = studentClass.getDeclaredField("no"); // 擷取no屬性(根據屬性的名稱來擷取Field)
        noFiled.set(obj, 22222); // 給obj對象的no屬性指派2222
        // 讀取屬性的值
        System.out.println(noFiled.get(obj));
        // 可以通路私有的屬性嗎?
        Field nameField = studentClass.getDeclaredField("name");
        // 打破封裝(反射機制的缺點:打破封裝,可能會給不法分子留下機會!!!)
        // 這樣設定完之後,在外部也是可以通路private的。
        nameField.setAccessible(true);
        // 給name屬性指派
        nameField.set(obj, "jackson");
        // 擷取name屬性的值
        System.out.println(nameField.get(obj));
    }
}
           

1.6 可變長參數

1 可變長度參數要求的參數個數是:0~N個。

2 可變長度參數在參數清單中必須在最後一個位置上,而且可變長度參數隻能有1個。

3 可變長度參數可以當做一個數組來看待。

  • 示例
m();
m(10);
m(10, 20);
public static void m(int... args){
	System.out.println("m方法執行了!");
}
           

1.6 Method相關操作

隻要看懂重點即可。

1.6.1 反射Method

1 擷取類了

Class userServiceClass = Class.forName(“com.malidong.reflect.UserService”);

2 擷取所有的Method(包括私有的!)

Method[] methods = userServiceClass.getDeclaredMethods();

3 擷取修飾符清單

Modifier.toString(method.getModifiers())

4 擷取方法的傳回值類型

method.getReturnType().getSimpleName()

5 擷取方法名

method.getName()

6 方法的修飾符清單(一個方法的參數可能會有多個。)

Class[] parameterTypes = method.getParameterTypes();

for(Class parameterType : parameterTypes){

System.out.println(parameterType.getSimpleName());

}

1.6.2 反編譯Method

隻能傳回方法名,不能傳回方法裡的内容。反編譯就是利用編譯把方法打出來。
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class ReflectTest08 {
    public static void main(String[] args) throws Exception{
        StringBuilder s = new StringBuilder();
        Class userServiceClass = Class.forName("java.lang.String");
        s.append(Modifier.toString(userServiceClass.getModifiers()) + " class "+userServiceClass.getSimpleName()+" {\n");
        Method[] methods = userServiceClass.getDeclaredMethods();
        for(Method method : methods){
            s.append("\t");
            s.append(Modifier.toString(method.getModifiers()));
            s.append(" ");
            s.append(method.getReturnType().getSimpleName());
            s.append(" ");
            s.append(method.getName());
            s.append("(");
            // 參數清單
            Class[] parameterTypes = method.getParameterTypes();
            for(Class parameterType : parameterTypes){
                s.append(parameterType.getSimpleName());
                s.append(",");
            }
            // 删除指定下标位置上的字元
            s.deleteCharAt(s.length() - 1);
            s.append("){}\n");
        }
        s.append("}");
        System.out.println(s);
    }
}
           

1.6.3 通過反射機制調用一個對象的方法(重點)

反射機制,讓代碼很具有通用性,可變化的内容都是寫到配置檔案當中,将來修改配置檔案之後,建立的對象不一樣了,調用的方法也不同了,但是java代碼不需要做任何改動。這就是反射機制的魅力。
import java.lang.reflect.Method;
public class ReflectTest09 {
    public static void main(String[] args) throws Exception{
        // 不使用反射機制,怎麼調用方法
        // 建立對象
        UserService userService = new UserService();
        // 調用方法
        boolean loginSuccess = userService.login("admin","123");
        System.out.println(loginSuccess ? "登入成功" : "登入失敗");
        // 使用反射機制來調用一個對象的方法該怎麼做?
        Class userServiceClass = Class.forName("com.malidong.reflect.UserService");
        // 建立對象
        Object obj = userServiceClass.newInstance();
        // 擷取Method
        Method loginMethod = userServiceClass.getDeclaredMethod("login", String.class, String.class);
        /*
        四要素:
        loginMethod方法
        obj對象
        "admin","123" 實參
        retValue 傳回值
         */
        Object retValue = loginMethod.invoke(obj, "admin","123123");
        System.out.println(retValue);
    }
}
           

1.7 Constructor相關操作

隻要看懂重點即可。

1.7.1 反編譯Constructor

反編譯一個類的Constructor構造方法。操作class檔案,把所有的構造方法列印出來。
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
public class ReflectTest10 {
    public static void main(String[] args) throws Exception{
        StringBuilder s = new StringBuilder();
        Class vipClass = Class.forName("java.lang.String");
        s.append(Modifier.toString(vipClass.getModifiers()));
        s.append(" class ");
        s.append(vipClass.getSimpleName());
        s.append("{\n");
        // 拼接構造方法
        Constructor[] constructors = vipClass.getDeclaredConstructors();
        for(Constructor constructor : constructors){
            //public Vip(int no, String name, String birth, boolean sex) {
            s.append("\t");
            s.append(Modifier.toString(constructor.getModifiers()));
            s.append(" ");
            s.append(vipClass.getSimpleName());
            s.append("(");
            // 拼接參數
            Class[] parameterTypes = constructor.getParameterTypes();
            for(Class parameterType : parameterTypes){
                s.append(parameterType.getSimpleName());
                s.append(",");
            }
            // 删除最後下标位置上的字元
            if(parameterTypes.length > 0){
                s.deleteCharAt(s.length() - 1);
            }
            s.append("){}\n");
        }
        s.append("}");
        System.out.println(s);
    }
}
           

1.7.2 反射機制調用構造方法

通過反射機制調用構造方法執行個體化java對象。
import java.lang.reflect.Constructor;
public class ReflectTest11 {
    public static void main(String[] args) throws Exception{
        // 不使用反射機制怎麼建立對象
        Vip v1 = new Vip();
        Vip v2 = new Vip(110, "zhangsan", "2001-10-11", true);
        // 使用反射機制怎麼建立對象呢?
        Class c = Class.forName("com.malidong.reflect.Vip");
        // 調用無參數構造方法
        Object obj = c.newInstance();
        System.out.println(obj);
        // 調用有參數的構造方法怎麼辦?
        // 第一步:先擷取到這個有參數的構造方法
        Constructor con = c.getDeclaredConstructor(int.class, String.class, String.class,boolean.class);
        // 第二步:調用構造方法new對象
        Object newObj = con.newInstance(110, "jackson", "1990-10-11", true);
        System.out.println(newObj);
        // 擷取無參數構造方法
        Constructor con2 = c.getDeclaredConstructor();
        Object newObj2 = con2.newInstance();
        System.out.println(newObj2);
    }
}
           

1.8 擷取父類和父接口

public class ReflectTest12 {
    public static void main(String[] args) throws Exception{
        // String舉例
        Class stringClass = Class.forName("java.lang.String");
        // 擷取String的父類
        Class superClass = stringClass.getSuperclass();
        System.out.println(superClass.getName());
        // 擷取String類實作的所有接口(一個類可以實作多個接口。)
        Class[] interfaces = stringClass.getInterfaces();
        for(Class in : interfaces){
            System.out.println(in.getName());
        }
    }
}
           

1.9 類加載器

專門負責加載類的指令/工具。

JDK自帶有3個類加載器

1啟動類加載器:rt.jar

2 擴充類加載器:ext/*.jar

3 應用類加載器:classpath

假設有這樣一段代碼:String s = “abc”;

1 首先通過“啟動類加載器”加載。注意:啟動類加載器專門加載:C:\ProgramFiles\Java\jdk1.8.0_101\jre\lib\rt.jar。rt.jar中都是JDK最核心的類庫。

2 如果通過“啟動類加載器”加載不到的時候,會通過"擴充類加載器"加載。注意:擴充類加載器專門加載:C:\Program Files\Java\jdk1.8.0_101\jre\lib\ext*.jar。

3 如果“擴充類加載器”沒有加載到,那麼會通過“應用類加載器”加載。注意:應用類加載器專門加載:classpath中的類。

  • 雙親委派機制

1 java中為了保證類加載的安全,使用了雙親委派機制。

2 優先從啟動類加載器中加載,這個稱為“父”,“父”無法加載到,再從擴充類加載器中加載,這個稱為“母”。雙親委派。如果都加載不到,才會考慮從應用類加載器中加載。直到加載到為止。

二、注解

2.1 基礎

[修飾符清單] @interface 注解類型名{

}
           

1 注解又叫做注釋,是一種引用資料類型,編譯後也是生成.class檔案。

2 注解使用時的文法格式是:@注解類型名

3 注解可以出現在類上,屬性上,方法上,變量上,等等。注解還可以出現在注解類型上。

  • jdk内置注解
java.lang包下一共有三個注解類型:Deprecated,Override,SuppressWarnings。重點掌握前兩個。

Override:

1 隻能注解方法,是給編譯器參考的,和運作階段沒有關系。

2 凡是java中的方法帶有這個注解的,編譯器都會進行編譯檢查,如果這個方法不是重寫父類的方法,編譯器報錯,就是@override下有紅曲線。

Deprecated:

1 用 @Deprecated 注釋的程式元素,不鼓勵程式員使用這樣的元素,通常是因為它很危險或存在更好的選擇。

2 表示注解的東西已過時。調用的話,會直接被橫線劃掉。

  • 元注解
1 元注解是用來标注“注解類型”的“注解”。常用的有Target,Retention。

Target:

1 用來标注“被标注的注解”可以出現在哪些位置上。

2 @Target(ElementType.METHOD):表示“被标注的注解”隻能出現在方法上。

3 @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})

Retention:

1 用來标注“被标注的注解”最終儲存在哪裡。

2 Retention(RetentionPolicy.SOURCE):表示該注解隻被保留在java源檔案中。

3 @Retention(RetentionPolicy.CLASS):表示該注解被儲存在class檔案中。

4 @Retention(RetentionPolicy.RUNTIME):表示該注解被儲存在class檔案中,并且可以被反射機制所讀取。

2.2 注解中的屬性

1 如果一個注解當中有屬性,那麼必須給屬性指派。(除非該屬性使用default指定了預設值。)

2 如果屬性名是value,并且隻有一個屬性,名字可以省略。@MyAnnotation(“zhangsan”)

3 屬性的類型可以是: byte short int long float double boolean char String Class 枚舉類型以及以上每一種的數組形式。

public @interface MyAnnotation {
    String name();
    /*
    年齡屬性
     */
    int age() default 25; //屬性指定預設值
}
           
public class MyAnnotationTest {
    //@MyAnnotation(屬性名=屬性值)
    @MyAnnotation(name = "zhangsan")
    public void doSome(){
    }
}
           

2.3 反射注解

讀取注解的class檔案,如果能被反射,則需要是@Retention(RetentionPolicy.RUNTIME):
public class ReflectAnnotationTest {
    public static void main(String[] args) throws Exception{
        // 擷取這個類
        Class c = Class.forName("com.bjpowernode.java.annotation5.MyAnnotationTest");
        // 判斷類上面是否有@MyAnnotation
        //System.out.println(c.isAnnotationPresent(MyAnnotation.class)); // true
        if(c.isAnnotationPresent(MyAnnotation.class)){
            // 擷取該注解對象
            MyAnnotation myAnnotation = (MyAnnotation)c.getAnnotation(MyAnnotation.class);
            //System.out.println("類上面的注解對象" + myAnnotation); // @com.bjpowernode.java.annotation5.MyAnnotation()
            // 擷取注解對象的屬性怎麼辦?和調接口沒差別。
            String value = myAnnotation.value();
            System.out.println(value);
        }

        // 判斷String類上面是否存在這個注解
        Class stringClass = Class.forName("java.lang.String");
        System.out.println(stringClass.isAnnotationPresent(MyAnnotation.class)); // false
    }
}
           
  • 通過反射擷取方法上的注解資訊
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
    /*
    username屬性
     */
    String username();
    /*
    password屬性
     */
    String password();
}
           
import java.lang.reflect.Method;
public class MyAnnotationTest {

    @MyAnnotation(username = "admin", password = "456456")
    public void doSome(){

    }
    public static void main(String[] args) throws Exception{
        // 擷取MyAnnotationTest的doSome()方法上面的注解資訊。
        Class c = Class.forName("com.bjpowernode.java.annotation6.MyAnnotationTest");
        // 擷取doSome()方法
        Method doSomeMethod = c.getDeclaredMethod("doSome");
        // 判斷該方法上是否存在這個注解
        if(doSomeMethod.isAnnotationPresent(MyAnnotation.class)) {
            MyAnnotation myAnnotation = doSomeMethod.getAnnotation(MyAnnotation.class);
            System.out.println(myAnnotation.username());
            System.out.println(myAnnotation.password());
        }
    }
}
           

2.4 注解在開發中的作用

1 假設有這樣一個注解,叫做:@Id

2 這個注解隻能出現在類上面,當這個類上有這個注解的時候,要求這個類中必須有一個int類型的id屬性。如果沒有這個屬性就報異常。如果有這個屬性則正常執行!

/*
自定義異常
 */
public class HasNotIdPropertyException extends RuntimeException {
    public HasNotIdPropertyException(){

    }
    public HasNotIdPropertyException(String s){
        super(s);
    }
}
           
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// 表示這個注解隻能出現在類上面
@Target(ElementType.TYPE)
// 該注解可以被反射機制讀取到
@Retention(RetentionPolicy.RUNTIME)
public @interface MustHasIdPropertyAnnotation {

}
// 這個注解@Id用來标注類,被标注的類中必須有一個int類型的id屬性,沒有就報異常。
           
@MustHasIdPropertyAnnotation
public class User {
    int id;
    String name;
    String password;
}
           
import java.lang.reflect.Field;

public class Test {
    public static void main(String[] args) throws Exception{
        // 擷取類
        Class userClass = Class.forName("com.bjpowernode.java.annotation7.User");
        // 判斷類上是否存在Id注解
        if(userClass.isAnnotationPresent(MustHasIdPropertyAnnotation.class)){
            // 當一個類上面有@MustHasIdPropertyAnnotation注解的時候,要求類中必須存在int類型的id屬性
            // 如果沒有int類型的id屬性則報異常。
            // 擷取類的屬性
            Field[] fields = userClass.getDeclaredFields();
            boolean isOk = false; // 給一個預設的标記
            for(Field field : fields){
                if("id".equals(field.getName()) && "int".equals(field.getType().getSimpleName())){
                    // 表示這個類是合法的類。有@Id注解,則這個類中必須有int類型的id
                    isOk = true; // 表示合法
                    break;
                }
            }

            // 判斷是否合法
            if(!isOk){
                throw new HasNotIdPropertyException("被@MustHasIdPropertyAnnotation注解标注的類中必須要有一個int類型的id屬性!");
            }

        }
    }
}