
「JavaSE学习笔记09」Junit、反射、注解
¶Chapter 15. Junit单元测试
¶15.1 软件测试方法
- 白盒测试(结构测试):测试者清楚待测试对象内部工作机制,该测试是开发者测试自己设计编码的代码。而接下来要讨论的 Junit 即是基于白盒测试
- 黑盒测试(功能测试):通过测试检验是否每个功能都能正常工作,检查程序功能是否按规格说明书的规定正常使用,这是在程序接口进行的测试。
¶15.2 Junit使用步骤
定义一个测试类,命名建议如下:
- 测试类的名称:
被测试的类名Test.java,如CalculatorTest; - 包名称:
xxx.xxx.xx.test
- 测试类的名称:
定义测试方法(能够在IDE中独立运行的模块),其定义建议如下:
- 方法名:test被测试的方法名称
- 返回值:
void - 参数列表:空参
给方法加上
@Test的注解此外,在这个测试类中,还能对另一些方法,加上这些注解:
@Before:常用于资源的申请,修饰的方法会在测试方法之前被自动执行@After:与上相反的情况。
导入 Junit 依赖环境
IDEA中鼠标指向红线处,即能够导入 Junit 各种依赖环境了。

点击你需要测试的方法的首行右侧绿色按钮,便能够单独地运行该方法。
通过 断言语句 来判断测试结果与期望的结果是否一致。
1
Assert.assertEquals(期望的结果,运算的结果);
¶案例演示
项目结构:
1 | |
1 | |
1 | |

¶Chapter 16. 反射
¶16.1 Java类的加载

如上图,Java类的完整加载机制分上面的七个阶段,现在讨论的是“加载”,是Java类的完整加载机制的第一个阶段。
参考了"学习java应该如何理解反射?"——Kira的知乎回答,假定你需要运行下面这个代码:
1 | |
首先这段代码通过 javac 编译成一个 .class 的二进制文件,然后通过 类加载器(ClassLoader)加载进JVM的内存中(此时,类 Object 加载到内存的方法区中)。
接着,创建了 Object 类的**Class 对象**(类型对象,每个类只有一个 Class 对象,作为方法区类的数据结构的接口。故并不是你 new 出来的实例对象!)
JVM 创建一个实例对象前,会先检查这个对象所属的类是否被加载,寻找该类对应的 Class 对象。若已经加载好,则会为你需要的对象分配内存,初始化(即 new Object())
¶16.2 反射概述
Java的反射(reflection)机制,是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。
这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。
此外,通过反射,我们能够解耦,提高程序的可扩展性。
下面的.java文件、.class文件、Class类的关系图,源自bravo1988的回答。
Class类对反射的支持写了内部类,里面的字段与.class文件的内容形成映射

¶16.3 Class对象及其内部成员的获取方法
获取 Class 对象的方法共三个:(获取的 Class 对象,都是一样的!)
将字节码文件(
.class)加载进内存,返回Class对象:Class.forName("全类名")(全类名,即"包名.类名"),举例如下:1
Class cls1 = Class.forName("top.JoyDee.Person");多用于配置文件,将类名定义在配置文件中,读入文件,加载类。
通过类名的属性
class来获取:类名.class,举例如下:1
Class cls2 = Person.class;多用于参数的传递。
对象继承了
Object类定义的getClass()方法:对象.getClass1
2Person a = new Person();
Class cls3 = a.getClass();多用在,有了对象,要获取其对应的字节码文件对象。
通过 Class 对象的方法,获取其内部成员:
一、获取成员变量们:
获取所有
public修饰的成员变量:Field[] getFields()1
2
3
4
5Class personCls = Person.class; //获取Person的Class对象
Field[] fs = personCls.getFields(); //获取所有public修饰的成员变量
for(Field fe : fs){
System.out.println(fe);
}
- 获取指定名称的 public修饰的成员变量:
Field getField(String name) - 获取所有的成员变量(包括
private、protected、public及默认),不考虑修饰符:Field[] getDeclaredFields() - 获取指定名称的 且不考虑修饰 的成员变量:
Field getDeclaredField(String name)
二、获取构造器们:
Constructor<?>[] getConstructors()Constructor<T> getConstructor(类<?>... parameterTypes)(括号中是某个构造器的 参数类型 的Class对象列表)1
2Class personCls = Person.class;
Constructor c = personCls.getConstructor(String.class, int.class);Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)Constructor<?>[] getDeclaredConstructors()
三、获取成员方法们:
获取本类及父类或父接口的所有公共方法:
Method[] getMethods()Method getMethod(String name, 类<?>... parameterTypes)注意,与 获取构造器 不同在于,获取成员方法,需要传入(方法名,参数类型的
Class对象列表)获取本类中的所有方法(包括
private、protected、public及默认)Method[] getDeclaredMethods()Method getDeclaredMethod(String name, 类<?>... parameterTypes)
四、获取全类名:String getName()
1 | |
¶16.4 成员变量、构造器、成员方法相关API
Field:成员变量
设置值:
void set(Object obj, Object value)获取值:
get(Object obj)忽略访问权限修饰符的安全检查:
成员变量.setAccessible(true)1
2
3
4
5
6
7
8
9
10Person p = new Person();
Field ne = personCls.getField("name"); //获取相应字段的成员变量
Object value = ne.get(p); //将p对象传进去,使其返回值
ne.set(p, "Luffy"); //修改
Field ie = personCls.getField("id");
is.setAccessible(true); //暴力反射
Object value2 = ie.get(p);
Constructor:构造器
创建其构造的对象:
T newInstance(Object... initargs)1
2
3Class personCls = Person.class;
Constructor c = personCls.getConstructor(String.class, int.class);
Object person = c.newInstance("Luffy", 20);
Method:成员方法
执行其方法:
Object invoke(Object obj, Object... args)1
2
3
4Class personCls = Person.class;
Person p = new Person();
Method eat_method = personCls.getMethod("eat", String.class); //获取指定名称的方法
eat_method.invoke(p, "饭"); //指向方法,传入 指定对象及方法参数列表获取方法名称:
String getName()忽略访问权限修饰符的安全检查:
1
mymethod.setAccessible(true);
¶16.5 综合案例
需求:写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法。
实现:
- 配置文件
- 反射
步骤:
- 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
- 在程序中加载读取配置文件
- 使用反射技术来加载类文件进内存
- 创建实例对象
- 执行方法
项目文件:
pro.properties 配置文件:
1 | |
Person.java 文件:
1 | |
ReflectDemo.java 文件
1 | |
¶Chapter 17. 注解
¶17.1 注解概述
Java注解 (Annotation)又称Java标注,是 Java 语言 5.0 版本开始支持加入源代码的特殊语法 元数据 。
注解与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。(注解,不是程序的一部分)
和 Javadoc 不同,Java标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java虚拟机可以保留标注内容,在运行时可以获取到标注内容。 当然它也支持自定义Java标注。
按照作用来分类:
根据 Annotation 生成帮助文档:加上
@Documente标签能使该 Annotation 标签的内容出现在 javadoc中;让编译器进行编译检查:例如
@Override代码分析:在反射中解析并使用 Annotation,或者,使用别人写的 测试类框架 对每个需要测试的方法加上诸如
@Check的注解
¶17.2 内置的注解
Annotation 接口的架构:

Java 定义了一套注解,共有 7 个,3 个在 java.lang 中,剩下 4 个在 java.lang.annotation 中。从 Java 7 开始,额外添加了新 3 个注解(本文暂不讨论)
¶作用在代码的注释
@Override:检查该方法是否有覆写方法。若发现其父类或引用的接口中并没有该方法,则会报编译错误。@Deprecated:标记过时的方法,若使用该方法,则会报编译警告@SuppressWarnings:指示编译器去忽略注释中声明的警告。1
2@SuppressWarnings("all")
/* 一般传递参数all */
¶作用在其他注解的注解(又称 元注解)
@Retention:标识这个注解如何保存,是只在代码中,还是编入.class文件中,或者是在运行时可通过反射访问。(缺省情况下,默认是RetentionPolicy.CLASS)可以选择:
SOURCE、CLASS、RUNTIME(一般而言,我们会取RUNTIME值)1
2@Retention(RetentionPolicy.RUNTIME)
/* 当前被描述的注解,会保留到class字节码文件中,并被JVM读取到 */@Documented:标记这些注解是否包含在用户API文档中。(缺省情况下不出现在 javadoc)@Target:标记这个注解应该是哪种 Java 成员,它一般指定Annotation类型属性ElementType取值):(缺省情况下可指定任何地方)源码中,
Target类中有一成员,为枚举类数组ElementType[],名称为valueTYPE:标记该注解只能作用在类上METHOD:标记该注解只能作用在方法上FIELD:标记该注解只能作用在成员变量上
1
2
3
4@Target(value={ElementType.TYPE, ElementType.METHOD})
public @interface MyAnno3{
}@Inherited:标记这个注解是继承于哪个注释类,一般指定Annotation的策略属性
¶17.3 自定义注解
一、通用格式:
1 | |
通过反汇编得到其本质:
1
2
3public interface 注解名称 extends java.lang.annotation.Annotation{
//...
}
二、举例:
定义 自定义的注解类:
1
2
3
4
5
6
7
8
9
10
11@Documented // 表示它可以出现在 javadoc 中。
@Target(ElementType.TYPE) //意味着MyAnnotation是来修饰"类、接口(包括注释类型)或枚举声明"的注解。
@Retention(RetentionPolicy.RUNTIME) //编译器会将该Annotation信息保留在.class文件中,并且能被虚拟机读取。
public @interface MyAnnotation{
// Annotation 接口的实现细节都由编译器完成。
public int age(); //定义抽象方法,它是这个注解的属性
public String() default "Luffy";
public MyAnno2 anno2(); //注解类型
public String[] strs();
}使用自定义注解类:
1
2
3
4@MyAnnotation(age = 20, anno2 = @MyAnno2, strs = {"Luffy", "Zoro"})
public class Worker{
}
三、属性:接口中的抽象方法。其要求有:
属性的返回值类型只能为:基本数据类型、
String、枚举、注解、以上类型的数组因而,属性的返回值不能是自定义的类
一旦定义了属性,在使用时需给属性进行赋值
定义时如果用
default设置默认值,就无需再赋值(见上面的代码)如果只有一个属性需要赋值,则只需要这样写即可:
1
2
3
4@MyAnnotation(20)
public class Worker{
//...
}
¶17.4 解析注解
在程序中使用(解析)注解,就是获取注解中定义的属性值。步骤如下:
获取你注解类 定义的 的 类型对象(如
Class、Method、Field)调用 类型对象 的方法,来获取指定的 注解对象 :
1
getAnnotation(Class) //其实就是在内存中生成了一个该注解接口的子类实现对象调用注解对象中的方法,来获取配置的属性值
¶案例一
现在通过注解的方式,简化 16.5 综合案例中 的简单框架配置
MyAnno.java 文件:描述需要执行的类名、方法名
1 | |
Person.java文件:
1 | |
ReflectDemo.java 文件:框架类(无需属性集类、IO流类)
1 | |
¶案例二:设计测试框架
Calculator.java 文件:
1 | |
TestCheck.java 文件:
1 | |
运行结果:写入了bug.txt文件中:
1 | |
`