【Java】Junit、反射和注解的笔记

1 Junit

黑盒测试:不需要写代码,给输入值,看程序输出是否符合期望
白盒测试:需要写代码,关注程序具体的执行流程
Junit —> 白盒测试

步骤

  1. 定义一个测试类(测试用例)
    【命名:类名+Test】【包名:xxx.xx.xx.test】
  2. 定义测试方法,可以独立运行
    【命名:test+测试的方法】【返回值:void】【参数列表:空参】
  3. 给方法加注解@Test
  4. 导入Junit的依赖环境

判断结果
5. 断言:Assert.assertEquals(expected,result);
6. 红失败,绿成功

@Before注解,所有测试方法前会先执行,初始化申请资源方法可以用
@After注解,所有测试方法执行后执行,释放资源方法可以用

2 反射

反射:框架设计的灵魂,将类的各个组成成分
框架:半成品软件,可以在框架的基础上进行软件的开发,简化编码

Java代码在计算机中经历的三个阶段
Java文件 —> Javac编译 —>class文件【Source源代码阶段】 —> 类加载器Class.Loader —> Class类对象 【Class类对象阶段】—> 创建对象 —> Xxxx对象 【Runtime运行时阶段】

反射的好处

  1. 在程序运行过程中,操作这些对象
  2. 可以解耦,提高程序的可扩展性

获取字节码Class对象的三种方式

  1. Class.forName(“全类名”):将字节码文件加载进内存,返回class对象
  2. 类型.class:通过类名的属性class获取
  3. 对象.getClass() :getClass()方法在Object类中定义着
    public static void main(String[] args) throws ClassNotFoundException {
    
    
        //1. Class.forName("全类名"):将字节码文件加载进内存,返回class对象
        Class cls = Class.forName("reflect.Person");
        System.out.println(cls);
        //2. 类型.class:通过类名的属性class获取
        Class cls2 = Person.class;
        System.out.println(cls2);
        //3. 对象.getClass() :getClass()方法在Object类中定义着
        Class cls3 = new Person().getClass();
        System.out.println(cls3);
        System.out.println(cls == cls2);//true
        System.out.println(cls == cls3);//true
    }

同一个字节码文件**.class文件在一次程序运行中,只会被加载一次,无论通过哪一种方式获取的Class对象都是同一个。

方法1多用于配置文件,将类名定义在配置文件中,读取文件,加载类
方法2多用于参数的传递
方法3多用于对象的获取字节码的方式

class对象的功能

  1. 获取成员变量们
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
    
    
        Class cls = Person.class;

        Field[] fields = cls.getFields();//获取所有public修饰的成员变量
        System.out.println(Arrays.toString(fields));

        Field school = cls.getField("school");//获取指定的public修饰的成员变量
        System.out.println(school);
        Person p = new Person();

        //获取成员变量school的值
        Object o = school.get(p);
        System.out.println(o);//null

        //设置成员变量school的值
        school.set(p,"张三小学");
        System.out.println(p);//Person{name='null', age=0, school='张三小学'}

        Field[] declaredFields = cls.getDeclaredFields();//获取所有的成员变量
        for(Field field: declaredFields){
    
    
            System.out.println(field);
        }

        //获取私有成员变量的值
        Field name = cls.getDeclaredField("name");//获取指定的成员变量
        //访问私有成员前 要忽略访问权限修饰符的安全检查
        name.setAccessible(true);//暴力反射
        Object o1 = name.get(p);
        System.out.println(o1);//null
    }
  1. 获取构造方法们
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
    
    
        Class cls = Person.class;
        //全参构造
        Constructor constructor = cls.getConstructor(String.class, int.class);
        System.out.println(constructor);
        //创建对象
        Object obj = constructor.newInstance("张三", 19);
        System.out.println(obj);

        //空参构造
        Constructor constructor2 = cls.getConstructor();
        System.out.println(constructor2);
        Object obj2 = constructor2.newInstance();
        System.out.println(obj2);
        
        //空参构造一般用这个 简化版
        Object obj3 = cls.newInstance();
        System.out.println(obj3);
    }
  1. 获取成员方方法们
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
    
    
        Class cls = Person.class;
        //eat方法为空参
        Method eat = cls.getMethod("eat");
        Person p = new Person();
        //执行方法
        eat.invoke(p);
        //eat方法中有一个String类型的参数
        Method eat2 = cls.getMethod("eat", String.class);
        eat2.invoke(p," food");

        //获取所有public修饰的方法
        Method[] methods = cls.getMethods();
        for(Method m : methods){
    
    
            System.out.println(m.getName());//除了类自己的方法,Object的方法也存在其中
        }
    }
  1. 获取类名

获取的是全类名

    public static void main(String[] args)  {
    
    
        Class cls = Person.class;
        String name = cls.getName();
        System.out.println(name);//reflect.Person
    }

案例:框架类
在不改变该类的任何代码的前提下,可以帮助我们创建任意类的对象并执行任意方法

实现:1. 配置文件 2.反射

步骤:

  1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
  2. 在程序中加载读取配置文件
  3. 使用反射技术来加载类文件进内存
  4. 创建对象
  5. 执行方法

创建配置文件 xxx.properties
修改配置文件就可以选择使用的类和方法

className=reflect.Student//全类名
methodName=learn
public class ReflectTest {
    
    
    //在不改变该类的任何代码的前提下,可以帮助我们创建任意类的对象并执行任意方法
    public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
    
    
        //1.加载配置文件
        //1.1创建Properties对象
        Properties pro = new Properties();
        //1.2加载配置文件,转换为一个集合
        //1.2.1获取class目录下的配置文件
        ClassLoader classLoader = ReflectTest.class.getClassLoader();
        InputStream is = classLoader.getResourceAsStream("pro.properties");
        pro.load(is);

        //2.获取配置文件中定义的数据
        String className = pro.getProperty("className");
        String methodName = pro.getProperty("methodName");

        //3.加载该类进内存
        Class cls = Class.forName(className);

        //4.创建对象
        Object obj = cls.newInstance();

        //5.获取方法对象
        Method method = cls.getMethod(methodName);

        //6.执行方法
        method.invoke(obj);
    }
}

运行执行的是配置文件里对应的类和类的方法

3 注解

定义:Annotation,也叫元数据,一种代码级别的说明,JDK1.5后的新特性,与类、接口、枚举在同一层子,可以声明在包、类、字段、方法、局部变量和方法参数等前面,用来对这些元素进行说明,注释。

概念:JDK1.5之后的新特性,用来说明程序

作用分类

  1. 编译检查:让编译器实现基本的编译检查 【@Override @FunctionalInterface】
  2. 代码分析:通过代码标识的注解对代码进行分析【使用反射】
  3. 编写文档:通过代码表示的注解生产文档【javadoc文档】

JDK的内置注解

  1. @Override:检测被该注解标注的方法是否是继承自父类/父接口的
  2. @Deprecated:将该注解标注的内容,表示已过时
  3. @SuppressWarnings :压制警告 @SuppressWarnings(“all”)写在类上方

自定义注解

  • 格式:
    元注解
    public @interface 注解名称{}

注解的本质:是一个接口 默认继承自Annotation接口
public interface javadoc.MyAnno extends java.lang.annotation.Annotation {}

注解的属性:接口中的抽象方法

要求:

  1. 属性的返回值类型有【基本数据类型 String 枚举 注解 以上类型的数组】,不可以为自定义类和void。
  2. 定义了属性,在使用时需要给属性赋值。如果在定义时,分号前写上default “默认值”,可以不赋值
  3. 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义。

元注解:用来描述注解的注解

  • @Target:描述注解能够作用的位置
    ElementType的取值:
    TYPE:可以作用于类上
    METHOD:可以作用于方法上
    FILED:可以作用于成员变量上
@Target(value = ElementType.TYPE)
  • @Retention:描述注解被保留的阶段
    RetentionPolicy的取值:
    RUNTIME:当前被描述的注解,会保留到class字节码文件中,并被JVM读取到。
    CLASS:
    SOURCE:
@Retention(RetentionPolicy.RUNTIME)
  • @Documented:描述注解是否被抽取到api文档中
  • @Inherited:描述注解是否被子类继承

解析注解

  1. 获取注解定义的位置对象(Class, Method, Field)
  2. 获取指定的注解:getAnnotation(Class)
    Pro annotation = reflectTestClass.getAnnotation(Pro.class);
  3. 调用注解中的抽象方法来获取配置的属性值

定义注解

@Target({
    
    ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Pro {
    
    
    String className();
    String methodName();
}

自定义类

public class Demo01 {
    
    
    public void show(){
    
    
        System.out.println("demo1...show");
    }
}

定义框架类

@Pro(className = "reflect.Demo01",methodName = "show")
public class ReflectTest {
    
    
    //在不改变该类的任何代码的前提下,可以帮助我们创建任意类的对象并执行任意方法
    public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
    
    
        //1.解析注解
        //1.1 获取该类的字节码文件对象
        Class<ReflectTest> reflectTestClass = ReflectTest.class;
        //2.获取注解对象,在内存中生产了一个该注解接口的子类实现对象
        /*
        public class ProImpl implements Pro{
            public String className(){
                return "reflect.Demo01";
            }
            public String methodName(){
                return "show";
            }
        }
         */
        Pro annotation = reflectTestClass.getAnnotation(Pro.class);
        //3.调用注解对象中定义的抽象方法获取返回值
        String className = annotation.className();
        String methodName = annotation.methodName();
        System.out.println(className);
        System.out.println(methodName);

        //4.加载类进内存
        Class cls = Class.forName(className);

        //5.创建对象
        Object obj = cls.newInstance();

        //6.获取方法对象
        Method method = cls.getMethod(methodName);
        method.invoke(obj);
    }
}

注解案例:简单的测试框架

定义Check注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Check {
    
    
}

定义类

package demo;

public class Calculator {
    
    
    //加法
    @Check
    public void add(){
    
    
        String str = null;
        str.toString();
        System.out.println("1 + 0 =" + (1 + 0));
    }
    //减法
    @Check
    public void sub(){
    
    
        System.out.println("1 - 0 =" + (1 - 0));
    }
    //乘法
    @Check
    public void mul(){
    
    
        System.out.println("1 * 0 =" + (1 * 0));
    }
    //除法
    @Check
    public void div(){
    
    
        System.out.println("1 / 0 =" + (1 / 0));
    }
    public void show(){
    
    
        System.out.println("no bug...");
    }
}

测试类-解析程序

public class TestCheck {
    
    
    public static void main(String[] args) throws IOException {
    
    
        //1.创建计算器对象
        Calculator c = new Calculator();
        //2.获取字节码文件对象
        Class cls = c.getClass();
        //3.获取所有方法
        Method[] methods = cls.getMethods();
        BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));
        //4.判断方法上是否有check注解
        int num = 0;
        for(Method method: methods){
    
    
            if(method.isAnnotationPresent(Check.class)){
    
    
                //5.有就执行方法
                try{
    
    
                    method.invoke(c);
                    //6.捕获异常
                }catch (Exception e){
    
    
                    num++;
                    bw.write(method.getName()+"方法出异常了");
                    bw.newLine();
                    bw.write("异常的名称:"+ e.getCause().getClass().getSimpleName());
                    bw.newLine();
                    bw.write("异常的原因:"+e.getCause().getMessage());
                    bw.newLine();
                    bw.write("-----------------------------");
                    bw.newLine();
                }
            }
        }
        bw.write("本次测试一共出现了"+num+"次异常");
        bw.flush();
        bw.close();
    }
}

bug.txt文件

add方法出异常了
异常的名称:NullPointerException
异常的原因:null
-----------------------------
div方法出异常了
异常的名称:ArithmeticException
异常的原因:/ by zero
-----------------------------
本次测试一共出现了2次异常

4 小结

  1. 大多数时候,我们会使用注解,而不是自定义注解
  2. 注解是给编译器和解析程序用的
  3. 注解不是程序的一部分

猜你喜欢

转载自blog.csdn.net/xd963625627/article/details/105440829