Java(L3)-- Junit、反射、注解

Junit、反射、注解

今天學(xué)習(xí)的是 Java 基礎(chǔ)加強(qiáng)部分的內(nèi)容,包括 Junit、反射、注解三部分內(nèi)容。

Part I. Junit

  1. 測試的分類:

    • 黑盒測試:只關(guān)注輸入與輸出的結(jié)果,不關(guān)注內(nèi)部實現(xiàn)的細(xì)節(jié)。
    • 白盒測試:既關(guān)注輸入與輸出的結(jié)果,又關(guān)注程序的執(zhí)行流程。
  2. Junit:白盒測試

    步驟:

    • 定義一個測試類:
      • 類名一般為 XxxxTest,如 CalculatorTest
      • 報名一般為 xxx.xxx.test,如
    • 定義測試方法
      • 方法名:testXxxx,如 testAdd()
      • 返回值:void
      • 參數(shù)列表:空參
    • 給測試方法加注解 @Test
    • 導(dǎo)入 junit 依賴包

    運行結(jié)果:

    • 紅色:測試方法運行發(fā)現(xiàn)錯誤

    • 綠色:測試方法運行未發(fā)現(xiàn)錯誤

    • 注:一般情況下,會用斷言操作來處理測試函數(shù)中得到的結(jié)果

      Assert.assertEquals(期望的結(jié)果,運算的結(jié)果);
      

    補(bǔ)充:

    • @Before:修飾的方法會在測試方法前自動執(zhí)行
    • @After:修飾的方法會在測試方法執(zhí)行后自動執(zhí)行

Part II. 反射

  1. Java代碼執(zhí)行的三個階段

    一般情況下,Java 代碼執(zhí)行過程分為三個階段:

    • Source 源代碼階段
    • Class 類對象階段
    • Runtime 運行時階段

    下面對這三個階段依次解釋:

    Source 階段

    一般而言,當(dāng)我們定義一個類時,會定義其成員變量構(gòu)造方法成員方法,并將其放在一個 .java 文件中。該文件經(jīng)過編譯后會生成一個 .class 文件,即字節(jié)碼文件,其中也包含了成員變量、構(gòu)造方法和成員方法三部分。Source 源代碼階段主要包含這兩個部分。

    Class 階段:

    字節(jié)碼文件經(jīng)過類加載器(ClassLoader)加載到內(nèi)存中,每個類經(jīng)過加載后對應(yīng)一個 Class 對象,該對象包括一個存放成員變量對象(field)的數(shù)組 Field [] fields;一個存放構(gòu)造方法對象(constructor)的數(shù)組 Constructor [] constructors;和一個用于存放成員方法對象(method)的數(shù)組 Method [] methods。

    Runtime 階段:

    內(nèi)存中的 Class 類對象可以直接創(chuàng)建類對象,即進(jìn)入 Runtime 階段。

  2. 反射

    反射即程序在運行時可以訪問、檢測和修改它本身狀態(tài)或行為的一種能力(維基百科)。在 Java 中,由 Source 階段和 Runtime 階段得到 Class 類對象并進(jìn)行各類操作的過程稱之為反射(我的理解)。

    得到 Class 對象的方法:

    • Class.forName("全類名"):將字節(jié)碼加載進(jìn)內(nèi)存,返回 Class 對象

      多用于配置文件,將類名、方法名等定義在配置文件中。

    • 類名.class:通過類名的屬性class獲取

    • 對象.getClass():由對象獲取 Class,getClass()方法在Object類中定義。

    注:同一個字節(jié)碼文件 (*.class) 在一次程序運行過程中,只會被加載一次,不論通過哪一種方式獲取的Class對象都是同一個。

  3. Class 對象的功能

  • 獲取成員變量:
    • Field[] getFields():獲取所有public修飾的成員變量
    • Field getField(String name):獲取指定名稱的 public修飾的成員變量
    • Field[] getDeclaredFields():獲取所有的成員變量
    • Field getDeclaredField(String name):獲取指定名稱的成員變量
  • 獲取構(gòu)造方法
    • Constructor<?>[] getConstructors():獲取所有public修飾的構(gòu)造函數(shù)
    • Constructor<T> getConstructor(Class<?>... parameterTypes):獲取指定名稱的 public 修飾的構(gòu)造函數(shù)
    • Constructor<?>[] getDeclaredConstructors():獲取所有構(gòu)造函數(shù)
    • Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):獲取指定名稱的構(gòu)造函數(shù)
  • 獲取成員方法
    • Method[] getMethods():獲取所有public修飾的成員方法
    • Method getMethod(String name, Class<?>... parameterTypes):獲取指定名稱的 public 修飾的成員方法
    • Method[] getDeclaredMethods():獲取所有的成員方法
    • Method getDeclaredMethod(String name, Class<?>... parameterTypes):獲取指定名稱的成員方法。
  • Field:成員變量
    • 設(shè)置值:void set(Object obj, Object value)
    • 獲取值:get(Object obj)
    • 忽略訪問權(quán)限修飾符的安全檢查:setAccessible(true),又稱暴力反射
  • Constructor:構(gòu)造方法
    • 創(chuàng)建對象:T newInstance(Object... initargs)
    • 創(chuàng)建空參對象也可以直接調(diào)用 Class 對象的 newInstance 方法
  • Method:方法對象
    • 執(zhí)行方法:Object invoke(Object obj, Object... args)
    • 獲取方法名稱:String getName()
  1. 案例

    要求:實現(xiàn)一個簡易框架,可以從配置文件讀取類名和方法名并執(zhí)行該類的該方法。

    public static void main(String[] args) throws Exception {
        //讀取配置文件并加載
        Properties pro = new Properties();
        ClassLoader classLoader = ReflectTest.class.getClassLoader();
        InputStream resourceAsStream = classLoader.getResourceAsStream("pro.properties");
        pro.load(resourceAsStream);
     //獲取類名和方法名
        String className = pro.getProperty("className");
        String methodName = pro.getProperty("methodName");
     //創(chuàng)建 Class 對象并創(chuàng)建實例
        Class<?> cls = Class.forName(className);
        Object o = cls.newInstance();
     //由方法名獲取成員方法并執(zhí)行
        Method method = cls.getMethod(methodName);
        method.invoke(o);
    }
    

Part III. 注解

注解(Annotation),也叫元數(shù)據(jù),一種代碼級別的說明。它是 JDK1.5 及以后版本引入的一個特性,與類、接口、枚舉是在同一個層次。它可以聲明在包、類、字段、方法、局部變量、方法參數(shù)等的前面,用來對這些元素進(jìn)行說明,注釋。

  1. 注解的作用分類

    • 編寫文檔:通過代碼里標(biāo)識的注解生成文檔
    • 代碼分析:通過代碼里標(biāo)識的注解對代碼進(jìn)行分析
    • 編譯檢查:通過代碼里標(biāo)識的注解讓編譯器能夠?qū)崿F(xiàn)基本的編譯檢查(如 Override )
  2. JDK 中預(yù)定義的一些注解

    • @Override:檢測被該注解標(biāo)注的方法是否是繼承自父類(接口)的
    • @Deprecated:該注解標(biāo)注的內(nèi)容,表示已過時
    • @SuppressWarnings:壓制警告。若傳遞的是 “all” 參數(shù),則壓制所有警告
  3. 自定義注解

    • 格式:
    元注解
    public @interface 注解名稱{
     屬性列表;
    }
    
    • 本質(zhì):注解本質(zhì)上就是一個接口,該接口默認(rèn)繼承Annotation接口
    public interface MyAnno extends java.lang.annotation.Annotation {}
    
    • 屬性:

      由于注解本身是一個對象,它的“屬性”實際上就是他的抽象函數(shù)。

      屬性的返回值類型有下列取值:

      • 基本數(shù)據(jù)類型
      • String
      • 枚舉
      • 注解
      • 以上類型的數(shù)組

      定義了屬性,在使用時需要給屬性賦值:

      • 如果定義屬性時,使用default關(guān)鍵字給屬性默認(rèn)初始化值,則使用注解時,可以不進(jìn)行屬性的賦值
      • 如果只有一個屬性需要賦值,并且屬性的名稱是value,則value可以省略,直接定義值即可
      • 數(shù)組賦值時,值使用{}包裹。如果數(shù)組中只有一個值,則{}可以省略
    • 元注解:用于描述注解的注解

      • @Target:描述注解能夠作用的位置

        其參數(shù) ElementType 的取值:

        • TYPE:可以作用于類上
        • METHOD:可以作用于方法上
        • FIELD:可以作用于成員變量上
      • @Retention:描述注解被保留的階段

        其參數(shù)一般取 RetentionPolicy.RUNTIME,會保留到字節(jié)碼中并且會被 JVM 獲取到

      • @Documented:描述注解是否被抽取到 api 文檔中

      • @Inherited:描述注解是否被子類繼承

    • 在程序中獲取注解的屬性值

      • 獲取注解所修飾的對象(Class,Method 或 Field)

      • 使用 getAnnotation(Class) 獲取制定的注解

        注:執(zhí)行該方法實際上就是在內(nèi)存中實現(xiàn)了該注解的子類對象

      • 使用注解的抽象函數(shù)獲取屬性值

    • 案例1:

      使用注解實現(xiàn) Part II 中的簡易框架:

      首先定義注解對象:

      @Retention(RetentionPolicy.RUNTIME)
      @Target(ElementType.TYPE)
      public @interface Pro {
          public abstract String className();
          public abstract String methodName();
      }
      

然后定義主類:

 ```java
 @Pro(className = "cn.dianshi.domain.Person", methodName = "sleep")
 public class ReflectTest {
     public static void main(String[] args) throws Exception {
         Class<ReflectTest> reflectTestClass = ReflectTest.class;
         Pro annotation = reflectTestClass.getAnnotation(Pro.class);
         String s = annotation.className();
         String s1 = annotation.methodName();
         System.out.println(s);
         System.out.println(s1);
 
         Class<?> aClass = Class.forName(s);
         Method method = aClass.getMethod(s1);
         Object o = aClass.newInstance();
         method.invoke(o);
     }
 }
 ```
  • 案例2:

    使用注解對以下類的方法進(jìn)行測試,如有錯誤則將錯誤信息寫在 bug.txt 中:

    Calculator.java:

    public class Calculator {
        //加法
        @Check
        public void add(){
            System.out.println("1 + 0 =" + (1 + 0));
        }
        //減法
        @Check
        public void sub(){
            System.out.println("1 - 0 =" + (1 - 0));
        }
        //乘法
        @Check
        public void mul(){
            System.out.println("1 * 0 =" + (1 * 0));
        }
        //除法
        @Check
        public void div(){
            System.out.println("1 / 0 =" + (1 / 0));
        }
    }
    

    實現(xiàn):

    首先定義 Check 注解:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Check {
    }
    

    然后編寫主類:

    public class TestCheck {
        public static void main(String[] args) throws Exception {
            Calculator calculator = new Calculator();
            Class<? extends Calculator> aClass = calculator.getClass();
            Method[] methods = aClass.getMethods();
            int count = 0;
            BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("bug.txt"));
            for (Method method : methods) {
                if(method.isAnnotationPresent(Check.class)){
                    try {
                        method.invoke(calculator);
                    } catch (Exception e) {
                        count ++;
                        bufferedWriter.write(method.getName() + "方法出現(xiàn)異常");
                        bufferedWriter.newLine();
                        bufferedWriter.write("異常名稱" + e.getCause().getClass().getSimpleName());
                        bufferedWriter.newLine();
                        bufferedWriter.write("異常的原因" + e.getCause().getMessage());
                        bufferedWriter.newLine();
                        bufferedWriter.write("--------------------");
                        bufferedWriter.newLine();
                    }
                }
            }
    
            bufferedWriter.write("一共出現(xiàn)" + count + "次異常");
    
            bufferedWriter.flush();
            bufferedWriter.close();
        }
    }
    
  • 注解小結(jié):

    • 多數(shù)情況下我們只是使用注解,只有極少數(shù)情況下才會自定義注解
    • 注解的服務(wù)對象是編譯器和解析程序
    • 注解不是程序的一部分,可以理解為注解就是一個標(biāo)簽
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容