Junit、反射、注解
今天學(xué)習(xí)的是 Java 基礎(chǔ)加強(qiáng)部分的內(nèi)容,包括 Junit、反射、注解三部分內(nèi)容。
Part I. Junit
-
測試的分類:
- 黑盒測試:只關(guān)注輸入與輸出的結(jié)果,不關(guān)注內(nèi)部實現(xiàn)的細(xì)節(jié)。
- 白盒測試:既關(guān)注輸入與輸出的結(jié)果,又關(guān)注程序的執(zhí)行流程。
-
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. 反射
-
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 階段。
-
反射
反射即程序在運行時可以訪問、檢測和修改它本身狀態(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對象都是同一個。
-
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()
-
案例
要求:實現(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)行說明,注釋。
-
注解的作用分類
- 編寫文檔:通過代碼里標(biāo)識的注解生成文檔
- 代碼分析:通過代碼里標(biāo)識的注解對代碼進(jìn)行分析
- 編譯檢查:通過代碼里標(biāo)識的注解讓編譯器能夠?qū)崿F(xiàn)基本的編譯檢查(如 Override )
-
JDK 中預(yù)定義的一些注解
- @Override:檢測被該注解標(biāo)注的方法是否是繼承自父類(接口)的
- @Deprecated:該注解標(biāo)注的內(nèi)容,表示已過時
- @SuppressWarnings:壓制警告。若傳遞的是 “all” 參數(shù),則壓制所有警告
-
自定義注解
- 格式:
元注解 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)簽