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 运行方式
- 选择测试方法名=>右键=>“Run 测试方法名”:只运行选中的测试方法
- 选择测试类名=>右键=>“Run 类名”:运行该测试类下的所有测试方法
- 选择测试模块=>右键=>“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 注解的作用
注解的作用就是给程序带入参数
生成帮助文档:@author(标识作者姓名)和@version(标识对象的版本号)
编译检查:
比如:@Override(用来修饰方法声明,标明该方法时重写父类中的方法。若父类不存在该方法则编译失败)
- 框架配置(框架=代码+配置文件)
常用的注解是上面三个
3.2 自定义注解
3.2.1 定义格式
public @interface 注解名{
}
- 上述定义的就是最简单的注解,但一般要添加属性
- 注解名按大驼峰命名
3.2.2 注解的属性
- 属性的作用
- 可以让用户在使用注解时传递参数,让注解的功能更加强大。
- 属性的格式
- 格式1:
数据类型 属性名();
- 格式2:
数据类型 属性名() default 默认值;
- 格式1:
- 属性适用的数据类型
- 八种基本数据类型(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接口!