反射和注解,枚举,junit单元测试

反射

  • 框架设计的灵魂
    • 框架 : 半成品软件 , 可以在框架的基础上进行软件开发 , 简化代码

(1)反射的概述

  • 将类的各个组成部分封装为其他对象 , 这就是反射机制
  • 像idea中的方法提示功能就是使用反射机制 : 用户在创建一个对象后 , 底层将这个对象所拥有的方法全部封装到了这个对象中 , 当用户输入这个对象的时候 , 在下边展示封装的所有的方法即可
  • 反射的好处:
    • 1.可以在程序运行的过程中 , 操作这些对象
    • 2.可以解耦合 , 提高程序的可扩展性
  • 一个java代码 , 通过编译 , 分成一个个部分 , 这些部分通过类加载器 , 封装到一个个对象中 , 像成员变量 , 成员方法 等 , 存储到硬盘中 , 最后当创建这个对象的时候 , 会调用这些对象

1644060469391


(2)反射的使用

1. Class.forName("全类名 : 包名.类名的方式");    //将字节码文件加载进内存 , 返回Class对象	(这是在Source源代码阶段)
//多用于配置文件中 , 读取文件 , 加载类 
2. 类名.class  :    //通过类名的属性class来获取    (这是在Class 类对象阶段 )
//多用于参数的传递 
3. 对象.getClass(); // getClass()方法在Object类中定义的 , (这是在Runtime 运行时阶段)
//多用于对象的获取字节码的方式
package com.sichen.javaweb.reflect;
import com.sichen.javaweb.domain.Person;
public class ReflectDemo1 {
    public static void main(String[] args) throws Exception {
//        1. Class.forName("全类名 : 包名.类名的方式");
        Class aClass = Class.forName("com.sichen.javaweb.domain.Person");
        System.out.println(aClass);
        //输出  class com.sichen.javaweb.domain.Person

        //2. 类名.class  :    //通过类名的属性class来获取
        Class personClass = Person.class;
        System.out.println(personClass);
        //输出  class com.sichen.javaweb.domain.Person

        //3.  对象.getClass();
        Person person = new Person();
        Class aClass1 = person.getClass();
        System.out.println(aClass1);
        //输出  class com.sichen.javaweb.domain.Person
        
        //比较三个对象是否是一个对象
        System.out.println(aClass == personClass);  //true
        System.out.println(aClass == aClass1);     //true
        System.out.println(personClass == aClass1); //true
        
    }
}
  • 结论 : 同一个字节码文件 (XX.class) ,在一次程序运行过程中 , 只会被加载一次 , 不论通过那种形式 , 获取到的class对象都是同一个

(3)反射的常用方法

暴力反射:
  • 正常情况下 , 打印带权限修饰符的数据时 , 会报非法访问异常 ,增加下边的代码后能够忽略访问权限修饰符的安全检查
  • age.setAccessible(true);
1.获取成员变量们(getField())
  • //获取成员变量对象
    Field[] getFields()      //获取所有public修饰的成员变量
    Field   getField(String name)  //获取指定名称的public修饰的成员变量
    //给成员变量设置值 : void set(Object obj , Object value)
    //获取成员变量中的值 :  get(Object obj)
    //这里边传一个参数 , 是一个对象 , 这个值是在那个对象里边存在的 , 就传哪个对象
    
    
    Field[] getDeclareFields()    //获取所有的成员变量 , 不考虑修饰符
    Field  getDeclareField(String name)//获取指定的成员变量 , 不考虑修饰符
        
    //正常情况下 , 打印带权限修饰符的数据时 , 会报非法访问异常 , 增加下边的代码后
    //能够忽略访问权限修饰符的安全检查
    age.setAccessible(true);//暴力反射
    
  • //上边获取到的这些对象 , 都有set 和 get方法 , 用来设置和获取对象中的值
    //这个数据是在那个类里边 , 下边的方法中的Object就要传一个这个类的对象 
    void set(Object obj , Object value);
    上边方法获取的对象名.set(类的对象名 , "要设置的值");
    
    get(Object obj);
    上边方法获取的对象名.get(类的对象名);
    
  • package com.sichen.javaweb;
    import java.lang.reflect.Field;
    public class Persontest {
        public static void main(String[] args) throws Exception {
            Class personClass = Person.class;
            Field[] fields = personClass.getFields();
            for (Field field : fields){
                System.out.println(field);
            }
            System.out.println("-----------------------");
    
            Field a = personClass.getField("a");
            //获取成员变量的值
            Person person = new Person();
            Object o = a.get(person);
            System.out.println(o);  //输出 sichen
    
            a.set(person , "思尘");
            System.out.println(a.get(person));//输出 思尘
            System.out.println("-------------------------------");
            
            Field[] declaredFields = personClass.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                //忽略访问权限修饰符的安全检查
                declaredField.setAccessible(true);//暴力反射
                System.out.println(declaredField.get(person));
                //输出null 100 思尘
            }
    
            Field age = personClass.getDeclaredField("age");
            //忽略访问权限修饰符的安全检查
            age.setAccessible(true);//暴力反射
            System.out.println(age.get(person));
            //输出 100
        }
    }
    
2.获取构造方法们(getConstructors())
  • //获取构造器对象 
    Constructor[]  getConstructor()
    Constructor getConstructor(... parameterTypes)
    
    Constructor[] getDeclaredConstructors()  
    Constructor getDeclaredConstructor(... parameterTypes)  
    //这些方法就是用来获取构造方法的 , 获取的构造方法对象中 , 
    //   有一个newInstance 的方法 是用来创建对象的
    
  • package com.sichen.javaweb;
    import java.lang.reflect.Constructor;
    public class Persontest2 {
        public static void main(String[] args) throws Exception{
            Class personClass = Person.class;
            //这里可以传一个带参的 , 也可以不带参 (访问的就是无参构造方法)
    Constructor constructor = personClass.getConstructor(String.class, int.class);
            //创建对象 , 上边如果不带参数 , 这里也不能带参数
            Object sichen = constructor.newInstance("sichen", 90);
            System.out.println(sichen);
            
            //如果构造使用空参的构造方法 , 也可直接执行下边的代码 , 一步到位
            //这个方法是Class对象中 , 直接就有的方法
            Object o = personClass.newInstance();
            System.out.println(o);
        }
    }
    
3.获取成员方法们(getMethods())
  • //获取方法对象 
    Method[] getMethods()  
    Method   getMethod(String name,... parameterTypes)  
    
    Method[] getDeclaredMethods()  
    Method   getDeclaredMethod(String name,... parameterTypes)    
    //这些方法中有一个方法 , 是用来执行这些获取的方法的
        //这里obj专递的是调用着对象 , args是调用者传递的参数
    Object invoke(Object obj , Object... args);  
    //获取方法名称
    String getName();
    
  • package com.sichen.javaweb;
    import java.lang.reflect.Method;
    public class Persontest3 {
            public static void main(String[] args) throws Exception{
                Class personClass = Person.class;
                Person person = new Person();
                //不带参的方法
                Method eat = personClass.getMethod("eat");
                eat.invoke(person);
    
                //带参的方法  , 方法带的什么参数 , 这里后边就要带什么参数
                Method eat1 = personClass.getMethod("eat", String.class);
                eat1.invoke(person , "鸡");
                
                //获取方法名
                Method[] methods = personClass.getMethods();
                for (Method method : methods) {
                    String name = method.getName();
                    System.out.println(name);
                }
            }
    }
    
4.获取类名(getName())
  • String getName();
    String name = personClass.getName();
    System.out.println(name);
    //输出 : com.sichen.javaweb.Person
    

案例 : 模拟一个"框架"

需求 : 写一个"框架" , 在不改变该类任何代码的前提下 , 可以帮我们创建任意类的对象 , 并执行其中任意的方法

  • 实现 :

    • 1.配置文件
    • 2.反射
  • 步骤 :

    • 1.将需要创建的对象的全类名和需要执行的方法定义在配置文件中
    • 2.在程序中加载读取配置文件
    • 3.使用反射技术 , 来加载类文件 , 进内存
    • 4.创建对象
    • 5.执行方法
  • package javaweb;
    import java.io.InputStream;
    import java.lang.reflect.Method;
    import java.util.Properties;
    
    public class ReflectTest {
        public static void main(String[] args) throws Exception {
            //1.加载配置文件
            //1.1创建properties对象
            Properties properties = new Properties();
            //1.2 加载配置文件 , 转换为一个集合
            //1.2.1 获取class目录下的配置文件
            ClassLoader classLoader = ReflectTest.class.getClassLoader();
            //获取pro.properties文件的一个字节流
            InputStream resourceAsStream = classLoader.getResourceAsStream("pro.properties");
            properties.load(resourceAsStream);
    //Properties类的重要方法
    //Properties 类存在于包 Java.util 中,该类继承自 Hashtable
    //1. getProperty(String key),用指定的键在此属性列表中搜索属性。也就是通过参数 key ,得到 key 所对应的 value。
    //2. load(InputStream inStream)从输入流中读取属性列表(键和元素对)。通过对指定的文件(比如说上面的 test.properties 文件)
    //进行装载来获取该文件中的所有键 - 值对。以供 getProperty ( String  key) 来搜索。
    //3. setProperty ( String  key, String  value) ,调用 Hashtable 的方法 put 。他通过调用基类的put方法来设置 键 - 值对。
    //4. store ( OutputStream  out, String  comments) ,   以适合使用 load 方法加载到 Properties 表中的格式,
    //将此 Properties 表中的属性列表(键和元素对)写入输出流。与 load 方法相反,该方法将键 - 值对写入到指定的文件中去。
    //5. clear () ,清除所有装载的 键 - 值对。该方法在基类中提供。
    
            //2.获取配置文件中定义的数据
            String classname = properties.getProperty("classname");
            //配置文件中: classname = javaweb.Person
            String method = properties.getProperty("method");
            //配置文件中: method = eat 
    
            //3.加载类进内存
            Class cls = Class.forName(classname);
            //4.创建对象
            Object o = cls.newInstance();
            //5.获取方法对象
            Method method1 = cls.getMethod(method);
            //6.执行方法
            method1.invoke(o);
        }
    }
    

注解(Annotation)

(1)注解的概述

  • 注解 : 说明程序的 , 是给计算机看的

    • 它是JDK1.5及之后版本引入的一个新特性 , 与类 , 接口 , 枚举 是在同一个层次 , 它可以声明在

      包 , 类 , 字段 , 方法 , 局部变量 , 方法参数等 的前面 , 用来对这些元素进行说明 , 注释 .

  • 作用分类 :

    • ①编写文档 : 通过代码里的标识的注解生成文档 (生成文档doc文档)
      • 抽取doc文档 : javadoc -encoding utf-8 javadoc.java
      • 在抽取时直接抽取会提示 : 编码GBK的不可映射字符 解决方法就是 增加-encoding utf-8 即可
      • 1642753556168
      • 抽取之后会生成很多文件
      • 1642753745628
      • 点击index.html : 能够看到 , 已经抽取成功了
      • 1642753798406
    • ②代码分析 : 通过代码里的标识的注解对代码进行分析 (使用反射)
    • ③编译检查 : 通过代码里的标识的注解让编译器能够实现基本的编译检查 (@Override)

(2)JDK中预定义的一些注解

@Override : 检测被该注解标注的方法是否是继承自父类(接口)的

@Deprecated : 将该注解标注的内容显示为已过时

@SuppressWarnings : 压制警告的


(3)自定义注解

  • 格式

    • public @interface 注解名 {}

    • 使用 : @注解名

  • 本质 : 注解本质上就是一个接口 , 该接口默认继承Annotation接口

    • Compiled from "ce.java"
      public interface ce extends java.lang.annotation.Annotation {
      }
      
  • 注解中的属性 : 注解本质是一个接口 , 所以接口中能够定义的 , 注解中也可以定义

    • 属性 : 接口中的抽象方法
  • 要求 :

    • 1.属性的返回值类型有下列取值 :
      • 基本数据类型
      • String
      • 枚举
      • 注解
      • 以上类型的数组 :
  • 注意 : 数组赋值比较特殊 , 数组赋值时 , 值使用"{}“包裹 , 如果数组中只有一个值的时候 , 则”{}"省略

    • 2.定义了这个属性 , 在使用时需要给这些属性赋值
      • 定义了属性 , 就要赋值 , 定义几个属性 , 就要赋几个值
      • ①也可以设置默认值 default 设置默认值
      • ②如果只有一个属性需要赋值 , 并且属性名称是value , 则value可以省略 , 直接定义即可
    • 1642772667523
  • 元注解 :

    • 用于描述注解的注解
      • @Target : 描述注解能够作用的位置
      • @Retention : 描述注解被保留的阶段
      • @Documented : 描述注解是否被抽取到API文档中
      • @Inherited : 描述注解是否被子类继承

(4)在程序使用(解析)注解

  • 获取注解中定义的属性值

  • 1.获取注解定义的位置的对象 (Class , Method , Field)

  • 2.获取指定的注解

    • getAnnotation (Class)

    • 其实就是在内存中生成了一个该注解接口的子类实现对象

    • public class ProImpl implements Pro{
          public String className(){
              return "com.sichen.javaweb.demo1";
          }
          public String method(){
              return "show1";
          }
      }
      
  • 3.调用注解中的抽象方法获取配置的属性值

    • MyAnno注解

    package com.sichen.javaweb;
    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();
    }

    
    - pro.java
    
    - ```java
    package com.sichen.javaweb;
    @MyAnno(className = "com.sichen.javaweb.demo1" , methodName = "show1")
    public class pro {
        public static void main(String[] args) {
            //1.解析注解
            //1.1获取该类的字节码文件对象
            Class<pro> proClass = pro.class;
            //2获取上边的注解对象
            MyAnno an = proClass.getAnnotation(MyAnno.class);
            //3.调用注解的对象中定义的抽象方法 , 获取返回值
            String s = an.className();
            String s1 = an.methodName();
            System.out.println(s); //输出 com.sichen.javaweb.demo1
            System.out.println(s1);  //输出 show1
        }
    }
    
    • demo1

    • package com.sichen.javaweb;
      public class demo1 {
          public void show1(){
              System.out.println("show1 .....");
          }
      }
      

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

  • Calculater.java

  • package com.sichen.demo;
    public class Calculater {
        //加
        @Check
        public void add(){
            System.out.println("1 + 0 =" + (1 + 0));
        }
        //减
        @Check
        public void jian(){
            System.out.println("1 - 0 =" + (1 - 0));
        }
        //乘
        @Check
        public void cheng(){
            System.out.println("1 * 0 =" + (1 * 0));
        }
        //除
        @Check
        public void chu(){
            System.out.println("1 / 0 =" + (1 / 0));
        }
        public void show(){
            System.out.println("show/.....");
        }
    }
    
  • Check.java

  • package com.sichen.demo;
    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 {
    }
    
  • TestCheck.java

package com.sichen.demo;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class TestCheck {
    public static void main(String[] args) throws IOException {
        //1.获取计算器对象
        Calculater calculater = new Calculater();
        //2.获取字节码文件对象
        Class<? extends Calculater> aClass = calculater.getClass();
        //3.获取所有方法
        Method[] methods = aClass.getMethods();
        //出现异常的次数
        int number = 0;
        BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));
        for (Method method : methods) {
            //4.判断方法上是否有Check注解
            if (method.isAnnotationPresent(Check.class)){
                try {
                    method.invoke(calculater);
                } catch (Exception e) {
                    number++;
                    bw.write(method.getName()+"方法出现异常了");
                    bw.newLine();
                    bw.write("异常的名称是:" + e.getCause().getClass().getSimpleName());
                    bw.newLine();
                    bw.write("异常的原因是:" + e.getCause().getMessage());
                    bw.newLine();
                    bw.write("--------------------");
                }
            }
        }
        bw.write("本次运行出现异常" +number+ "次");
    }
}
  • 小结 :

    以后大多数时候 , 我们会使用注解 , 而不是自定义注解

  • 注解给谁用 ?

    • 1.编译器
    • 2.给解析程序用
  • 注解不是程序的一部分 , 可以理解为注解就是一个标签


详细注解:

doc文档中的注解

@param : 参数

1642753954734

@author : 作者

@version : 版本

@since : 从以下版本开始

1642753928861


JDK中预定义的一些注解

@Override :

  • 检测被该注解标注的方法是否是继承自父类(接口)的

@Deprecated :

  • 将该注解标注的内容 , 已过时
  • 1642754686555

@SuppressWarnings :

@SuppressWarnings ("all")  // 压制所有的警告

一般传递参数all , 并且将注解放在类上 , 这样这个类中所有的警告都不会出现

  • 压制警告的 : 在使用工具时 , 会给你提示各种信息 , 如果不想要提示这种信息 , 就可以增加注解
  • 1642754900839

元注解 :

  • 用于描述注解的注解 : 都是放在注解上边的注解

  • @Target : 描述注解能够作用的位置

    • @Target(value = {ElementType.TYPE})   //可以作用于类上
      @Target(value = {ElementType.METHOD})  //可以作用于方法上
      @Target(value = {ElementType.FIELD})  //可以作用于成员变量上
      
    • 1642819638560
  • @Retention : 描述注解被保留的阶段

    • @Retention(RetentionPolicy.RUNTIME)  //当前被描述的注解 , 会被保留到 Runtime运行时阶段
      @Retention(RetentionPolicy.CLASS)   //当前被描述的注解 , 会被保留到class字节码文件中 , 并被JVM读取到
      @Retention(RetentionPolicy.SOURCE)  //当前被描述的注解 , 会被保留到Source 源代码阶段
      
  • @Documented : 描述注解是否被抽取到API文档中 , 这样在抽取doc文档的时候 , 就会把这个被修饰的内容一块抽取

  • @Documented   //表示被该注解标识的内容可以被抽取
    public @interface MyAnno {}
    
  • @Inherited : 描述注解是否被子类继承

  • @Inherited     //表示被此注解标识的注解 , 能够被子类继承
    

Junit单元测试

  • 测试的分类 :

    • (1)黑盒测试
      • 看不到详细代码 , 不需要写代码只能测试功能 , 通过输入输出 , 看到是否符合自己的设定 (相当于游戏测试 , 程序打包成成品 , 用户来体验看是否有bug)
    • (2)白盒测试
      • 要去关注程序执行的流程 , 看到详细代码 , 需要写代码 (就相当于边写边调试)
  • Junit就是白盒测试的一种

    • 使用步骤 :

      • 第一步 : 定义一个测试类 (测试用例)
        • 建议 : 测试类名 : 被测试的类名 + Test
          • 包名 : xx.xxx.xxx.text
      • 第二步 : 定义测试方法 : 可以独立运行
        • 建议 : 方法名 : text测试的方法 testAdd()
          • 返回值 : void
          • 参数列表 : 空参
      • 第三步 : 给方法加一个注解 : @Test
      • 第四步 : 导入Junit 依赖环境
      • 第五步 : 判定结果
    • 注意: 一般在测试的时候不会看输出的结果 , 因为输出的结果可能不准确

      • 这里使用断言的形式 :

      • Assert.assertEquals(规定的结果值 , 实际的结果值)

      • //Assert中有很多方法 , 这里使用这一种就行
         @Test
            public void testAdd(){
                //创建计算器对象
            Calculator calculator = new Calculator();
            int add = calculator.add(1, 2);
            Assert.assertEquals(3,add);
          }
        
        1642687745772
    • 测试的时候 , 有时候代码会重复 , 比如说创建对象 , 释放资源

    • 这个时候 有两个注解 : @Before @After (这两个注解仅针对@Test注解的方法)

    • package test;
      import com.sichen.javaweb.Calculator;
      import org.junit.After;
      import org.junit.Assert;
      import org.junit.Before;
      import org.junit.Test;
      public class CalculatorTest {
          //在这里定义一个变量 , 后边就可以直接使用了 , 不用每个方法中再去new
          private Calculator calculator;
          /**
           * 在所有方法执行前都要先执行这个方法
           */
          @Before
          public void init(){
              calculator = new Calculator();
          }
          /**
           * 所有程序在执行完之后都会自动执行这个方法
           */
          @After
          public void close(){
              System.out.println("释放资源");
          }
          /**
           * 测试方法add
           */
          @Test
          public void testAdd(){
              int add = calculator.add(1, 2);
              Assert.assertEquals(3,add);
          }
          /**
          *  测试方法sub
          */
          @Test
          public void testsub(){
              int sub = calculator.sub(1, 2);
              Assert.assertEquals(-1 , sub);
          }
      }
      

测试的时候 , 有两种情况

  • 第一种 : 测试成功 :
  • 1642687882797
  • 第二种 : 有异常
  • 1642687778684

枚举类

1642756143214

public void close(){
        System.out.println("释放资源");
    }
    /**
     * 测试方法add
     */
    @Test
    public void testAdd(){
        int add = calculator.add(1, 2);
        Assert.assertEquals(3,add);
    }
    /**
    *  测试方法sub
    */
    @Test
    public void testsub(){
        int sub = calculator.sub(1, 2);
        Assert.assertEquals(-1 , sub);
    }
}
```

测试的时候 , 有两种情况

  • 第一种 : 测试成功 :
  • [外链图片转存中…(img-6a5Y66Q0-1646802348291)]
  • 第二种 : 有异常
  • 1642687778684

枚举类

[外链图片转存中…(img-xWbGuQYa-1646802348292)]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值