Java系列笔记第十章: Junit、反射、注解

第十章 Junit、反射、注解

1. Junit

1.1 Junit简介

Junit就是单元测试。

测试分类:

  • 黑盒测试
  • 白盒测试

Junit是白盒测试的一种。

1.2 Junit的使用

传统测试方法需要创建一个类的对象,执行代码看结果。

  1. 定义一个测试类。

    • 类名:****Test
    • 包名: ***.test
  2. 定义测试方法

    • 方法可以独立运行。
    • 方法名:test方法名()。
    • 返回值:void。
    • 参数列表:空参。
  3. 给方法加上@Test注解。

    • 加上注解后方法就能独立运行了。
  4. import org.junit.Test;

  5. 使用断言操作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 预定义的注解

  1. @Override

  2. @Deprecated

    • 将该注解标注的内容标记为已过时,不建议使用。
  3. @SuppressWarnings(String str)

    • 压制警告

    • str 设置为"all",忽略所有编译器警告。

3.3 自定义注解

3.3.1 格式

元注解
public @interface 注解名称{
    属性列表;
}

注解的本质:
注解就是一个接口,继承了Annotation接口。

public interface MyAnno extends java.annotation.Annotation

3.3.2 注解的属性

注解的属性: 也就是接口中的抽象方法。

属性的要求:

  1. 返回值类型。

    • 基本数据类型

    • String

    • 枚举

    • 注解

    • 以上四种的数组

  2. 定义了属性,使用时需要给属性赋值。

    • 如果不想赋值,定义属性时可以加默认值。

    • int age() default 12;

    • 如果只有一个属性需要赋值,而且属性名是value,则可以省略value,直接写数值进去。

    • 注解赋值方法:anno2 = @Anno2

    • 数组赋值时,使用大括号包裹,如果数组只有一个值,可以省略大括号。

3.3.3 元注解

注解的注解,叫元注解。

  1. @Target 描述注解能够作用的位置。

    • ElementType.TYPE 作用于类上
    • ElementType.METHOD 作用于方法上
    • ElementType.FIELD 作用于成员变量上
  2. @Retention 描述注解被保留的阶段。

    • RetentionPolicy.SOURCE 源代码阶段
    • RetentionPolicy.CLASS 类对象阶段
    • RetentionPolicy.RUNTIME 运行时阶段
    • 自定义的注解一般用于运行时阶段。
  3. @Documented 注解是否被抽取到javadoc中。

    • 只要加上就行,没有赋值。
  4. @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);
    }
}
  1. 获取该类的字节码对象。

    Class c = Demo.class;

  2. 获取注解对象。

    MyAnno anno = c.getAnnatation(Myanno.class);

  3. 调用注解对象中的抽象方法,获取返回值。

    String className = anno.className();

    String methodName = anno.methodName();

  4. 通过Class的静态方法来生成类对象。

    Class cls = Class.forName(className);

  5. 通过类对象生成类的实例。

    Object obj = cls.newInstance();

  6. 通过类对象获取方法对象。

    Method method = cls.getMethod(methodName);

  7. 通过方法对象执行方法。

    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
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值