1. 程式人生 > >Java:Junit、反射、註解

Java:Junit、反射、註解

1、Junit單元測試-Junit概述

1)、單元測試:在開發中編寫一個功能,而這個功能需要啟動系統,並經過一系列的操作才能達到這個功能,這樣為了測試這個功能就帶來了麻煩。“單元測試”是指將“一部分”程式碼進行單獨測試。可以大大提高程式測試的效率

2)、JUnit是第三方公司實現的一個“單元測試”的工具包,是基於“註解”的。

2、Junit單元測試-Junit的使用

編寫程式碼後,在需要單元測試的方法之上加上@Test

在這裡插入圖片描述

3、Junit單元測試[email protected]註解

1)、@Test註解:Junit中用於測試方法的註解,可以在想要測試的方法上新增此註解

2)、在意個類中可以定義多個使用@Test註解的測試方法,每個方法可以獨立執行,也可以一起執行(按照方法名升序

排列執行)

public class Student {//右鍵-->Run Student
    @Test
    public void show2(){//右鍵-->Run show2
        System.out.println("show2");
    }
    @Test
    public void show3(){
        System.out.println("show3");
    }
    @Test
    public void show1(){
        System.out.println("show1");
    }
}

注意:測試方法必須是公有、無參、無返回值、非靜態的方法。

4、Junit單元測試-其他註解

1)、@Before:在@Test方法之前執行,可以定義多個@Before方法,在每個@Test方法執行前,所有@Before都會以方法名倒敘執行

2)、@After:在@Test方法之後執行,可以定義多個@After方法,在每個@Test方法執行之後,所有@After都會以方法名升序執行

3)、@BeforeClass:用於測試靜態方法。在所有的註解之前只執行一次

4)、@AfterClass:用於測試靜態方法。在所有的註解之後只執行一次

public class Student {
    @BeforeClass
public static void bc(){ System.out.println("before class...."); } //-----------------------------------------------// @Before public void b1(){ System.out.println("b1"); } @Before public void b2(){ System.out.println("b2"); } @Before public void b3(){ System.out.println("b3"); } //-----------------------------------------------// @Test public void show2(){ System.out.println("show2"); } @Test public void show3(){ System.out.println("show3"); } @Test public void show1(){ System.out.println("show1"); } //-----------------------------------------------// @After public void a1(){ System.out.println("a1"); } @After public void a2(){ System.out.println("a2"); } @After public void a3(){ System.out.println("a3"); } //-----------------------------------------------// @AfterClass public static void ac(){ System.out.println("after class...."); } }

5、反射-反射的概念及演示

反射:“反向載入”要使用的“類”,之前使用某個類都是直接建立物件(存在依賴,存在依賴就會有問題存在,被依賴的類出現修改,那麼依賴者就也要更改自己的程式碼)

class Demo{
    public static void main(String[] args){
        Student stu = new Student();//正向載入
    }
}
//使Demo類產生了對Student類的依賴,如果Student類發生修改,Demo類就需要修改。

反射就可以很好的解決這個問題

例:(模擬遊戲更新地圖演示)

//舊版本地圖:(當需要更新地圖時就要重新建立一個地圖類,那麼測試類中也必須要對地圖類進行更改,很麻煩)
public class Game {
    public void paoTu(){
        System.out.println("第一個版本的跑圖....");
    }
}
//新版本的地圖
public class Game2 {
    public void paoTu2(){
        System.out.println("第二個版本的跑圖....");
    }
}

測試類:(使用反射,脫離依賴)

public class Demo {
    public static void main(String[] args) throws Exception {
        /*Game2 g = new Game2();//更新一次地圖就要在這裡修改一次,麻煩
        g.paoTu2();*/
        
        Class c = Class.forName(getValue("className"));
        Object o = c.getConstructor().newInstance();
        Method m = c.getDeclaredMethod(getValue("methodName"));
        m.invoke(o);
    }

    //編寫方法,根據某個鍵,讀取它對應的值
    public static String getValue(String key) {
        Properties pro = new Properties();
        try (FileReader in = new FileReader("game.txt")) {
            pro.load(in);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return pro.getProperty(key);
    }
}

在專案根目錄下建立game.txt檔案,內容如下:

className = cn.itheima.demo03_反射的概念及演示.Game2
methodName = paoTu2

這樣就使用反射避免了測試類對地圖類的依賴,當有新的地圖進行修改時,只需要對game.txt檔案內容進行修改即可

6、反射-執行時class檔案的載入

在這裡插入圖片描述

注意:JVM在第一次使用任何類時,都會在記憶體中建立一個這個類的Class物件,而針對每個類,都只有一個Class物件

反射工作機制:

在這裡插入圖片描述

7、反射-獲取Class物件的物件的三種方式

1)、getClass()方法-Object類中定義的;

2)、任何資料型別(基本型別、引用型別)都有一個靜態屬性:class

3)、Class類的靜態方法:forName(全名限定的類名);比較常用

注意:以上三種獲取的方式都是先判斷此class是否存在,不存在就建立

程式碼:

public static void main(String[] args) throws ClassNotFoundException {
    //1.getClass()
    Student stu = new Student();
    Class c1 = stu.getClass();

    //2.任何的"資料型別(基本型別,引用型別",都有一個靜態的 屬性:class)
    Class c2 = Student.class;

    //3.使用Class的靜態方法:forName(全名限定的類名)
    Class c3 = Class.forName("cn.itheima.demo05_獲取Class物件的三種方式.Student");

    System.out.println(c1 == c2);//true
    System.out.println(c1 == c3);//true
}

Class物件方法拓展:

String getSimpleName(); //獲得簡單類名,只是類名,沒有包
String getName(); //獲取完整類名,包含包名+類名
T newInstance() ;//建立此 Class 物件所表示的類的一個新例項。要求:類必須有public的無引數構造方法

8、反射-獲取構造方法並建立物件

  1. Constructor getConstructor(Class... parameterTypes) 根據引數型別獲取構造方法物件,只能獲得public修飾的構造方法。 如果不存在對應的構造方法,則會丟擲 java.lang.NoSuchMethodException 異常。

  2. Constructor getDeclaredConstructor(Class... parameterTypes) 根據引數型別獲取構造方法物件,包括private修飾的構造方法。 如果不存在對應的構造方法,則會丟擲 java.lang.NoSuchMethodException 異常。

  3. Constructor[] getConstructors() 獲取所有的public修飾的構造方法

  4. Constructor[] getDeclaredConstructors() 獲取所有構造方法,包括privat修飾的(公有、受保護、預設、私有)

示例程式碼:

public static void main(String[] args) throws Exception {
    //1.獲取Student的Class物件
    Class c = Class.forName(
		"cn.itheima.demo06_反射_獲取構造方法並建立物件.Student");

    //2.獲取所有構造方法
    Constructor[] cArray = c.getDeclaredConstructors();
    for (Constructor con : cArray) {
        System.out.println(con);
    }

    //3.獲取某個構造方法:
    System.out.println("獲取公有、無參的構造方法:");
    Constructor con = c.getDeclaredConstructor();

    //4.建立物件
    Object obj = con.newInstance();//相當於:Object obj = new Student();


    System.out.println("獲取私有、double和String引數的構造方法:");
    Constructor con2 = c.getDeclaredConstructor(double.class, String.class);
    //由於是私有的構造方法,所以要先設定暴力訪問
    con2.setAccessible(true);

    Object obj2 = con2.newInstance(3.14, "張學友");
}

Constructor類中常用方法:

1. T newInstance(Object... initargs);//根據指定引數建立物件。
2. void setAccessible(true);//暴力反射,設定為可以直接訪問私有型別的構造方法。

9、反射-獲取成員屬性並賦值和取值

Field是屬性類,類中的每一個屬性(成員變數)都是Field的物件,通過Field物件可以給對應的成員變數賦值和取值。

  1. Field getDeclaredField(String name) 根據屬性名獲得屬性物件,包括private修飾

  2. Field getField(String name) 根據屬性名獲得屬性物件,只能獲取public修飾

  3. Field[] getFields() 獲取所有的public修飾的屬性物件,返回陣列。

  4. Field[] getDeclaredFields() 獲取所有的屬性物件,包括private修飾的,返回陣列。

示例程式碼:

1、Student類:

public class Student {
    public String name;
    protected int age;
    String sex;
    private String address;

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}

2、測試類:

public static void main(String[] args) throws Exception {

    //1.獲取Class
    Class c = Class.forName(
		"cn.itheima.demo07_反射_獲取成員屬性並賦值和取值.Student");
    //建立兩個Student物件
    Object obj1 = c.getDeclaredConstructor().newInstance();
    Object obj2 = c.getDeclaredConstructor().newInstance();
    //2.獲取所有屬性
    Field[] fields = c.getDeclaredFields();
    for (Field f : fields) {
        System.out.println(f);
    }
    System.out.println("------------------------------");
    //3.獲取某個屬性
    System.out.println("獲取name屬性:");
    Field f = c.getDeclaredField("name");

    System.out.println("獲取address屬性:");
    Field f2 = c.getDeclaredField("address");

    System.out.println("為第一個Student物件的name賦值為:劉德華,address賦值為:香港:");
    f.set(obj1, "劉德華");
    //私有的要先設定暴力訪問
    f2.setAccessible(true);
    f2.set(obj1,"香港");


    System.out.println("為第二個Student物件的name賦值為:章子怡,address賦值為:北京:");
    f.set(obj2, "章子怡");
    //設定暴力訪問
    f2.setAccessible(true);
    f2.set(obj2,"北京");


    System.out.println(obj1);
    System.out.println(obj2);
}

10、反射-獲取成員方法並呼叫

  1. Method getMethod("方法名", 方法的引數型別... 型別) 根據方法名和引數型別獲得一個方法物件,只能是獲取public修飾

  2. Method getDeclaredMethod("方法名", 方法的引數型別... 型別) 根據方法名和引數型別獲得一個方法物件,包括private修飾

  3. Method[] getMethods() (瞭解) 獲取所有的public修飾的成員方法,包括父類中。(成員方法)

  4. Method[] getDeclaredMethods() (瞭解) 獲取當前類中所有的方法,包含私有的,不包括父類中。(成員方法)

示例程式碼:

1、Student類:

public class Student {
    public void show1(){
        System.out.println("公有、無參、無返回值的show1()...");
    }

    private int show2(String a, int b) {
        System.out.println("私有、有參,有返回值的show2()...");
        return 20;
    }
}

2、測試類:

public static void main(String[] args) throws Exception {
    //1.先獲取Class物件
    Class c = Class.forName(
			"cn.itheima.demo08_反射_獲取成員方法並呼叫.Student");
    //2.建立物件
    Object obj = c.getDeclaredConstructor().newInstance();

    //3.獲取所有方法
    Method[] methods = c.getDeclaredMethods();
    for (Method m :methods) {
        System.out.println(m);
    }

    System.out.println("獲取公有、無參、無返回值的show1()");
    Method m = c.getDeclaredMethod("show1");

    //呼叫方法--必須要先建立物件
    m.invoke(obj);

    System.out.println("獲取私有、有參、有返回值的show2()");
    Method m2 = c.getDeclaredMethod("show2", String.class, int.class);

    //呼叫方法
    m2.setAccessible(true);
    Object result = m2.invoke(obj, "劉德華", 20);
    System.out.println("返回值:" + result);
}

Method類中常用方法:

1. Object invoke(Object obj, Object... args);//根據引數args呼叫物件obj的該成員方法,如果obj=null,則表示該方法是靜態方法
2. void setAccessible(boolean flag);//暴力反射,設定為可以直接呼叫私有修飾的成員方法

11、註解-註解的概念及作用

1)、在程式碼中我們曾看到@Override,@FunctionalInterface就是“註解”。

2)、作用:用在“原始碼”中,做“標記”的。用來告訴“編譯器”怎樣編譯“下面的程式碼”。也可以像JUnit的@Test註解一樣,單獨啟動一個“註解解析器”,讀取註解,並做一些事情。

註解(Annotation)相當於一種標記,在程式中加入註解就等於為程式打上某種標記,以後,javac編譯器、開發工具和其他程式可以通過反射來了解你的類及各種元素上有無何種 標記,看你的程式有什麼標記,就去幹相應的事,標記可以加在包、類,屬性、方法,方法的引數以及區域性變數上。

12、註解-JDK內部註解

1)、@Override(重寫)

class Student{
@Override
    public String toString(String s){
        return "...";
    }
}

2)、@FunctionalInterface(定義函式式介面)

@FunctionalInterface
interface IA{
    //必須要有,且只有一個抽象方法
    public void show();
}

13、註解-自定義註解的基本格式

1)、自定義註解格式:

public @interface 註解名{}

2)、給自定義註解新增屬性

  • 格式1:資料型別 屬性名();
  • 格式2:資料型別 屬性名() default 預設值;
public @interface Student {
String name(); // 姓名
int age() default 18; // 年齡
String gender() default "男"; // 性別
}
// 該註解就有了三個屬性:name,age,gender

例:

public @interface Book {
    // 書名
	String value();
	// 價格
	double price() default 100;
	// 多位作者
	String[] authors();
}

編寫“註解解析器”,就可以解析註解,然後進行使用

14、註解-元註解

1)、“元註解”也是一種註解,但是隻能用在定義註解上,他規定了定義的註解可以用在什麼位置(約束自定義註解使用的位置)

2)、元註解分為倆類:

@Target:規定註解可以用在哪些位置:(可以選擇的值被定義在:java.lang.annotation.ElementType列舉中。

@Target(ElementType.TYPE)//可以用在類,介面中
@Target(ElementType.FIELD)//可以用在欄位上(成員屬性)
@Target(ElementType.METHOD)//可以用在方法上
@Target(ElementType.PARAMETER)//可以用在引數上
@Target(ElementType.CONSTRUCTOR)//可以用在構造方法是哪個
@Target(ElementType.LOCAL_VARIABLE)//用在區域性變數上.

@Retention:規定註解會存在於哪裡:(可以選擇的值被定義在:java.lang.annotaion.RetentionPolicy列舉中。

@Retention(RetentionPolicy.SOURCE)//註解只存在於“原始碼中”,給編譯器看的。例如:@Override,@FunctionalInterface
@Retention(RetentionPolicy.CLASS)//註解會存在於原始碼、class中,
給編譯器看的。當JVM執行此類時,不會將註解資訊讀取到記憶體。
@Retention(RetentionPolicy.RUNTIME)//註解會存在於原始碼、class、記憶體中給執行時的“註解解析器”看的。像JUnit的@Test

例:

將註解定義為只能用在方法上,而且要能夠進入到記憶體:

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

15、註解-解析註解

  • Anontation:所有註解型別的公共介面,類似所有類的父類是Object。

  • AnnotatedElement:定義了與註解解析相關的方法,常用方法以下四個:

    boolean isAnnotationPresent(Class annotationClass); 判斷當前物件是否有指定的註解,有則返回true,否則返回false。
    T getAnnotation(Class<T> annotationClass); 獲得當前物件上指定的註解物件。
    Annotation[] getAnnotations(); 獲得當前物件及其從父類上繼承的所有的註解物件。
    Annotation[] getDeclaredAnnotations();獲得當前物件上所有的註解物件,不包括父類的。
    

獲取註解資料:

註解作用在那個成員上,就通過反射獲得該成員的物件來得到它的註解。

  • 如註解作用在方法上,就通過方法(Method)物件得到它的註解

    // 得到方法物件
    Method method = clazz.getDeclaredMethod("方法名");
    // 根據註解名得到方法上的註解物件
    Book book = method.getAnnotation(Book.class);
    
  • 如註解作用在類上,就通過Class物件得到