单元测试就是针对最小的功能单元编写测试代码,Java 程序最小的功能单元是方法。
单元测试就是针对 Java 方法的测试,进而检查方法的正确性,需要用到 JUnit 单元测试框架。
JUnit 单元测试框架是使用 Java 语言实现的,是开源的,几乎所有的 IDE 工具都集成了 JUnit。
JUnit 单元测试框架的优点:
使用单元测试进行业务方法预期结果、正确性的测试,步骤:
@Test
注解,第一次使用时需要 Alt + Enter
导入包 org.junit.Test
,标注该方法是一个测试方法;方法名 | 说明 |
---|---|
public static void assertEquals(String message, long expected, long actual) | 断言,类 Assert 直接调用 |
说明:参数一是预期值与实际值不相同时,给出的提示信息;参数二是预期值;参数三是实际值。
当没有问题时
当出现问题时
注意:
测试类和测试方法的命名应该规范。
每个测试方法必须是公共的、无参数、无返回值的非静态方法。
测试方法应该使用
@Test
注解标记。一个业务方法对应一个测试方法。
运行测试方法时,可以单独测试方法,也可以一起测试所有方法。
JUnit 4 版本
注解 | 说明 |
---|---|
@Test | 测试方法 |
@Before | 用来修饰实例方法,该方法会在每一个测试方法执行之前执行一次 |
@After | 用来修饰实例方法,该方法会在每一个测试方法执行之后执行一次 |
@BeforeClass | 用来修饰静态方法,该方法会在所有测试方法执行之前只执行一次 |
@AfterClass | 用来修饰静态方法,该方法会在所有测试方法执行之后只执行一次 |
注意:
- 开始执行的方法:一般用来完成初始化资源;
- 执行完之后的方法:一般用来释放资源。
反射是在运行时获取类的字节码文件对象,然后可以解析这个类的全部成分。
在运行时,可以直接得到这个类的构造器对象(Constructor)、成员变量对象(Field)、成员方法对象(Method)。
这种运行时动态获取类信息以及动态调用类中成分的能力称为 Java 语言的反射机制。
反射的核心思想和关键:得到编译后的 Class文件对象。
反射的作用:
反射适合的用途:做 Java 的高级框架。
反射的第一步是先得到编译后的 Class 类对象,有三种方法。
forName
。class
方法。getClass
方法获取对应类的 Class 对象。方法名 | 说明 | |
---|---|---|
方法一 | public static Class> forName(String className) | Class 类调用 |
方法二 | class | 类调用 |
方法三 | public final native Class> getClass() | 对象调用 |
注意:方法一中的参数为全限名,也就是包名 + 类名。
总体步骤:
第二步:Class 类获得构造器 Constructor 对象的方法:
方法名 | 说明 |
---|---|
public Constructor>[] getDeclaredConstructors() | 返回所有构造器对象的数组,存在就能拿到 |
public Constructor getDeclaredConstructor(Class>… parameterTypes) | 返回单个构造器对象,存在就能拿到 |
注意:
这两个方法都是 Class 对象调用。
这两个方法获取构造器,无所谓构造器的声明类型,即,无所谓构造器是 public 还是 private。
返回单个构造器对象时,方法的参数为 Class 对象且为可变参数,如:
String.class
、int.class
。返回单个构造器对象时,若不写参数,则返回无参构造器,有参数,返回有参构造器。
第三步:创建对象的方法:
方法名 | 说明 |
---|---|
public T newInstance(Object … initargs) | 根据指定的构造器创建对象 |
public void setAccessible(boolean flag) | 参数设置为 true,表示取消访问检查,进行暴力反射 |
注意:
- 这两个方法都是构造器对象调用。
- 第二个方法是当构造器方法不是用 public 修饰时使用,打开权限,可以有效一次,反射可以破坏封装性,私有的也可以执行。
- 第一个方法中,当构造器为无参时,方法不写参数;当构造器为有参时,方法中的参数为初始化值。
第二步代码示例:获取所有构造器对象的数组、获取单个构造器对象
第三步代码示例:初始化对象、暴力反射
总体步骤:
第二步:Class 类获得成员变量 Field 对象的方法:
方法名 | 说明 |
---|---|
public Field[] getDeclaredFields() | 返回所有成员变量对象的数组,存在就能拿到 |
public Field getDeclaredField(String name) | 返回单个成员变量对象,存在就能拿到 |
注意:
这两个方法都是 Class 对象调用。
这两个方法获取成员变量对象,无所谓成员变量的声明类型,即,无所谓成员变量是 public 还是 private。
第二个方法的参数为成员变量的名称,即属性的名称。
第三步:赋值或者取值的方法:
方法名 | 说明 |
---|---|
public void set(Object obj, Object value) | 赋值 |
public Object get(Object obj) | 取值 |
public void setAccessible(boolean flag) | 参数设置为 true,表示取消访问检查,进行暴力反射 |
注意:
- 这三个方法都是成员变量对象调用。
- 当成员变量对象不是用 public 修饰时使用 setAccessible,打开权限,反射可以破坏封装性,私有的也可以执行。
- 在赋值方法中,参数一是创建的对象,参数二是为该对象指定的属性赋值。
第二步代码示例:获取所有成员变量对象的数组、获取单个成员变量对象
第三步代码示例:赋值、取值、暴力反射
总体步骤:
第二步:Class 类获得成员方法对象的方法:
方法名 | 说明 |
---|---|
public Method[] getDeclaredMethods() | 返回所有成员方法对象的数组,存在就能拿到 |
public Method getDeclaredMethod(String name, Class>… parameterTypes) | 返回单个成员方法对象,存在就能拿到 |
注意:
这两个方法都是 Class 对象调用。
这两个方法获取成员方法对象,无所谓成员方法的声明类型,即,无所谓成员方法是 public 还是 private。
第二个方法的第一个参数为:方法名;第二个参数为:该方法参数类型的 Class 对象,如:
int.class
,若该方法没有参数,则可以不写第二个参数。
第三步:运行方法:
方法名 | 说明 |
---|---|
public Object invoke(Object obj, Object… args) | 运行方法 |
public void setAccessible(boolean flag) | 参数设置为 true,表示取消访问检查,进行暴力反射 |
注意:
- 这三个方法都是成员方法对象调用。
- 当成员方法对象不是用 public 修饰时使用 setAccessible,打开权限,反射可以破坏封装性,私有的也可以执行。
- 第一个方法的第一个参数:对象;第二个参数:传递的参数(如果没有参数可以不写)。
第二步代码示例:获取所有成员方法对象的数组、获取单个成员方法对象
第三步代码示例:运行方法、暴力反射
泛型只是在编译阶段可以约束集合只能操作某种数据类型,在编译成 Class 文件进入运行阶段的时候,其真实类型都是 ArrayList,泛型相当于被擦除,此时,可以为集合存入其他任意类型的元素,这就用到了反射,因为反射是在运行的技术。
通过反射为集合添加其他类型的数据
需求:给定任意一个对象,在不清楚对象字段的情况下,可以把对象的字段名称和对应值存储到文件中去。
分析:
MybatisUtil
存放 save
方法,可以接收任意类型的对象;save
方法中,应用反射获取该对象的全部成员变量名称以及对应值;PrintStream
将获取的数据写入到文件中;main
方法中对象调用 save
方法,得到目标文件。注意:
- 当成员变量是非公有属性时,要用
setAccessible
来打开权限,暴力反射。- Class 对象获取类名的两个方法,如下:
方法名 说明 public String getName() 获取当前 Class 对象的全限名:包名 + 类名 public String getSimpleName() 获取当前 Class 对象的类名
注解:Annotation,又称标注。Java 语言中的类、构造器、方法、成员变量、参数等都可以被注解进行标注。
作用:对 Java 中类、构造器、方法、成员变量、参数等做标记,然后进行特殊处理。
例如在 JUnit 框架中,标注了注解 @Test
的方法就可以被当作测试方法执行,而没有标记的就不能当作测试方法执行。
格式
类、构造器、方法、成员变量、参数等都可以被注解进行标注。
特殊属性 value:
元注解:注解注解的注解。
两个元注解:
@Target
:约束自定义注解只能在哪些地方使用。@Retention
:声明注解的生命周期。注解的操作中经常需要进行解析,注解的解析就是判断是否存在注解,存在注解就解析出内容。
与注解解析相关的接口:
Annotation
:注解的顶级接口,注解都是该类型的对象;AnnotationElement
:该接口定义了与注解解析相关的解析方法。Class、Method、Field、Constructor 这些类都实现了 AnnotationElement
接口,都拥有解析注解的能力。
解析注解的方法:
方法名 | 说明 |
---|---|
public boolean isAnnotationPresent(Class extends Annotation> annotationClass) | 判断当前对象是否使用了指定的注释 |
public A getDeclaredAnnotation(Class annotationClass) | 根据注解类型获得对应的注解对象 |
public Annotation[] getDeclaredAnnotations() | 获得当前对象所有注解,返回注解数组 |
注意:方法一和方法二都是由 Class、Method、Field、Constructor 这些类对象调用,参数为注解的类类型,如:
Book.class
。
解析注解的技巧:注解在哪个成分上,就先拿哪个成分对象。
Class
对象,然后再拿注解;Method
对象,然后再拿注解;Field
对象,然后再拿注解。需求:自定义一个注解
MyTest
,和 JUnit 框架一样,只有方法上有MyTest
注解才可以被执行。
思路:先用反射提取类中的所有方法,用循环依次判断每一个方法是否有 MyTest
注解,有注解然后执行。
代理也是一个对象,用来对被代理对象的行为额外做一些辅助操作。
Java 中代理的类是:java.lang.reflect.Proxy
,类中有一个静态方法 newProxyInstance
,可以为对象产生一个代理对象返回。
方法名 | 说明 |
---|---|
public static Object newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h) | 为对象返回代理对象 |
注意:
- 参数一:是定义代理类的类加载器,用来产生代理对象,如:
dog.getClass().getClassLoader()
;- 参数二:是代理类要实现的接口列表,用来将被代理的方法交给代理对象,如:
dog.getClass().getInterfaces()
;- 参数三:是代理对象的核心处理程序,用来指定代理对象干什么事,如:
new InvocationHandler()
;
实现动态代理的步骤:
Proxy
类提供的方法 newProxyInstance
,产生一个代理对象。动态代理的优点:
需求:为 dog 对象创建一个代理对象。
注意点:
method
去触发对象方法的执行;回到代理中,由代理负责返回结果给方法的调用者。执行流程图如下。method
去触发对象,method
是正在调用的方法,args
是该方法的参数,固定步骤。需求:模拟用户管理业务,包括用户登录、用户删除、用户查询功能,做性能分析,统计每个功能的耗时。
分析:把性能分析交给代理去统一完成,这样可以省去在每个方法中都进行一遍性能分析,提高了代码的复用性。
注意:必须要有接口,并且实现类要实现接口,代理通常是基于接口实现的。
对比