A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 小石姐姐 于 2018-12-6 17:38 编辑

day14 JUnit 反射 注解 今日内容
JUnit单元测试: 测试一个方法有没有问题, 是否能否按照我们设计的方式运行 (不用main方法直接运行)
        (会使用即可)
反射: 让Java程序在运行时, 帮我们创建对象, 调用方法
        反射创建一个类的对象
        反射修改/获取一个对象的成员变量
        反射调用一个对象的成员方法
        案例: 通过反射技术获取配置文件中的信息 (后期框架的底层实现原理)
注解: 给程序看的一些信息, 便于实现某些功能
        如何自定义注解
        如何利用反射解析注解中的信息
        案例: 通过注解和反射技术获取配置文件中的信息 (后期框架的底层实现原理)
案例:
        反射和注解模拟JUnit
JUnit单元测试
JUnit: 测试概述知识点:
什么是测试, 为什么要测试
什么是黑盒测试, 什么是白盒测试
总结:
测试:
        测试代码是否正确, 能否达到预期效果
    public int 除(int a, int b) {
        if (b == 0) {
                        // 补救措施
        }
            return a / b;
    }
    除(2, 0);
        
测试分类:
        1. 黑盒测试:不需要写代码, 给输入值, 看程序是否能够输出期望的值
                比如你下载一个APP, 随便点点点, APP闪退了
        2. 白盒测试:需要写代码的. 关注程序具体的执行流程
                比如今天学习的JUnit大
集成测试 (多个人, 多个模块)
接口测试 (网络请求的接口)
单元测试 (测试每个定义的方法)
小![](./img/测试分类.bmp)补充: JUnit: 使用步骤知识点:
JUnit的使用步骤
如何根据颜色判断JUnit测试成功还是失败
如何通过 assert断言 来判断结果
        int result = add(1,2)
        // 判断result的值是否和我们期望的一样: 断言(判断) assert
总结:
JUnit使用步骤:
        1. 定义一个测试类(也叫测试用例)
            包名:xxx.xxx.xx.test
                被测试的类名:   Calculator   
                对应的测试类名: CalculatorTest
        2. 定义测试方法:可以独立运行
                被测试的方法名:       add()      
        对应的测试方法名: testAdd()  
                    建议测试方法的返回值是void, 参数列表是空参
        3. 在方法上加 @Test 注解
        4. 在 @Test 注解上按 Alt+Enter, 选择 "Add 'JUnit4' to Classpath" 导入JUnit依赖环境
        5. 在方法名上右键, 选择 "Run '方法名()'"
            
判定结果:
        红色:失败
        绿色:成功.(测试通过)
        
断言: Assert
        使用断言操作来判断结果是否符合预期:                 Assert.assertEquals(期望的结果, 运算的结果);                如果 期望的结果 和 运算的结果 相等, 则认为测试通过, 否则测试失败
        
测试失败的原因提示:
    java.lang.AssertionError:
    Expected :1    (表示我期望得到的是1)
    Actual   :-1   (但是实际得到的是-1)
补充: 5分钟练习: 使用JUnit
需求:
建类Calculator, 表示计算器
[AppleScript] 纯文本查看 复制代码
public class Calculator {
        // 计算两个int相加
    public int add(int a, int b) {
        return a + b;
    }
        // 计算两个int相减
    public int sub(int a, int b) {
        return a - b;
    }
}

建测试类CalculatorTest:
        定义测试add的方法: public void testAdd() {}, 添加 @Test 注解
                方法中:
                        创建Calculator对象, 调用 add() 方法, 计算1和2的和
                        使用 Assert.assertEquals() 方法测试计算的结果是否为3
        定义测试sub的方法: public void testSub() {}, 添加 @Test 注解
                方法中:
                        创建Calculator对象, 调用 sub() 方法, 计算1和2的差
                        使用 Assert.assertEquals() 方法测试计算的结果是否为-1
    分别在 testAdd() 和 testSub() 方法上运行JUnit测试, 查看结果
代码:
// 专门用于测试Calculator类的测试类
[Java] 纯文本查看 复制代码
public class CalculatorTest {    // 不要写main方法, 要用JUnit提供的测试方式
    // 每个方法对应一个测试方法    @Test  // 红色, Alt + 回车, 选 Add 'JUnit4' to classpath
    public void testAdd() {  // 在测试方法中, 编写测试代码
        // 测试Calculator的add方法
        Calculator calculator = new Calculator();
        int result = calculator.add(1, 2);        // 不要打印, 而是使用JUnit提供的Assert类的方法, 来断言结果
        // Assert.assertEquals(我们期望的结果, 实际得到的结果);
        Assert.assertEquals(3, result);
    }    @Test  // 每个测试方法上都要加上 @Test 注解, 这样才能将该方法独立运行
    public void testSub() {
        // 测试Calculator的sub方法
        Calculator calculator = new Calculator();
        int result = calculator.sub(2, 1);        Assert.assertEquals(1, result);
    }}

JUnit: @Before, @After知识点:
@Before 有什么作用
@After 有什么作用
总结:
@Before: 修饰的方法会"在每个测试方法执行 之前"被执行
@After:  修饰的方法会"在每个测试方法执行 之后"被执行注意:
        @Before, @After 修饰的方法可以有多个, 但是谁先执行是由JUnit内部来决定的, 没有明显的规律
        所以不要写多个@Before, @After 修饰的方法
补充: 5分钟练习: 使用@Before, @After
需求:
在刚才的CalculatorTest测试类中, 再定义两个方法:
        @Before
        
[Java] 纯文本查看 复制代码
public void start() {
        System.out.println("开始测试方法");
        }
        
        @After
        public void end() {
        System.out.println("测试方法结束");
        }

重新测试, 查看是否每个方法测试前后都有输出
补充:
工作:
        需求讨论 -> 编写代码 -> 测试 -> 发布上线开发环境
测试环境 测试人员
生产环境 真正用户开发人员编写单元测试
持续集成CI
测试报告: 全都通过就自动发布到测试环境(给测试人员测试用的 黑盒测试)
测试覆盖率: 100%
反射
反射: 概述知识点:
什么是框架
什么是反射
反射有什么好处
Java代码在计算机中有哪3个阶段
总结:
框架:
        半成品软件. 可以在框架的基础上进行软件开发, 简化编码
        比如JUnit就是一个单元测试框架, 它不是一个独立的软件, 而是和我们开发的软件结合, 简化代码测试
        
反射: 将类的各个组成部分, 封装为其他对象, 这就是反射机制
        成员变量(字段): Field类的对象
        构造方法: Constructor类的对象
        成员方法: Method类的对象
        好处:
                1. 可以在程序运行过程中, 操作这些对象
                2. 可以解耦, 提高程序的可扩展性
               
Java代码在计算机中的3个阶段:
        SOURCE: 源代码阶段
        CLASS: 类对象阶段
        RUNTIME: 运行时阶段![](./img/Java代码的三个阶段.bmp)补充: 反射: 获取字节码对象的3种方式知识点:
获取一个类的字节码对象有哪3种方式
同一个类的字节码对象能有几个
总结:
获取一个类的字节码对象的3种方式:
        1. Class.forName("全类名")
                将字节码文件加载进内存,返回Class对象
                适用场景: 多用于配置文件,将类名定义在配置文件中. 读取文件, 加载类
        2. 类名.class                         .
                通过类名的属性class获取
                适用场景: 多用于参数的传递  getConstructor(String.class, int.class)
        3. 对象.getClass()
                getClass()方法在Object类中定义
                适用场景: 多用于对象的获取字节码的方式 p.getClass()
               
同一个类的字节码对象, 只有"唯一的一个"
补充: 反射: Class的方法概述知识点:
Class类中提供了哪些方法获取 成员变量Field, 构造方法Constructor, 成员方法Method
总结:
java.lang.Class<T>类: 表示一个类的字节码对象, 其中包含该类中定义的内容
// 成员方法
// 1. 获取成员变量们
        Field[] getFields(): 获取所有 public 的成员变量
        Field getField(String name): 获取指定名称的 public 的成员变量
        Field[] getDeclaredFields(): 获取所有的成员变量, 不考虑权限修饰符
        Field getDeclaredField(String name): 获取指定名称的成员变量, 不考虑权限修饰符
// 2. 获取构造方法们
        Constructor<?>[] getConstructors(): 获取所有 public 的构造方法
        Constructor<T> getConstructor(Class<?>... parameterTypes): 获取指定的 public 构造方法
        Constructor<?>[] getDeclaredConstructors(): 获取所有的构造方法, 不考虑权限修饰符
        Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes): 获取指定的构造方法, 不考虑权限修饰符
// 3. 获取成员方法们:
        Method[] getMethods(): 获取所有 public 的成员方法
        Method getMethod(String name, Class<?>... parameterTypes) : 获取指定的 public 成员方法
        Method[] getDeclaredMethods(): 获取所有的成员方法, 不考虑权限修饰符
        Method getDeclaredMethod(String name, Class<?>... parameterTypes): 获取指定的成员方法, 不考虑权限修饰符
// 4. 获取Class对象代表的类的全类名
        String getName(): 获取当前Class对象代表的类的全类名
// 5. 创建对象
        T newInstance(): 使用当前类的空参构造, 创建一个对象
补充: 反射: 获取成员变量Field知识点:
如何通过反射技术, 设置一个对象中某个属性的值
如何通过反射技术, 获取一个对象中某个属性的值
总结:
java.lang.Class<T>类: 表示一个类的字节码对象, 其中包含该类中定义的内容
        // 获取功能
    1. 获取成员变量们
                Field[] getFields(): 获取所有 public 的成员变量
                Field getField(String name): 获取指定名称的 public 的成员变量
                Field[] getDeclaredFields(): 获取所有的成员变量, 不考虑权限修饰符
                Field getDeclaredField(String name): 获取指定名称的成员变量, 不考虑权限修饰符java.lang.reflect.Field: 表示一个成员变量
        // 成员方法 String name;   Person p = new Person(); p2.name = "abc";
        void set(Object obj, Object value): 设置指定对象的成员变量的值 field.set(p1, "abc")
        Object get(Object obj): 获取指定对象的成员变量的值 field.get(p1)
        void setAccessible(boolean flag): 传true时忽略访问权限修饰符的安全检查. 暴力反射 field.set
补充: 5分钟练习: 使用反射获取类中成员变量, 并赋值和获取值
需求:
定义Person类:
        成员变量(注意权限):
                public String name;
                private int age;
        生成无参/有参构造, set/get方法, toString方法
定义测试类
        在main()方法中:
                通过Person类名先获取Person类的Class对象
                创建Person对象p
                通过反射获取p对象的name属性的Field对象, 将姓名修改为 "张三"
                通过反射获取p对象的age属性的Field对象, 将年龄修改为 18
                打印p对象, 查看是否修改
代码:
[Java] 纯文本查看 复制代码
public class Person {    public String name;
    private int age;    public Person() {
    }    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }    public String getName() {
        return name;
    }    public void setName(String name) {
        this.name = name;
    }    public int getAge() {
        return age;
    }    public void setAge(int age) {
        this.age = age;
    }    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

[Java] 纯文本查看 复制代码
public class Test {
    public static void main(String[] args) throws Exception {
        // 通过Person类名先获取Person类的Class对象
        Class cls = Person.class;        // 创建Person对象p, 用来修改它的属性值
        Person p = new Person();        // 通过反射获取p对象的name属性的Field对象, 将姓名修改为 "张三"
        Field nameField = cls.getField("name");
        nameField.set(p, "张三");        // 通过反射获取p对象的age属性的Field对象, 将年龄修改为 18
        // age是private的, 获取的时候要调用带有 Declared 的方法
        Field ageField = cls.getDeclaredField("age");
        // age是private的, 所以操作前, 需要先暴力反射
        ageField.setAccessible(true);
        ageField.set(p, 18);        // 打印p对象, 查看是否修改
        System.out.println(p);  // Person{name='张三', age=18}    }
}

反射: 获取构造方法Constructor知识点:
如何通过反射技术, 获取一个类的构造方法, 并创建一个类的对象
总结:
java.lang.Class<T>类: 表示一个类的字节码对象, 其中包含该类中定义的内容
        // 获取构造方法们
        Constructor<?>[] getConstructors(): 获取所有 public 的构造方法
        Constructor<T> getConstructor(Class<?>... parameterTypes): 获取指定的 public 构造方法
        Constructor<?>[] getDeclaredConstructors(): 获取所有的构造方法, 不考虑权限修饰符
        Constructor<T> getDeclaredConstructor(Class... parameterTypes): 获取指定的构造方法, 不考虑权限修饰符
        T newInstance(): 使用当前类的空参构造创建一个对象
        
        
        Constructor con = c.getConstructor(String.class, int.class);
        con.newInstance("zhangsan", 18);
        new Person("zhangsan", 18);
               
java.lang.reflect.Constructor<T>: 表示一个构造方法
        // 成员方法
        T newInstance(Object... initargs): 使用当前构造方法传入参数, 创建对象
        void setAccessible(boolean flag): 注意: 构造方法不能利用此方法忽略权限, 会抛异常
补充: 5分钟练习: 使用反射获取类中构造方法, 创建对象
需求:
继续在刚才的测试类的 main()方法中编写代码:
        1. 通过Person类的Class对象
                调用Class对象的无参 newInstance()方法
                创建一个Person对象p1, 打印p1对象查看属性值
        2. 通过Person类的Class对象, 获取Person类的有参构造Constructor对象
                调用该构造方法Constructor对象的 newInstance(Object... o), 传入参数"张三", 18,
                创建一个Person对象p2, 打印p2对象查看属性值
代码:
/*
            1. 通过Person类的Class对象
            调用Class对象的无参 newInstance()方法
            创建一个Person对象p1, 打印p1对象查看属性值
         */
        Object p1 = cls.newInstance();  // 这是利用Class类中的newInstance方法创建的对象, 底层利用的是Person类的无参构造
        System.out.println(p1);        /*
            2. 通过Person类的Class对象, 获取Person类的有参构造Constructor对象
            调用该构造方法Constructor对象的 newInstance(Object... o), 传入参数"张三", 18,
            创建一个Person对象p2, 打印p2对象查看属性值
         */
        Constructor con = cls.getConstructor(String.class, int.class);
        Object p2 = con.newInstance("张三", 18);
        System.out.println(p2);
反射: 获取成员方法Method知识点:
如何通过反射技术, 获取一个类的成员方法, 并调用执行该方法
总结:
java.lang.Class<T>类: 表示一个类的字节码对象, 其中包含该类中定义的内容
        // 获取成员方法们:
        Method[] getMethods(): 获取所有 public 的成员方法
        Method getMethod(String name, Class<?>... parameterTypes) : 获取指定的 public 成员方法
        Method[] getDeclaredMethods(): 获取所有的成员方法, 不考虑权限修饰符
        Method getDeclaredMethod(String name, Class<?>... parameterTypes): 获取指定的成员方法, 不考虑权限修饰符
        
java.lang.reflect.Method类: 表示一个成员方法
        // 成员方法 Person p = new Person();   p.eat("adf", 123);  
        Object invoke(Object obj, Object... args): 使用指定对象和指定参数值调用此方法
        String getName(): 获取方法名
        void setAccessible(boolean flag): 传true时忽略访问权限修饰符的安全检查. 暴力反射
补充: 5分钟练习: 使用反射获取类中成员方法, 并调用
需求:
继续在之前定义的Person类中, 增加2个成员方法:
        // 无参方法
      
[AppleScript] 纯文本查看 复制代码
  public void eat() {
                System.out.println("无参方法: 吃");
        }
        // 有参方法
        public void eat(String food) {
                System.out.println("有参方法: 吃" + food);
        }

在测试类的 main()方法中:
        创建Person对象p
        通过Class对象获取Person类的无参方法 eat() 的Method对象, 传入p对象来调用
        通过Class对象获取Person类的有参方法 eat(String food) 的Method对象, 传入p对象和"KFC"来调用
代码:
// 通过Class对象获取Person类的无参方法 eat() 的Method对象, 传入p对象来调用
        Method noParamEatMethod = cls.getDeclaredMethod("eat");  // 无参, 就只需要传方法名
        Object result1 = noParamEatMethod.invoke(p1);  // 调用无参方法, 只需要传对象, 参数值不用传
        System.out.println("调用无参方法的返回值:" + result1); // null        // 通过Class对象获取Person类的有参方法 eat(String food) 的Method对象, 传入p对象和"KFC"来调用
        Method oneParamEatMethod = cls.getDeclaredMethod("eat", String.class); // 有参, 传递方法名和参数类型的字节码对象
        oneParamEatMethod.invoke(p1, "KFC");  // 调用有参方法, 需要传递对象和参数值
反射: 利用反射实现可以运行任意类的任意方法的框架案例
补充:java.lang.Class<T>
        // 成员方法
        ClassLoader getClassLoader(): 返回该类的类加载器
        
java.lang.ClassLoader: 类加载器 加载.class文件到内存的方法区中, 其他类型文件.properties
        // 成员方法
        InputStream getResourceAsStream(String name): 读取相对于 out/production/模块名 目录中的文件, 返回一个字节流        
使用类加载器加载配置文件
        // 随便获取一个类的字节码对象
        Class clazz = 类名.class;
        // 用字节码对象获取类加载器, 可加载bin目录中编译的文件
        ClassLoader classLoader = clazz.getClassLoader();
        // 使用类加载器加载一个文件, 返回一个字节流
        InputStream is = classLoader.getResourceAsStream("相对于src目录的相对路径");
        // 有了字节流, 就可以使用Properties的load(InputStream in)方法读取配置
        Properties p = new Properties();
        p.load(is);
        String value = p.getProperty("key");
5分钟练习: 利用反射实现一个框架知识点:
需求:
写一个"框架"程序, 框架写好后"不需改动框架的任何代码", 只需修改配置文件中的类名和方法名, 就能让框架程序帮我们创建配置文件中指定的类的对象, 并且执行其中指定的方法分析:
        实现技术:
                1. 配置文件: Properties 加载配置信息
                2. 反射: 使用配置文件中的全类名, 获取该类的字节码对象, 创建该类对象, 调用指定的方法
步骤:
        建类Person:
                定义成员方法 public void eat(){}. 方法中输出"吃东西"
        建类Student:
                定义成员方法 public void sleep(){} 方法中输出"睡觉"
        在src目录下创建一个文件: pro.properties
                填写以下键值对:
                className=Person类的全名
                methodName=eat
        建类Test, 定义main方法, 在main方法中:
                 创建Properties对象
                 获取当前Test类的字节码对象, 使用字节码对象调用getClassLoader()获取一个类加载器对象
                 使用类加载器对象调用 getResourceAsStream("pro.properties") 获取一个InputStream对象
                 使用Properties对象的load方法加载InputStream对象
                         // 获取类加载器
            ClassLoader cl = 当前类名.class.getClassLoader();
            InputStream is = cl.getResourceAsStream("pro.properties");
            // 创建Properties对象, 加载流
            Properties properties = new Properties();
            properties.load(is);
                 从Properties对象中根据键"className"和"methodName"分别获取类名字符串和方法名字符串
                 使用Class.forName("类的全名")方法传入读取到的类名, 获取该类字节码对象
                 通过字节码对象创建该类对象, 并获取指定方法名的Method对象
                 通过Method对象的invoke方法调用该方法, 查看是否调用吃饭方法
         修改配置文件中的类名为Student类的全名, 方法为sleep, 重新运行代码查看是否调用了睡觉方法
代码:
[AppleScript] 纯文本查看 复制代码
配置文件 src目录下的pro.properties
className=com.itheima.test03.Student
methodName=sleeppublic class Person {    public void eat() {
        System.out.println("人吃饭");
    }
}public class Student {    public void sleep() {
        System.out.println("学生睡觉");
    }
}/*

模拟实现框架
    目的: 加载配置文件中指定的类, 并调用指定的方法
*/
[AppleScript] 纯文本查看 复制代码
public class Test {
    public static void main(String[] args) throws Exception {
        /*
            加载配置文件:
                通过类加载器加载配置文件变成字节输入流
                然后让Properties加载流中的数据
         */
        ClassLoader classLoader = Test.class.getClassLoader();
        InputStream is = classLoader.getResourceAsStream("pro.properties");
        Properties properties = new Properties();
        properties.load(is);        /*
            获取配置信息:
                通过Properties的键, 获取值
         */
        // 通过配置文件中的类名key获取真正的类名的value
        String className = properties.getProperty("className");
        // 通过配置文件中的方法名key获取真正的方法名的value
        String methodName = properties.getProperty("methodName");        /*
            通过反射技术创建指定类的对象, 并调用方法:
         */
        //  先通过读取到的全类名, 获取字节码对象.
        Class cls = Class.forName(className);
        // 通过字节码对象创建该类的对象
        Object obj = cls.newInstance();
        // 通过字节码对象获取指定的方法对象
        Method method = cls.getDeclaredMethod(methodName);
        // 调用方法
        method.invoke(obj);
    }
}

注解
注解: 概念知识点:
什么是注解
注解可以用在哪里
注解有哪3个作用
总结:
注解: Annotation
        JDK 1.5 引入. 也叫元数据, 是一种代码级别的说明
        它可以声明在包, 类, 字段(成员变量), 方法, 局部变量, 方法参数等的前面, 用来对这些元素进行说明注解: 说明程序的。给计算机看的
注释: 用文字描述程序的。给程序员看的使用注解: @注解名称作用分类:
        1. 编写文档: 通过代码里标识的注解生成文档
                (生成API文档 @author @version @since @param @return)
        2. 代码分析: 通过代码里标识的注解对代码进行分析 (使用反射)
        (JUnit提供的 @Test @Before @After)
        3. 编译检查: 通过代码里标识的注解让编译器能够实现基本的编译检查
                (@Override @FunctionalInterface)
补充: 注解: JDK内置注解知识点:
@Override 有什么用处
@Deprecated 有什么用处
@SuppressWarnings 有什么用处
总结:
JDK中预定义的一些注解:
        @Override: 检测被该注解标注的方法是否是"重写"父类(接口)的
        @Deprecated: 该注解标注的内容,表示已过时
        @SuppressWarnings: 压制警告. 一般传递参数all  @SuppressWarnings("all")
补充: 自定义注解: 格式和本质知识点:
定义注解的格式是怎样的
注解的本质是什么
总结:
public interface 接口名 {}自定义注解格式:关键字 @interface    元注解
    public @interface 注解名称 {
        属性; (接口中的抽象方法)
            属性;
            属性;
            ...
        
    }@注解名称
   
注解的本质:
        注解本质上就是一个接口,该接口默认继承Annotation接口
        public interface MyAnno extends java.lang.annotation.Annotation {}
补充:
枚举:
        enum, enumeration. JDK 1.5 引入
        主要是用于定义一些"相关的"常量, 比如星期, 颜色, 用来进行区分枚举定义方式:
public enum 枚举名 {
    枚举常量1, 枚举常量2, 枚举常量3;
}
使用方式:
枚举名.常量名---// 示例
[AppleScript] 纯文本查看 复制代码
public enum Week {  // 星期
    SUNDAY, MONDAY, TUESDAY, WEDSDAY, THURSDAY, FRIDAY, SATURDAY;
}setWeek(Week.SUNDAY);switch(weekday) {
    case Week.SUNDAY:
        // 玩
        break;
    case Week.MONDAY:
        // 上课
        break;
}---public enum Color {  // 颜色
    RED, YELLOW, GREEN;
}
setColor(Color.RED);switch(color) {
    case Color.RED:
        // 红灯停
        break;
    case Color.GREEN:
        // 绿灯行
        break;
}
---// 类似于类中静态常量
[AppleScript] 纯文本查看 复制代码
public class Calendar {
    public static final int SUNDAY = 1;
    public static final int MONDAY = 2;
    public static final int TUESDAY = 3;
}
calendar.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY);---public enum Person {  // 一些人
    P1, P2, P3;
}

kill(Person.P2);枚举用于"区分不同的情况"
自定义注解: 属性定义知识点:
注解中的属性是什么
属性有什么特殊要求
总结:
属性:
        接口中的"抽象方法"
   
属性的要求:
    1. 属性的"返回值类型"可以是以下类型:
        基本数据类型(8种)
        String
        枚举
        注解
        以上类型的数组
    2. 定义了属性,在使用注解时, 需要"给属性赋值" (其实是抽象方法的返回值)
        1. 属性使用 default 关键字指定默认值, 则可以不赋值
        2. 如果只有一个名为"value"的属性需要赋值, 则 value 可以省略, 直接写值即可
        3. 给数组赋值时,值使用{}包裹。如果数组中只有一个值,则{}可以省略
补充:
// 定义注解
[AppleScript] 纯文本查看 复制代码
public @interface MyAnno {
    String name() default "张三";  // 有默认值
    int value();                  // 属性名为value
    String[] strs();              // 字符串数组
}// 使用注解
@MyAnno(name = "李四", value = 10, strs = {"a", "b"})
public class Worker {
   
}

自定义注解: 元注解知识点:
什么是元注解, 有什么用
@Target 元注解有什么用
@Retention 元注解有什么用
@Documented 元注解有什么用
@Inherited 元注解有什么用
总结:
元注解:
        用于描述注解的注解
        
常用元注解:
        @Target: 描述注解能够作用的位置
                ElementType枚举的常用取值:
                        TYPE:可以作用于类上
                        METHOD:可以作用于方法上
                        FIELD:可以作用于成员变量上
                示例: @Target(value = {ElementType.TYPE, ElementType.METHOD})
        @Retention: 描述注解被保留的阶段
                RetentionPolicy枚举的取值:
                        SOURCE: 保留到源代码阶段
                        CLASS: 保留到类对象阶段
                        RUNTIME: 保留到运行时阶段
                示例: @Retention(RetentionPolicy.RUNTIME):保留注解到class字节码文件中并被JVM读取到
        @Documented: 加上后, 当前注解会被抽取到api文档中
        @Inherited: 加上后, 当前注解会被子类继承
补充:
// 在自定义注解时, 可以使用以下元注解对我们自定义的注解进行约束
[AppleScript] 纯文本查看 复制代码
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnno {
    int value();
    String name() default "张三";
}@MyAnno(value = 12)
public class Test {
   
}

自定义注解: 解析注解知识点:
如何获取到注解中的属性的值        @Deprecated
      
[AppleScript] 纯文本查看 复制代码
  @ProAnno(className = "com.itheima.test04.Person", methodName = "eat")
    public class Test {
        
    }

总结:
获取注解属性值的步骤:
        1. 获取注解定义位置的对象 (Class对象(类注解), Field对象(成员变量注解), Method对象(方法注解))
        2. 调用 ProAnno a = cls.getAnnotation(ProAnno.class) 方法获取注解对象
        3. 通过注解对象调用抽象方法获取属性值
        
        // 比如获取一个类上的注解
    注解类型 注解变量名 = 被注解的类.class.getAnnotation(注解名.class);
    数据类型 变量名 = 注解变量名.抽象方法();    ProAnno proAnno = Test.class.getAnnotation(ProAnno.class);
    String className = proAnno.className();
    String methodName = proAnno.methodName();
补充: 5分钟练习: 自定义注解并解析属性值
需求:
定义Person类:
        定义成员方法: public void eat() {}, 方法内部打印"吃东西"
定义注解: ProAnno, 表示用作配置信息的注解
        添加元注解:
                @Target(ElementType.TYPE) 只能用在类上
                @Retention(RetentionPolicy.RUNTIME) 保留到运行时
        定义属性:
                String className();
                String methodName();
定义测试类Test:
        在Test类声明上添加注解: @ProAnno(className="Person类的全名", methodName="eat")
        获取Test类的Class对象, 调用 getAnnotation(注解.class) 方法获取到 ProAnno 对象
        通过 ProAnno 对象调用抽象方法, 获取类名和方法名
        利用反射技术, 通过类名创建对象, 通过方法名获取方法对象, 反射调用该方法, 查看效果
代码:
[AppleScript] 纯文本查看 复制代码
public class Person {
    public void eat() {
        System.out.println("吃东西");
    }
}@Target(ElementType.TYPE)           // 只能用在类上
@Retention(RetentionPolicy.RUNTIME) // 保留到运行时期, 一般固定写成这样
public @interface ProAnno {    String className();  // 类名属性
    String methodName(); // 方法名属性
}// 使用注解方式配置要调用的类和方法
@ProAnno(className = "com.itheima.test04.Person", methodName = "eat")
public class Test {
    public static void main(String[] args) throws Exception {
        // 获取当前类的字节码对象
        Class<Test> cls = Test.class;  // 这里写上泛型, 下面获取注解对象就可以直接获取到对应的类型        // 通过字节码对象获取使用在类上的注解
        ProAnno pro = cls.getAnnotation(ProAnno.class);        // 通过注解对象, 调用抽象方法, 获取属性值
        String className = pro.className();
        String methodName = pro.methodName();        // 剩下的步骤都和反射一样, 获取方法, 调用方法
        Class c = Class.forName(className);
        // 创建该类对象
        Object obj = c.newInstance();
        // 获取方法对象
        Method method = c.getDeclaredMethod(methodName);
        // 调用方法
        method.invoke(obj);
    }
}

注解案例: 自己实现测试框架补充:
java.lang.reflect.Method类:
     
[AppleScript] 纯文本查看 复制代码
   // 继承的方法
        boolean isAnnotationPresent(注解名.class): 判断当前方法上是否出现了某个注解java.lang.Class类:
        String getName(): 获取类的全名 com.itheima.Person
        String getSimpleName(): 获取类名不含包名 Personjava.lang.Throwable
        String getMessage(): 获取异常的详细原因
        // 成员方法
        Throwable getCause(): 获取真正导致异常的根本异常
        
        try {
                // 调用方法
                method.invoke(calculator);
    } catch (Exception e) {
            // 计数器增加
            number++;
            // 获取方法名
            method.getName();
            // 获取异常的类型
            e.getCause().getClass().getSimpleName();// 可以获取最根本异常NullPointerException
            e.getClass().getSimpleName();  // 不能获取最根本异常 InvocationTargetException
            // 获取异常的详细原因
            e.getMessage();  
    } 

5分钟练习: 自定义测试框架
自定义的测试框架如要实现方式如下:
        1. 获取Calculator的字节码对象
        2. 通过Calculator的字节码对象获取所有的Method对象
        3. 遍历出每个Method对象, 判断是否添加了 @Check 注解, 是则调用该方法
        4. 在捕获的异常中, 使用IO流将发生异常的方法名, 异常类名和原因, 写入文件
        
add 方法出异常了
异常的名称: NullPointerException
异常的原因:
-------------------
div 方法出异常了
异常的名称: XxxException
异常的原因: / by zero
-------------------
总共发生了 2 个异常
需求:
已知 Check注解 和 小明写的计算器类:/* 用于测试方法的自定义注解 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Check {
}/* 小明定义的计算器类 */
[AppleScript] 纯文本查看 复制代码
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("永无bug...");
    }
}
请定义测试类Test, 在main方法中实现测试框架:
        1. 创建Calculator类的对象, 通过Calculator对象获取该类的Class对象
        2. 通过Class对象的 getMethods() 方法, 获取包含所有public权限的Method对象的数组
        3. 定义异常计数器变量 int number = 0;
        4. 创建字节缓冲输出流对象指向bug.txt文件:
                BufferedWriter bw = new BufferedWriter(new FileWriter("bug.txt"));
        5. 遍历Method对象数组, 取出每个Method对象
        6. if判断当前Method方法上是否使用了@Check注解: if(method.isAnnotationPresent(Check.class)){}
        7. 如果有, 则调用该方法: method.invoke(计算器对象); 并使用try...catch捕获Exception
        8. 在catch中, 一旦发生异常, 则异常计数器number++, 并利用BufferedWriter将异常信息写入文件:
                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();
代码:--- 今日API
获取一个类的字节码对象的3种方式:
        1. Class.forName("全类名")
        2. 类名.class                         .
        3. 对象.getClass()java.lang.Class<T>类: 表示一个类的字节码对象, 其中包含该类中定义的内容
// 获取功能
// 1. 获取成员变量们
        Field[] getFields(): 获取所有 public 的成员变量
        Field getField(String name): 获取指定名称的 public 的成员变量
        Field[] getDeclaredFields(): 获取所有的成员变量, 不考虑权限修饰符
        Field getDeclaredField(String name): 获取指定名称的成员变量, 不考虑权限修饰符
// 2. 获取构造方法们
        Constructor<?>[] getConstructors(): 获取所有 public 的构造方法
        Constructor<T> getConstructor(Class<?>... parameterTypes): 获取指定的 public 构造方法
        Constructor<?>[] getDeclaredConstructors(): 获取所有的构造方法, 不考虑权限修饰符
        Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes): 获取指定的构造方法, 不考虑权限修饰符
// 3. 获取成员方法们:
        Method[] getMethods(): 获取所有 public 的成员方法
        Method getMethod(String name, Class<?>... parameterTypes) : 获取指定的 public 成员方法
        Method[] getDeclaredMethods(): 获取所有的成员方法, 不考虑权限修饰符
        Method getDeclaredMethod(String name, Class<?>... parameterTypes): 获取指定的成员方法, 不考虑权限修饰符
// 4. 获取Class对象代表的类的全类名
        String getName(): 获取当前Class对象代表的类的全类名
// 其他
        T newInstance(): 使用当前类的空参构造创建一个对象
        A getAnnotation(Class<A> annotationClass): 获取当前类的注解对象
        ClassLoader getClassLoader(): 返回该类的类加载器
               
java.lang.reflect.Field: 表示一个成员变量
        // 成员方法
        void set(Object obj, Object value): 设置指定对象的成员变量的值
        Object get(Object obj): 获取指定对象的成员变量的值
        void setAccessible(boolean flag): 传true时忽略访问权限修饰符的安全检查. 暴力反射
        
java.lang.reflect.Constructor<T>: 表示一个构造方法
        // 成员方法
        T newInstance(Object... initargs): 使用当前构造方法传入参数, 创建对象
        void setAccessible(boolean flag): 注意: 构造方法不能利用此方法忽略权限, 会抛异常
        
java.lang.reflect.Method类: 表示一个成员方法
        // 成员方法
        Object invoke(Object obj, Object... args): 使用指定对象和指定参数值调用此方法
        String getName(): 获取方法名
        void setAccessible(boolean flag): 传true时忽略访问权限修饰符的安全检查. 暴力反射
        
java.lang.ClassLoader: 类加载器
        // 成员方法
        InputStream getResourceAsStream(String name): 读取相对于bin目录中的文件, 返回一个字节流
        
常用元注解:
        @Target: 描述注解能够作用的位置
                ElementType枚举的常用取值:
                        TYPE:可以作用于类上
                        METHOD:可以作用于方法上
                        FIELD:可以作用于成员变量上
                示例: @Target(value = {ElementType.TYPE, ElementType.METHOD})
        @Retention: 描述注解被保留的阶段
                RetentionPolicy枚举的取值:
                        SOURCE: 保留到源代码阶段
                        CLASS: 保留到类对象阶段
                        RUNTIME: 保留到运行时阶段
                示例: @Retention(RetentionPolicy.RUNTIME):保留注解到class字节码文件中并被JVM读取到
        @Documented: 加上后, 当前注解会被抽取到api文档中
        @Inherited: 加上后, 当前注解会被子类继承
今日目标 掌握获取类的字节码对象的三种方式
1. Class.forName("全类名");
2. 类名.class
3. 对象名.getClass()
能够通过反射获取成员方法对象,并且调用方法
1. 获取类的字节码对象Class cls
2. Method m = cls.getDeclaredMethod("方法名", 参数类型.class, ...)
3. 返回值 = m.invoke(对象, 参数值,...);
能够通过反射创建类的实例对象
1. 获取类的字节码对象Class cls
2. 无参: cls.newInstance()
    有参: Constructor con = cls.getConstructor(参数类型.class, ...)
        对象 = con.newInstance(参数值, ...)
能够说出注解的作用,能描述和注释的区别
1. 生成文档 @param @return
2. 代码分析 @ProAnno @Check
3. 编译检查 @Override @FunctionalInterface
   
注释: 给程序员看
注解: 给程序看
能够使用注解并了解自定义注解的语法规范
元注解
public @interface 注解名 {
    属性(抽象方法)
    返回值类型 方法名() default 默认值;
}只有一个属性名value, 使用时省略value=   @Pro(11)
数组元素只有一个, 省略{}返回值类型:
        基本类型
        String
        枚举
        注解
        以上的数组
能够说出常用的元注解及其作用
@Target: 规定注解所使用的位置
        ElementType.TYPE类  METHOD方法  FIELD成员变量
@Retention: 规定注解所保存的时期
        RetentionPolicy.RUNTIME
@Documented: 保存到API文档中
@Inherited: 子类可以继承该注解
能够使用Junit进行单元测试
1. 导入依赖 JUnit4
2. 在被测试的方法上加 @Test 注解
        Assert.assertEquals(期望的值, 实际值)
3. 运行测试方法看颜色

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马