Java(L3)-- Junit、反射、注解

Junit、反射、注解

今天学习的是 Java 基础加强部分的内容,包括 Junit、反射、注解三部分内容。

Part I. Junit

  1. 测试的分类:

    • 黑盒测试:只关注输入与输出的结果,不关注内部实现的细节。
    • 白盒测试:既关注输入与输出的结果,又关注程序的执行流程。
  2. Junit:白盒测试

    步骤:

    • 定义一个测试类:
      • 类名一般为 XxxxTest,如 CalculatorTest
      • 报名一般为 xxx.xxx.test,如
    • 定义测试方法
      • 方法名:testXxxx,如 testAdd()
      • 返回值:void
      • 参数列表:空参
    • 给测试方法加注解 @Test
    • 导入 junit 依赖包

    运行结果:

    • 红色:测试方法运行发现错误

    • 绿色:测试方法运行未发现错误

    • 注:一般情况下,会用断言操作来处理测试函数中得到的结果

      Assert.assertEquals(期望的结果,运算的结果);
      

    补充:

    • @Before:修饰的方法会在测试方法前自动执行
    • @After:修饰的方法会在测试方法执行后自动执行

Part II. 反射

  1. Java代码执行的三个阶段

    一般情况下,Java 代码执行过程分为三个阶段:

    • Source 源代码阶段
    • Class 类对象阶段
    • Runtime 运行时阶段

    下面对这三个阶段依次解释:

    Source 阶段

    一般而言,当我们定义一个类时,会定义其成员变量构造方法成员方法,并将其放在一个 .java 文件中。该文件经过编译后会生成一个 .class 文件,即字节码文件,其中也包含了成员变量、构造方法和成员方法三部分。Source 源代码阶段主要包含这两个部分。

    Class 阶段:

    字节码文件经过类加载器(ClassLoader)加载到内存中,每个类经过加载后对应一个 Class 对象,该对象包括一个存放成员变量对象(field)的数组 Field [] fields;一个存放构造方法对象(constructor)的数组 Constructor [] constructors;和一个用于存放成员方法对象(method)的数组 Method [] methods。

    Runtime 阶段:

    内存中的 Class 类对象可以直接创建类对象,即进入 Runtime 阶段。

  2. 反射

    反射即程序在运行时可以访问、检测和修改它本身状态或行为的一种能力(维基百科)。在 Java 中,由 Source 阶段和 Runtime 阶段得到 Class 类对象并进行各类操作的过程称之为反射(我的理解)。

    得到 Class 对象的方法:

    • Class.forName("全类名"):将字节码加载进内存,返回 Class 对象

      多用于配置文件,将类名、方法名等定义在配置文件中。

    • 类名.class:通过类名的属性class获取

    • 对象.getClass():由对象获取 Class,getClass()方法在Object类中定义。

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

  3. Class 对象的功能

  • 获取成员变量:
    • Field[] getFields():获取所有public修饰的成员变量
    • Field getField(String name):获取指定名称的 public修饰的成员变量
    • Field[] getDeclaredFields():获取所有的成员变量
    • Field getDeclaredField(String name):获取指定名称的成员变量
  • 获取构造方法
    • Constructor<?>[] getConstructors():获取所有public修饰的构造函数
    • Constructor<T> getConstructor(Class<?>... parameterTypes):获取指定名称的 public 修饰的构造函数
    • Constructor<?>[] getDeclaredConstructors():获取所有构造函数
    • Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):获取指定名称的构造函数
  • 获取成员方法
    • Method[] getMethods():获取所有public修饰的成员方法
    • Method getMethod(String name, Class<?>... parameterTypes):获取指定名称的 public 修饰的成员方法
    • Method[] getDeclaredMethods():获取所有的成员方法
    • Method getDeclaredMethod(String name, Class<?>... parameterTypes):获取指定名称的成员方法。
  • Field:成员变量
    • 设置值:void set(Object obj, Object value)
    • 获取值:get(Object obj)
    • 忽略访问权限修饰符的安全检查:setAccessible(true),又称暴力反射
  • Constructor:构造方法
    • 创建对象:T newInstance(Object... initargs)
    • 创建空参对象也可以直接调用 Class 对象的 newInstance 方法
  • Method:方法对象
    • 执行方法:Object invoke(Object obj, Object... args)
    • 获取方法名称:String getName()
  1. 案例

    要求:实现一个简易框架,可以从配置文件读取类名和方法名并执行该类的该方法。

    public static void main(String[] args) throws Exception {
        //读取配置文件并加载
        Properties pro = new Properties();
        ClassLoader classLoader = ReflectTest.class.getClassLoader();
        InputStream resourceAsStream = classLoader.getResourceAsStream("pro.properties");
        pro.load(resourceAsStream);
     //获取类名和方法名
        String className = pro.getProperty("className");
        String methodName = pro.getProperty("methodName");
     //创建 Class 对象并创建实例
        Class<?> cls = Class.forName(className);
        Object o = cls.newInstance();
     //由方法名获取成员方法并执行
        Method method = cls.getMethod(methodName);
        method.invoke(o);
    }
    

Part III. 注解

注解(Annotation),也叫元数据,一种代码级别的说明。它是 JDK1.5 及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

  1. 注解的作用分类

    • 编写文档:通过代码里标识的注解生成文档
    • 代码分析:通过代码里标识的注解对代码进行分析
    • 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查(如 Override )
  2. JDK 中预定义的一些注解

    • @Override:检测被该注解标注的方法是否是继承自父类(接口)的
    • @Deprecated:该注解标注的内容,表示已过时
    • @SuppressWarnings:压制警告。若传递的是 “all” 参数,则压制所有警告
  3. 自定义注解

    • 格式:
    元注解
    public @interface 注解名称{
     属性列表;
    }
    
    • 本质:注解本质上就是一个接口,该接口默认继承Annotation接口
    public interface MyAnno extends java.lang.annotation.Annotation {}
    
    • 属性:

      由于注解本身是一个对象,它的“属性”实际上就是他的抽象函数。

      属性的返回值类型有下列取值:

      • 基本数据类型
      • String
      • 枚举
      • 注解
      • 以上类型的数组

      定义了属性,在使用时需要给属性赋值:

      • 如果定义属性时,使用default关键字给属性默认初始化值,则使用注解时,可以不进行属性的赋值
      • 如果只有一个属性需要赋值,并且属性的名称是value,则value可以省略,直接定义值即可
      • 数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略
    • 元注解:用于描述注解的注解

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

        其参数 ElementType 的取值:

        • TYPE:可以作用于类上
        • METHOD:可以作用于方法上
        • FIELD:可以作用于成员变量上
      • @Retention:描述注解被保留的阶段

        其参数一般取 RetentionPolicy.RUNTIME,会保留到字节码中并且会被 JVM 获取到

      • @Documented:描述注解是否被抽取到 api 文档中

      • @Inherited:描述注解是否被子类继承

    • 在程序中获取注解的属性值

      • 获取注解所修饰的对象(Class,Method 或 Field)

      • 使用 getAnnotation(Class) 获取制定的注解

        注:执行该方法实际上就是在内存中实现了该注解的子类对象

      • 使用注解的抽象函数获取属性值

    • 案例1:

      使用注解实现 Part II 中的简易框架:

      首先定义注解对象:

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

然后定义主类:

 ```java
 @Pro(className = "cn.dianshi.domain.Person", methodName = "sleep")
 public class ReflectTest {
     public static void main(String[] args) throws Exception {
         Class<ReflectTest> reflectTestClass = ReflectTest.class;
         Pro annotation = reflectTestClass.getAnnotation(Pro.class);
         String s = annotation.className();
         String s1 = annotation.methodName();
         System.out.println(s);
         System.out.println(s1);
 
         Class<?> aClass = Class.forName(s);
         Method method = aClass.getMethod(s1);
         Object o = aClass.newInstance();
         method.invoke(o);
     }
 }
 ```
  • 案例2:

    使用注解对以下类的方法进行测试,如有错误则将错误信息写在 bug.txt 中:

    Calculator.java:

    public class Calculator {
        //加法
        @Check
        public void add(){
            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));
        }
    }
    

    实现:

    首先定义 Check 注解:

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

    然后编写主类:

    public class TestCheck {
        public static void main(String[] args) throws Exception {
            Calculator calculator = new Calculator();
            Class<? extends Calculator> aClass = calculator.getClass();
            Method[] methods = aClass.getMethods();
            int count = 0;
            BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("bug.txt"));
            for (Method method : methods) {
                if(method.isAnnotationPresent(Check.class)){
                    try {
                        method.invoke(calculator);
                    } catch (Exception e) {
                        count ++;
                        bufferedWriter.write(method.getName() + "方法出现异常");
                        bufferedWriter.newLine();
                        bufferedWriter.write("异常名称" + e.getCause().getClass().getSimpleName());
                        bufferedWriter.newLine();
                        bufferedWriter.write("异常的原因" + e.getCause().getMessage());
                        bufferedWriter.newLine();
                        bufferedWriter.write("--------------------");
                        bufferedWriter.newLine();
                    }
                }
            }
    
            bufferedWriter.write("一共出现" + count + "次异常");
    
            bufferedWriter.flush();
            bufferedWriter.close();
        }
    }
    
  • 注解小结:

    • 多数情况下我们只是使用注解,只有极少数情况下才会自定义注解
    • 注解的服务对象是编译器和解析程序
    • 注解不是程序的一部分,可以理解为注解就是一个标签
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,583评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,669评论 2 374
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,684评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,682评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,533评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,396评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,814评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,458评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,745评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,789评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,565评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,410评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,828评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,050评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,342评论 1 253
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,793评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,010评论 2 337