Java-Junit、反射、注解(持续更新中)

1. Junit

1.1 概述

1、Junit的概念

  • Java语言编写的第三方单元测试框架。
  • 框架就是jar(类库)包的集合

2、Junit的作用

  • 用来测试类中的方法功能是否正确,保证程序的稳定性和有效性;
  • 是符合要求的方法独立运行。

3、单元测试的概念

  • 开发者编写的一小段代码用来对类中的方法功能进行测试(一个单元就是一个类)

1.2 使用步骤

  • 编写业务类
    • 在业务类中编写业务方法,实现某一功能的方法。
  • 编写测试类
    • 在测试类中编写测试方法,对业务类中的业务方法进行测试。

1.3 使用Junit测试的注意事项(重点)

  • 测试类的命名规范:以Test开头,以业务类类名结尾
  • 测试方法的要求:
    • 命名要求:以Test开头,以业务方法结尾
    • 声明要求:必须是public修饰的,必须没有返回值,必须没有参数,必须使用@Test注释

1.4 运行测试方法的几种方式

1.4.1 运行方式

  1. 选择测试方法名=>右键=>“Run 测试方法名”:只运行选中的测试方法
  2. 选择测试类名=>右键=>“Run 类名”:运行该测试类下的所有测试方法
  3. 选择测试模块=>右键=>“Run ‘All Tests’”:运行该模块下的所有测试类的所有测试方法

1.4.2 运行测试结果

  • 绿色:表示测试通过
  • 红色:表示失败或出现错误

1.5 Junit常用注解

  • @Before:初始化的方法,被修饰的测试方法会在每一个测试方法执行之前执行一次
  • @After:被修饰的测试方法会在每一个测试方法执行之后执行一次
  • @BeforeClass:在所有的测试方法运行之前,运行一次,而且必须修饰静态方法
  • @AfterClass:所有的测试方法运行以后,运行一次,必须修饰静态方法

注意,@BeforeClass和@AfterClass修饰静态方法,所以就算只调用一个其他测试方法都会调用

另外,Junit4和Junit5的区别仅是部分注解名字有所调整,实际使用一样。部分注解调整如下:

Junit4 Junit5
@Before @BeforeEach
@After @AfterEach
@BeforeClass @BeforeAll
@AfterClass @AfterAll

1.6 测试方法中的某些常用方法

1、断言:预先判断某个条件一定成立,如果不成立则程序直接崩溃

void assertEquals(String message, Object expected, Object actual)

其中:message: 异常消息提示字符串(期望值和实际值不同时报错并在控制台返回该字符串)
expected:期望值
actual:实际值

注意:该方法只能用在业务方法有返回值的情况。

2. 反射

2.1 概念引入

  • 反射:一种可在程序运行过程中对类进行解剖并操作类中的方法、属性、构造方法等所有成员的机制。
    • 构造方法:通过反射操作构造方法创建类的对象
    • 成员方法:通过反射操作成员方法来调用方法
    • 成员变量:通过反射操作成员变量(给该成员变量赋值和取值)

代码编译成.class文件,程序运行加载该.class文件时解剖该类,获取的所有数据,并为.class文件创建一个Class对象封装获得的数据(在内存的堆内)

  • 使用反射机制解剖类的前提
    • 必须获得该类的字节码文件对象,即Class类型对象

任何一个class文件都是Class类的示例对象

2.2 获得Class对象的三种方式

  • 方式一:类名.class
//比如
Class c = Car.class;//Car是一个类的类名
  • 方式二:通过对象调用getClass()方法获取(该方法是Object类的方法)
//比如
Car car = new Car();
Class c = car.getClass();
  • 方式三:通过Class.forName("全限定类名")方法获取
//比如
Class c = Class.forName("java.lang.String");

2.3 获得Class对象的信息

  • String getSimpleName();:获得类名字符串;
  • String getName();:获得类全名字符串,即包名+类名;
  • T newInstance();:创建此Class对象所表示的类的一个新实例。但必须保证该类有共有的无参构造(1.9已过时)

2.4 获取Class对象的Constructor信息(操作构造方法)

2.4.1 Constructor类概述

  • Constructor是构造方法类,类中的每一个构造方法都是Constructor的对象
  • 反射操作构造方法就是要获得对应的Constructor对象类创建该类的对象

2.4.2 Class类中与Constructor相关方法

- Constructor getConstructor(Class...parameterTypes);
    - 根据参数类型获取构造方法对象,只能获得public修饰的构造方法。若不存在对应构造方法,则抛异常
- Constructor getDeclaredConstructor(Class...parameterTypes);
    - 根据参数类型获取构造方法对象,获得包括private修饰的构造方法。若不存在对应构造方法,则抛异常
- Constructor[] getConstructors();
    - 获取所有的public修饰的构造方法
- Constructor[] getDeclaredConstructors();
    - 获取所有构造方法,包括private修饰的

示例代码:

/*
Class...parameterTypes里面放的是构造方法的成员变量对应的Class类对象。比如学生类有一个构造方法:
*/
public Student(String name,String gender){
    this.name = name;
    this.gender = gender;
}
/*
则Class...parameterTypes里面是String.class,String.class,如下:
*/
//创建构造方法对象
Constructor cons = c.getConstructor(String.class,String.class);

2.4.3 Constructor类中常用方法

- T newInstance(Object...initargs):根据指定参数创建对象
- void setAccessible(true):暴力反射,设置是否取消权限检查。true为取消权限检查,默认为false

示例代码1:

/*接上例*/
//根据构造方法对象创建实例
Object stu = cons.newInstance("Jack","男");

示例代码2:private的构造方法

private Student(String name,int age){
    this.name = name;
    this.age = age;
}
//私有的方法需要用getDeclaredConstructor()获得构造方法对象
Constructor cons = c.getDeclaredConstructor(String.class,Integer.class);
/*如果需要根据私有构造方法对象创建的实例,必须在调用创建实例前暴力反射一下*/
cons.setAccessible(true);
Object stu = cons.newInstance("Jack","男");

2.5 获取Class对象的Method信息(操作成员方法)

2.5.1 Method类概述

  • 类中的每一个方法都是Method的对象
  • 通过Method对象可以调用方法。(包括getter&setter、普通成员方法、静态方法)

2.5.2 Class类中与Method相关方法

- Method getMethod(String methodName,Class...params)
    - 根据方法名和参数类型获取一个方法对象,只能是public修饰的
- Method getDeclaredMethod(String methodName,Class...params)
    - 根据方法名和参数类型获取一个方法对象,包括private修饰的
- Method[] getMethods():获取所有public修饰的成员方法,包括父类的
- Method[] getDeclaredMethods():获取当前类中所有的方法,包括私有的,不包括父类的

2.5.3 Method类中常用方法

- Object invoke(Object obj, Object... args)
    obj:调用方法的对象。若为null,则表示为静态方法(静态方法的obj也可以是Class对象)
    args:对象obj调用该成员方法所传入的参数列表
- void setAccessible(boolean flag)
    暴力反射,设置是否取消权限检查。true为取消权限检查,可调用私有修饰的成员方法;默认为false。由method对象调用

2.6 获取Class对象的Field信息(操作成员变量)

2.6.1 Field类概述

  • Field是属性类,类中的每一个属性(成员变量)都是Field的对象,
  • 通过Field对象可以给对应的成员变量赋值和取值。

2.6.2 Class类中与Field相关方法

- Field getField(String name)
    根据属性名获得属性对象,只能获取public修饰的
- Field getDeclaredField(String name)
    根据属性名获得属性对象,包括private修饰的
- Field[] getFields()
    获取所有的public修饰的属性对象,返回数组。
- Field[] getDeclaredFields()
    获取所有的属性对象,包括private修饰的,返回数组。

2.6.3 Field类中常用方法

//Set类型的方法
void set(Object obj, Object value)//==>引用类型
void setInt(Object obj, int i)
void setLong(Object obj, long l)
void setBoolean(Object obj, boolean z)
void setDouble(Object obj, double d)
//Get类型的方法
Object get(Object obj)//==>引用类型
int getInt(Object obj)
long getLong(Object obj)
boolean getBoolean(Object ob)
double getDouble(Object obj)
//暴力反射
void setAccessible(true);暴力反射,设置是否取消权限检查。true为取消权限检查;默认为false。Field对象调用。

Class getType(); 获取属性的类型,返回Class对象。
String getName(); 获得成员变量的名称。Field对象调用

2.7 综合案例

已知有两个properties文件:

//文件名为book.properties
class = day14projects.src.studynotes.Notes_Reflect.Book
bookname = Harry Potter
price = 698.6
author = J.K. Rowling
//文件名为stu.properties
class = day14projects.src.studynotes.Notes_Reflect.Student
name = JoJo
age = 18
stand = STAR PLATINUM

以及两个对应的类:

public class Book {
    private String bookname;
    private double price;
    private String author;

    public Book() {
    }

    public Book(String bookname, double price, String author) {
        this.bookname = bookname;
        this.price = price;
        this.author = author;
    }

    @Override
    public String toString() {
        return "Book{" +
                "bookname='" + bookname + '\'' +
                ", price=" + price +
                ", author='" + author + '\'' +
                '}';
    }
    /*此处省略getter&setter,实际是有的*/
}
public class Student {
    private String name;
    private int age;
    private String stand;

    public Student() {
    }

    public Student(String name, int age, String stand) {
        this.name = name;
        this.age = age;
        this.stand = stand;
    }

    //成员方法
    public static void useStand() {
        System.out.println("白金之星!!!欧拉欧拉欧拉欧拉欧拉欧拉欧拉欧拉欧拉欧拉欧拉欧拉欧拉欧拉!!!!!");
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", stand='" + stand + '\'' +
                '}';
    }
     /*此处省略getter&setter,实际是有的*/
}

需求1:使用反射根据properties文件的信息创建对应的类的实例

需求2:使用反射创建Student对象,并调用成员方法

public class ReflectDemo {
    public static void main(String[] args) throws Exception {
        //需求1
        Student stu = createObject("day14projects/stu.properties");
        Book book = createObject("day14projects/book.properties");
        System.out.println(stu);
        System.out.println(book);

        //需求2

    }
    /*
    需求1:可以用泛型创建实例,这时候只能给成员变量赋值,而不能调用方法。原因是两个类的成员变量的数据类型不一样,无法统一构造方法。而且成员方法也不同,无法用泛型代替。
    */
    //将创建对象功能抽取成方法
    public static <T> T createObject(String pathname) throws Exception {
        //创建Properties对象,用来接收数据
        Properties pro = new Properties();
        //创建输入流
        try (FileInputStream fis = new FileInputStream(pathname)) {
            //根据输入流获取数据加载到Pro中
            pro.load(fis);
        //数据获取成功之后可以就不用输入流了,可以直接关闭

        } catch (IOException e) {
            e.printStackTrace();
        }

        //获得类全名,该类全名已保存在properties文件中
        String className = pro.getProperty("class");
        //根据类全名获得Class对象
        Class c = Class.forName(className);
        //创建对象
        Object obj = c.newInstance();//为了运用泛型,不需要强转

        //获得类中的所有成员变量(包括私有变量),并用数组保存
        Field[] fields = c.getDeclaredFields();
        //遍历数组
        for (Field field : fields) {
            //获得成员变量名称
            String name = field.getName();
            //获得对应的值
            String value = pro.getProperty(name);
            /*注意:接下来要进行暴力反射才能为私有变量赋值*/
            field.setAccessible(true);

            //获得成员变量的类型
            Class type = field.getType();
            if (type == int.class) {    //如果是int类型的数据,就把value值赋给obj
                field.setInt(obj, Integer.parseInt(value));
            } else if (type == double.class) {    //如果是double类型的数据,就把value值赋给obj
                field.setDouble(obj, Double.parseDouble(value));
            } else {    //如果还有其他基本数据类型的话还要细分
                //如果是字符串,给obj赋值
                field.set(obj, value);
            }
        }
        return (T)obj;//因为是创建子类对象,所以要根据传入的具体类型强转
    }
    /*
    需求2:单独创建一个方法来创建Student对象和调用成员方法
     */
    public static void applyStudent(String pathname) throws Exception {
        //创建Properties对象,用来接收数据
        Properties pro = new Properties();
        //创建输入流
        FileInputStream fis = new FileInputStream(pathname);
        //根据输入流获取数据加载到Pro中
        pro.load(fis);
        //数据获取成功之后可以就不用输入流了,可以直接关闭
        fis.close();
        //获得类全名,该类全名已保存在properties文件中
        String className = pro.getProperty("class");
        //根据类全名获得Class对象
        Class c = Class.forName(className);

        /*
        上述的代码与需求1的一致,下面用构造方法对象创建对象
         */
        Constructor<Student> cons = c.getDeclaredConstructor(String.class,int.class,String.class);//使用有参构造方法
        //从文件中获得数据再根据构造方法对象创建对象。操作比较繁琐
        Field[] fields = c.getDeclaredFields();
        String name = pro.getProperty(fields[0].getName());
        int age = Integer.parseInt(pro.getProperty(fields[1].getName()));
        String stand = pro.getProperty(fields[2].getName());
        Student stu = cons.newInstance(name,age,stand);

        //或者这种直接创建并强转成Student类型
        Student stu2 = (Student)c.newInstance();

        /*
        使用反射调用成员方法
         */
        //获取方法对象
        Method method = c.getDeclaredMethod("useStand", null);//方法名为useStand,方法无参
        method.invoke(stu2,null);//第一个参数是调用方法的对象,后面一个是参数列表

    }
}

3.注解

3.1 注解的概述

3.1.1 概念

  • 1.5新特性
  • 注解是一种标记,作为类的一个组成部分,为类携带额外的信息。
  • 注解可加在包,类,字段,方法,方法参数以及局部变量上。
  • 编译器或JVM根据注解完成对应的功能

3.1.2 注解的作用

  • 注解的作用就是给程序带入参数

    1. 生成帮助文档:@author(标识作者姓名)和@version(标识对象的版本号)

    2. 编译检查:

    比如:@Override(用来修饰方法声明,标明该方法时重写父类中的方法。若父类不存在该方法则编译失败)

    1. 框架配置(框架=代码+配置文件)

常用的注解是上面三个

3.2 自定义注解

3.2.1 定义格式

public @interface 注解名{

}
  • 上述定义的就是最简单的注解,但一般要添加属性
  • 注解名按大驼峰命名

3.2.2 注解的属性

  1. 属性的作用
    • 可以让用户在使用注解时传递参数,让注解的功能更加强大。
  2. 属性的格式
    • 格式1:数据类型 属性名();
    • 格式2:数据类型 属性名() default 默认值;
  3. 属性适用的数据类型
    • 八种基本数据类型(int,float,boolean,byte,double,char,long,short)
    • String类型,Class类型,枚举类型,注解类型
    • 以上所有类型的一维数组形式

3.2.2 使用自定义注解

以示例代码为例:

//自定义注解
public @interface Book {
    // 书名
    String value();
    // 价格
    double price() default 100;
    // 多位作者
    String[] authors();
}
//使用注解
public class BookShelf {
    @Book(value = "Harry Potter",price = 698,authors = {"JK 罗琳","邓布利多"})
    public void showBook(){
    }
}
  • 属性有默认值,使用注解时可不用给该属性赋值
  • 属性无默认值,则在注解时必须给该属性赋值

3.2.3 特殊属性value

  • 当注解只有一个属性且名称为value,使用注解是给value赋值可直接写属性值(eg:"Harry Potter",而可以不写成value = "Harry Potter"
  • 当注解中除了value还有至少一个属性被赋值时,value属性名不能省略(除非value的其他属性有默认值且不赋值,此时也可以省略value属性名)

3.3 元注解

3.3.1 概述

  • Java API提供的注解
  • 专门用来定义注解的注解。
  • 任何Java官方提供的非元注解的定义中都使用到了元注解。

3.3.2 常用元注解

  • @Target
  • @Retention
  • @Inherited(了解):用来标明该注解可以被子类继承

3.3.3 元注解@Target

  • 作用:指明此注解用在哪个位置,如果不写默认是任何地方都可以使用。
//可选的参数值在枚举类ElemenetType中包括:
TYPE: 用在类,接口上
FIELD:用在成员变量上
METHOD: 用在方法上
PARAMETER:用在方法参数上
CONSTRUCTOR:用在构造方法上
LOCAL_VARIABLE:用在局部变量上

3.3.4 元注解@Retention

  • 作用:定义该注解的生命周期(有效范围)
//可选的参数值在枚举类RetentionPolicy中包括:
SOURCE:注解只存在于Java源代码中,编译生成字节码文件的阶段就不存在了。
CLASS:注解存在于Java源代码、编译生成的字节码文件中,运行阶段不存在。(不指定的话默认存活到这个阶段)
RUNTIME:注解存在于Java源代码中、编译以后的字节码文件中、运行阶段中,程序可以通过反射获取该注解。

3.4 注解解析

  • 通过Java技术获取注解属性信息的过程

3.4.1 与注解解析相关的类和接口

  • Anontation:所有注解类型的“父类”(已默认),类似所有类的父类是Object。
  • AnnotatedElement:定义了与注解解析相关的方法。如下:
- boolean isAnnotationPresent(Class annotationClass);
    - 判断当前对象是否有指定的注解,有返回true,否则false
- T getAnnotation(Class<T> annotationClass);
    - 获得当前对象上指定的注解对象
- Annotation[] getAnnotations(); 
    - 获得当前对象即其从父类上继承的所有的注解对象
- Annotation[] getDeclaredAnnotations(); 
    - 获得当前对象上所有的注解对象,不含父类

3.4.2 获取注解数据的原则

注解作用在哪个成员上就获得该成员对应的对象去获得注解信息
  • 如注解作用在成员方法上,就通过成员方法(Method)对象得到它的注解

    // 得到方法对象
    Method method = clazz.getDeclaredMethod("方法名");
    // 根据注解名得到方法上的注解对象
    Book book = method.getAnnotation(Book.class);
  • 如注解作用在成员变量上,就通过属性(Field)对象得到它的注解

  • 如注解作用在构造方法上,就通过构造方法(Constructor)对象得到它的注解

  • 如注解作用在类上,就通过Class对象得到它的注解

    // 获得Class对象
    Class c = 类名.class;
    // 根据注解的Class获得使用在类上的注解对象
    Book book = c.getAnnotation(Book.class);

注解解析须知:Class、Method、Field、Constructor等类都是实现了AnnotatedElement接口!

猜你喜欢

转载自blog.csdn.net/KeepStruggling/article/details/81948090