第十章 Junit、反射、注解
1. Junit
1.1 Junit简介
Junit就是单元测试。
测试分类:
- 黑盒测试
- 白盒测试
Junit是白盒测试的一种。
1.2 Junit的使用
传统测试方法需要创建一个类的对象,执行代码看结果。
-
定义一个测试类。
- 类名:****Test
- 包名: ***.test
-
定义测试方法
- 方法可以独立运行。
- 方法名:test方法名()。
- 返回值:void。
- 参数列表:空参。
-
给方法加上@Test注解。
- 加上注解后方法就能独立运行了。
-
import org.junit.Test;
-
使用断言操作Assert来判断结果是否成功。
Assert.assertEquals(3, i);
package JunitDemoTest;
import JunitDemo.Calc;
import org.junit.Assert;
import org.junit.Test;
public class CalcTest {
@Test
public void testPlus() {
//创建计算器对象
Calc c = new Calc();
//调用待测试方法
int i = c.plus(1, 2);
//断言来判断结果
Assert.assertEquals(3, i);
}
}
1.3 @Before
注解
这个注解会在每个测试方法运行之前执行,一般用于资源申请。
1.4 @After
注解
这个注解会在每个测试方法运行之后执行,一般用于资源释放。s
2. 反射
框架设计的灵魂
框架:
半成品软件,可以在框架的基础上进行开发,简化编码。
2.1 反射的概念
将类的各个组成部分封装成其他对象,这就是反射机制。
类加载器:
将字节码文件加载进内存。加载进内存后,就变成了Class类对象。
class类对象有三个重要部分:
- 成员变量, 封装为field对象,存进Field[]。
- 构造方法, 封装为Constructor对象,存进Constructor[]。
- 成员方法, 封装为Method对象,存进Method[]。
封装类对象的好处:
- 运行时可以操作class对象,比如idea在你新建一个字符串后,就能自动提示字符串的方法。
- 解耦,提高程序的可扩展性。
2.2 获取Class对象的三种方式
2.2.1 在源代码阶段
Class.forName("全类名")
全类名指的是带上包名的类名。
多用于配置文件,将类名放进配置文件,读取文件,加载类对象。
这是个静态方法,通过Class类可以直接调用。这个方法会抛出类未找到异常,需要进行处理。
将字节码文件加载进内存,返回Class对象。
2.2.2 在类对象阶段
类名.class()
多用于传参。
因为此阶段类已经被加载进内存,所以不需要再加载了,只用类名就能直接调用class方法获取类对象。
2.2.3 在运行时阶段
对象.getClass()
方法,因为new出来对象了,所以可以直接用对象的方法来获取class对象。
用于对象的字节码对象。
注意: 同一个字节码文件,这三个方法获取到的类对象是同一个。也就是说,在一次程序运行过程中,同一个class文件只会被加载一次。
2.3 获取Filed
2.3.1 获取公有成员变量
Filed[] getFileds()
获取所有public修饰的成员变量。Filed getFiled(String name)
获取指定的public成员变量。
获取到了成员变量对象后,就可以通过get和set方法获取或者设置值。
get(Object obj)
通过成员变量对象来获取obj对象的某个成员变量的值。set(Object obj, Object value)
设置值。
2.3.2 获取所有成员变量
Filed[] getDeclaredFileds()
Filed getDeclaredFiled(String name)
获取指定的成员变量。
若想访问非公有成员变量的成员变量对象,需要忽略安全检查。
setAccessible(true)
,将其设置为可访问,这叫做暴力反射。
提示: 反射面前不存在私有,利用反射也能设置私有变量的值。
2.3 获取构造方法
Constructor getConstructor(...)
需要传进来的参数是调用这个方法的对象的构造方法里面的参数。
如果这个对象new的时候是需要一个int,那么获取构造器对象时,就传进去int.class。
用处: 创建对象。
Object newInstance(...)
传进去对应的参数。
如果使用空参数的构造方法来创建对象,操作可以简化为:
类对象.newInstance()
2.4 获取成员方法
-
Method[] getMethods()
获取public成员方法。- 还有一些Object的方法也在里面,被继承下来了。
-
Method getMethod(String name, 类<?>... paramTypes)
- 传进来方法名称、方法的参数列表, 比如int.class, String.class。
-
Method[] getDeclaredMethods()
获取全部成员方法 -
Method getDeclaredMethod(String name, 类<?>... paramTypes)
拿到方法对象后执行方法:
方法对象.invoke(Object 类的实例, 参数列表)
获取方法名
String 方法对象.getName()
2.5 获取类名
类对.getName()
获取类名,而且是全类名。
2.6 反射的案例
需求: 写一个框架,可以帮我们创建任意类的对象,并执行任意一个方法。
分析: 既然是框架,那么框架代码就不能被改变,所以不能直接在框架内新建对象。
-
需要配置文件。
- 将全类名和方法名放到配置文件里。
- 程序中加载配置文件。
-
反射
- 程序中利用反射将class文件加载进内存。
- 利用类对象创建对象,使用方法。
ClassLoader对象
可以通过当前类名.class.getClassLoader()
来获取。
有了类加载器对象
后,就能找到当前路径下的文件。
使用类加载器对象
的getResourceAsStream(String filename)
方法来将配置文件加载为字节输入流。
Properties对象
就可以使用load
方法将字节流加载进来,获取配置文件。
从配置文件中读取到类名与方法名。
使用Class.forName(String className)
来创建类对象
。
使用类对象.newInstance()
来创建实例。
使用类对象.getMethod(String 方法名)
获取方法对象。
使用方法对象.invoke(实例)
来执行方法
3. 注解
3.1 简介
注解:给编译器看的注释。
JDK 1.5 引入。
作用分类:
-
编译检查,比如
@Override
。 -
编写文档,通过代码里标识的注解来生成文档。
javadoc **.java
-
代码分析:通过代码里标识的注解对代码进行分析(通过反射)。
3.2 JDK 预定义的注解
-
@Override
-
@Deprecated
- 将该注解标注的内容标记为
已过时
,不建议使用。
- 将该注解标注的内容标记为
-
@SuppressWarnings(String str)
-
压制警告
-
str
设置为"all"
,忽略所有编译器警告。
-
3.3 自定义注解
3.3.1 格式
元注解
public @interface 注解名称{
属性列表;
}
注解的本质:
注解就是一个接口,继承了Annotation接口。
public interface MyAnno extends java.annotation.Annotation
3.3.2 注解的属性
注解的属性: 也就是接口中的抽象方法。
属性的要求:
-
返回值类型。
-
基本数据类型
-
String
-
枚举
-
注解
-
以上四种的数组
-
-
定义了属性,使用时需要给属性赋值。
-
如果不想赋值,定义属性时可以加默认值。
-
int age() default 12;
-
如果只有一个属性需要赋值,而且属性名是value,则可以省略value,直接写数值进去。
-
注解赋值方法:
anno2 = @Anno2
-
数组赋值时,使用大括号包裹,如果数组只有一个值,可以省略大括号。
-
3.3.3 元注解
注解的注解,叫元注解。
-
@Target
描述注解能够作用的位置。ElementType.TYPE
作用于类上ElementType.METHOD
作用于方法上ElementType.FIELD
作用于成员变量上
-
@Retention
描述注解被保留的阶段。RetentionPolicy.SOURCE
源代码阶段RetentionPolicy.CLASS
类对象阶段RetentionPolicy.RUNTIME
运行时阶段- 自定义的注解一般用于运行时阶段。
-
@Documented
注解是否被抽取到javadoc中。- 只要加上就行,没有赋值。
-
@Inherited
描述注解是否被子类继承。- 注解打上这个元注解后,如果用此注解标注了一个类,这个类被继承,那么子类也会被注解标注。
3.4 注解的使用
在程序中解析注解的值。
配置文件的加载可以交给注解来做。
3.4.1 注解的解析
注解定义如下:
package Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnno {
String className();
String methodName();
}
待调用的两个类如下:
package Annotation;
public class Demo01 {
public void show(){
System.out.println("Demo1");
}
}
package Annotation;
public class Demo02 {
public void show() {
System.out.println("Demo2");
}
}
测试代码:
package Annotation;
import java.lang.reflect.Method;
@MyAnno(className = "Annotation.Demo01", methodName = "show")
public class Demo {
public static void main(String[] args) throws Exception {
Class<Demo> demoClass = Demo.class;
MyAnno anno = demoClass.getAnnotation(MyAnno.class);
String className = anno.className();
String methodName = anno.methodName();
Class cls = Class.forName(className);
Object obj = cls.newInstance();
Method method = cls.getMethod(methodName);
method.invoke(obj);
}
}
-
获取该类的字节码对象。
Class c = Demo.class;
-
获取注解对象。
MyAnno anno = c.getAnnatation(Myanno.class);
-
调用注解对象中的抽象方法,获取返回值。
String className = anno.className();
String methodName = anno.methodName();
-
通过Class的静态方法来生成类对象。
Class cls = Class.forName(className);
-
通过类对象生成类的实例。
Object obj = cls.newInstance();
-
通过类对象获取方法对象。
Method method = cls.getMethod(methodName);
-
通过方法对象执行方法。
method.invoke(obj);
``
3.5 注解案例
简单的测试框架
要求:RunCheck类的主方法运行时,自动执行加了@check注解的方法,验证是否有异常。
计算器类:
package Annotation.checkFrame;
public class Calculator {
@check
public int add() {
return 1 + 0;
}
@check
public int div() {
return 1 / 0;
}
}
注解接口:
package Annotation.checkFrame;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface check {
}
执行测试框架:
package Annotation.checkFrame;
import java.lang.reflect.Method;
public class RunCheck {
public static void main(String[] args) {
//1. 创建计算器对象
Calculator c = new Calculator();
//2. 获取字节码对象
Class<? extends Calculator> cls = c.getClass();
//3. 获取所有方法
Method[] methods = cls.getMethods();
//4. 检查方法是否有@check注解标记
for (Method method : methods) {
if (method.isAnnotationPresent(check.class)) {
try {
// 5. 执行方法,捕获异常。
method.invoke(c);
} catch (Exception e) {
System.out.println(method.getName() + "方法出现异常");
System.out.println("异常类型是:" + e.getCause().getClass().getSimpleName());
System.out.println("异常原因是:" + e.getCause().getMessage());
}
}
}
}
}
//=============输出==============//
div方法出现异常
异常类型是:ArithmeticException
异常原因是:/ by zero