Junit、反射、注解

学习目标

  1. 能够使用Junit进行单元测试
  2. 能够通过反射技术获取Class字节码对象
  3. 能够通过反射技术获取构造方法对象,并创建对象。
  4. 能够通过反射获取成员方法对象,并且调用方法。
  5. 能够通过反射获取属性对象,并且能够给对象的属性赋值和取值。
  6. 能够说出注解的作用
  7. 能够自定义注解和使用注解
  8. 能够说出常用的元注解及其作用
  9. 能够解析注解并获取注解中的数据
  10. 能够完成注解的MyTest案例

第1章 Junit单元测试

1.1 Junit的概述

Junit是一个Java语言的单元测试框架,简单理解为可以用于取代java的main方法。Junit属于第三方工具,一般情
况下需要导入jar包。不过,多数Java开发环境已经集成了JUnit作为单元测试工具。

  • 在Java中,一个类就是一个单元。
  • 单元测试是开发者编写的一小段代码,用于检验某个类某个方法的功能或某个业务逻辑是否正确。

1.2 Junit的使用

1.2.1 下载

官网:http://www.junit.org

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qGYEL1GI-1582364395176)(assets/1550408381529.png)]

1.2.2 步骤

  • 编写业务类

  • 创建测试类

    编写测试方法

1.2.3 测试方法

特点

  • 方法命名规则:以test开头,使用驼峰命名法。
  • 方法声明上:必须使用注解:@Test,必须使用public修饰符,没有返回值,方法没有参数。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1jzG9Fpb-1582364395177)(assets/1550408468953.png)]

运行测试方法

选中方法名:右键 --> Run 测试方法名,则运行选中的测试方法
比如测试方法名为testSum ,则右键 --> Run testSum

选中类名:右键 --> Run 类名,则运行该类的所有测试方法
比如类名为TestCalculte,则右键 --> Run TestCalculte

选中模块名或项目名:右键 --> Run ‘All Tests’,则运行整个模块中所有类的所有测试方法。

查看测试结果

绿色:表示测试通过,如下图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qB993CKA-1582364395177)(assets/1550408531008.png)]

红色:表示失败或出现错误,如下图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZCQ9LPq6-1582364395178)(assets/1550408543457.png)]

1.2.4 常用注解

  • @Before:在每个测试方法之前都会运行一次
  • @After:在每个测试方法运行以后运行的方法
  • @BeforeClass:在所有的测试方法运行之前,运行一次,而且必须用在静态方法上面
  • @AfterClass:所有的测试方法运行以后,运行一次,必须用在静态方法上面

1.2.5 示例代码

业务类

/**
业务类
*/
public class Calculate {
    /*
    求a和b之和
    */
    public int sum(int a,int b){
        return a + b;
    }
    /**
    求a和b之差
    */
    public int sub(int a,int b){
        return a ‐ b;
    }
}

测试类

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
public class TestCalculate {
    @BeforeClass
    public static void init() {
        System.out.println("类加载时,只运行一次");
    }
    @Before
    public void myBefore(){
        System.out.println("方法前");
    }
    /*
测试方法
*/
    @Test
    public void testSum(){
        // 创建业务类对象
        Calculate c = new Calculate();
        // 调用求和方法
        int result = c.sum(1,2);
        System.out.println(result);
    }
    /*
测试方法
*/
    @Test
    public void testSub(){
        // 创建业务类对象
        Calculate c = new Calculate();
        // 调用求和方法
        int result = c.sub(1,2);
        System.out.println(result);
    }
    @After
    public void myAfter(){
        System.out.println("方法后");
    }
    @AfterClass
    public static void destory() {
        System.out.println("类结束前,只运行一次");
    }
}

第2章 反射

2.1 反射的基本概念

2.1.1 什么是反射

反射是一种机制,利用该机制可以在程序运行过程中对类进行解剖并操作类中的方法,属性,构造方法等成员。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d2XI50WQ-1582364395178)(assets/1550408721119.png)]

2.1.2 反射在实际开发中的应用

开发IDE(集成开发环境)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oLHrHxzS-1582364395179)(assets/1550408748632.png)]

以上的IDE内部都大量使用了反射机制,我们在使用这些IDE写代码也无时无刻的使用着反射机制,一个常用反射机制的地方就是当我们通过对象调用方法或访问属性时,开发工具都会以列表的形式显示出该对象所有的方法或属性,以供方便我们选择使用,如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mW8R1tcv-1582364395179)(assets/1550408762515.png)]

这些开发工具之所有能够把该对象的方法和属性展示出来就使用利用了反射机制对该对象所有类进行了解剖获取到了类中的所有方法和属性信息,这是反射在IDE中的一个使用场景。

各种框架的设计

以上三个图标上面的名字就是Java的三大框架,简称SSH.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-onYwFmI1-1582364395179)(assets/1550408800447.png)]
这三大框架的内部实现也大量使用到了反射机制,所有要想学好这些框架,则必须要求对反射机制熟练了。

2.1.3 使用反射机制解剖类的前提

必须先要获取到该类的字节码文件对象,即Class类型对象。关于Class描述字节码文件如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p2oetZcy-1582364395179)(assets/1550408832883.png)]

说明:

1)Java中使用Class类表示某个class文件.
2)任何一个class文件都是Class这个类的一个实例对象.

2.2 获取Class对象的三种方式

创建测试类:Student

public class Student {
    static {
        System.out.println("静态代码块");
    }
    {
        System.out.println("构造代码块");
    }
}

2.2.1 方式1:通过类名.class获取

public class Demo01 {
    public static void main(String[] args) {
        // 获得Student的Class的对象
        Class c = Student.class;
        // 打印输出:class com.itheima.reflect.Student
        System.out.println(c);
    }
}

2.2.2 方式2:通过Object类的成员方法getClass()方法获取

    public class Demo01 {
    public static void main(String[] args) {
    // 创建学生对象
    Student stu = new Student();
    // 获得学生类的Class对象
    Class c = stu.getClass();
    // 打印输出:class com.itheima.reflect.Student
    System.out.println(c);
    }
    }

2.2.3 方式3:通过Class.forName(“全限定类名”)方法获取

    public class Demo01 {
    public static void main(String[] args) throws Exception {
    // 获得字符串的Class对象
    Class c = Class.forName("java.lang.String");
    // 打印输出:class java.lang.String
    System.out.println(c);
    }
    }

2.3 获取Class对象的信息

知道怎么获取Class对象之后,接下来就介绍几个Class类中常用的方法了。

2.3.1 Class对象相关方法

String getSimpleName(); 获得简单类名,只是类名,没有包

String getName(); 获取完整类名,包含包名+类名	

T newInstance() ;创建此 Class 对象所表示的类的一个新实例。要求:类必须有public的无参数构造方法

2.3.2 获取简单类名

public class Demo02 {
    public static void main(String[] args) throws Exception {
        // 获得字符串的Class对象
        Class c = Class.forName("java.lang.String");
        // 获得简单类名
        String name = c.getSimpleName();
        // 打印输入:name = String
        System.out.println("name = " + name);
    }
}

2.3.3 获取完整类名

public class Demo02 {
    public static void main(String[] args) throws Exception {
        // 获得字符串的Class对象
        Class c = Class.forName("java.lang.String");
        // 获得完整类名(包含包名和类名)
        String name = c.getName();
        // 打印输入:name = java.lang.String
        System.out.println("name = " + name);
    }
}

2.3.4 创建对象

public class Demo02 {
    public static void main(String[] args) throws Exception {
        // 获得字符串的Class对象
        Class c = Class.forName("java.lang.String");
        // 创建字符串对象
        String str = (String) c.newInstance();
        // 输出str:空字符串 ""
        System.out.println(str);
    }
}

2.4 获取Class对象的Constructor信息

一开始在阐述反射概念的时候,我们说到利用反射可以在程序运行过程中对类进行解剖并操作里面的成员。而一般
常操作的成员有构造方法,成员方法,成员变量等等,那么接下来就来看看怎么利用反射来操作这些成员以及操作
这些成员能干什么,先来看看怎么操作构造方法。而要通过反射操作类的构造方法,我们需要先知道一个Constructor类。

2.4.1 Constructor类概述

Constructor是构造方法类,类中的每一个构造方法都是Constructor的对象,通过Constructor对象可以实例化对象。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7mhmkxLc-1582364395180)(assets/1550409076914.png)]

2.4.2 Class类中与Constructor相关方法

1. Constructor getConstructor(Class... parameterTypes)
根据参数类型获取构造方法对象,只能获得public修饰的构造方法。
如果不存在对应的构造方法,则会抛出 java.lang.NoSuchMethodException 异常。

2. Constructor getDeclaredConstructor(Class... parameterTypes)
根据参数类型获取构造方法对象,包括private修饰的构造方法。
如果不存在对应的构造方法,则会抛出 java.lang.NoSuchMethodException 异常。

3. Constructor[] getConstructors()
获取所有的public修饰的构造方法

4. Constructor[] getDeclaredConstructors()
获取所有构造方法,包括privat修饰的

2.4.3 Constructor类中常用方法

1. T newInstance(Object... initargs)
根据指定参数创建对象。

2. void setAccessible(true)
暴力反射,设置为可以直接访问私有类型的构造方法。

2.4.4 示例代码

学生类

public class Student {
    // 姓名
    private String name;
    // 性别
    public String gender;
    // 年龄
    private int age;
    // public 有参构造方法
    public Student(String name, String gender, int age) {
        System.out.println("public 修饰有参数构造方法");
        this.name = name;
        this.gender = gender;
        this.age = age;
    }
    // public 无参构造方法
    public Student() {
        System.out.println("public 修饰无参数构造方法");
    }
    // private 有参构造方法
    private Student(String name,String gender){
        System.out.println("private 修饰构造方法");
        this.name = name;
        this.gender = gender;
    }
    // getter & setter 方法
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getGender() {
        return gender;
    }
    public void setGender(String gender) {
        this.gender = gender;
    }
    public int getAge() {
        1. 测试类
            return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    // 普通方法
    public void sleep(){
        System.out.println("睡觉");
    }
    public void sleep(int hour){
        System.out.println("public修饰‐‐‐sleep‐‐‐睡" + hour + "小时");
    }
    private void eat(){
        System.out.println("private修饰‐‐‐eat方法‐‐‐吃饭");
    }
    // 静态方法
    public static void study(){
        System.out.println("静态方法‐‐‐study方法‐‐‐好好学习Java");
    }
    @Override
    public String toString() {
        return "Student{" +
            "name='" + name + '\'' +
            ", gender='" + gender + '\'' +
            ", age=" + age +
            '}';
    }
}

测试类

public class Demo03 {
    public static void main(String[] args)throws Exception{
        test01();
        test02();
        test03();
        test04();
    }
    /**
    4. Constructor[] getDeclaredConstructors()
    获取所有构造方法,包括privat修饰的
    */
    public static void test04() throws Exception{
        System.out.println("‐‐‐‐‐‐‐‐‐‐‐ test04() ‐‐‐‐‐‐‐‐‐‐‐");
        // 获取Student类的Class对象
        Class c = Student.class;
        // 获取所有的public修饰的构造方法
        Constructor[] cons = c.getDeclaredConstructors();
        // 遍历构造方法数组
        for(Constructor con:cons) {
            // 输出con
            System.out.println(con);
        }
    }
    /**
    3. Constructor[] getConstructors()
    获取所有的public修饰的构造方法
    */
    public static void test03() throws Exception{
        System.out.println("‐‐‐‐‐‐‐‐‐‐‐ test03() ‐‐‐‐‐‐‐‐‐‐‐");
        // 获取Student类的Class对象
        Class c = Student.class;
        // 获取所有的public修饰的构造方法
        Constructor[] cons = c.getConstructors();
        // 遍历构造方法数组
        for(Constructor con:cons) {
            // 输出con
            System.out.println(con);
        }
    }
    /**
    2. Constructor getDeclaredConstructor(Class... parameterTypes)
    根据参数类型获取构造方法对象,包括private修饰的构造方法。
    如果不存在对应的构造方法,则会抛出 java.lang.NoSuchMethodException 异常。
    */
    public static void test02() throws Exception{
        System.out.println("‐‐‐‐‐‐‐‐‐‐‐ test02() ‐‐‐‐‐‐‐‐‐‐‐");
        // 获取Student类的Class对象
        Class c = Student.class;
        // 根据参数获取对应的private修饰构造方法对象
        Constructor cons = c.getDeclaredConstructor(String.class,String.class);
        // 注意:private的构造方法不能直接调用newInstance创建对象,需要暴力反射才可以
        // 设置取消权限检查(暴力反射)
        cons.setAccessible(true);
        // 调用Constructor方法创建学生对象
        Student stu = (Student) cons.newInstance("林青霞","女");
        // 输出stu
        System.out.println(stu);
    }
    /**
    1. Constructor getConstructor(Class... parameterTypes)
    根据参数类型获取构造方法对象,只能获得public修饰的构造方法。
    如果不存在对应的构造方法,则会抛出 java.lang.NoSuchMethodException 异常。
    */
    public static void test01() throws Exception{
        System.out.println("‐‐‐‐‐‐‐‐‐‐‐ test01() ‐‐‐‐‐‐‐‐‐‐‐");
        // 获取Student类的Class对象
        Class c = Student.class;
        // 根据参数获取对应的构造方法对象
        Constructor cons = c.getConstructor(String.class,String.class,int.class);
        // 调用Constructor方法创建学生对象
        Student stu = (Student) cons.newInstance("张曼玉","女",28);
        // 输出stu
        System.out.println(stu);
    }
}

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nGBbtLu6-1582364395180)(assets/1550409478622.png)]

2.5 获取Class对象的Method信息

操作完构造方法之后,就来看看反射怎么操作成员方法了。同样的在操作成员方法之前我们需要学习一个类:
Method类。

2.5.1 Method类概述

Method是方法类,类中的每一个方法都是Method的对象,通过Method对象可以调用方法。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jfEh24Kn-1582364395180)(assets/1550410442813.png)]

2.5.2 Class类中与Method相关方法

1. Method getMethod("方法名", 方法的参数类型... 类型)
根据方法名和参数类型获得一个方法对象,只能是获取public修饰的

2. Method getDeclaredMethod("方法名", 方法的参数类型... 类型)
根据方法名和参数类型获得一个方法对象,包括private修饰的

3. Method[] getMethods()
获取所有的public修饰的成员方法,包括父类中。

4. Method[] getDeclaredMethods()
获取当前类中所有的方法,包含私有的,不包括父类中

2.5.3 Method类中常用方法

1. Object invoke(Object obj, Object... args)
根据参数args调用对象obj的该成员方法
如果obj=null,则表示该方法是静态方法

2. void setAccessible(boolean flag)
暴力反射,设置为可以直接调用私有修饰的成员方法

2.5.4 示例代码

public class Demo04 {
    public static void main(String[] args)throws Exception{
        // 获得Class对象
        Class c = Student.class;
        // 快速创建一个学生对象
        Student stu = (Student ) c.newInstance();
        // 获得public修饰的方法对象
        Method m1 = c.getMethod("sleep",int.class);
        // 调用方法m1
        m1.invoke(stu,8);
        // 获得private修饰的方法对象
        Method m2 = c.getDeclaredMethod("eat");
        // 注意:private的成员方法不能直接调用,需要暴力反射才可以
        // 设置取消权限检查(暴力反射)
        m2.setAccessible(true);
        // 调用方法m2
        m2.invoke(stu);
        // 获得静态方法对象
        Method m3 = c.getDeclaredMethod("study");
        // 调用方法m3
        // 注意:调用静态方法时,obj可以为null
        m3.invoke(null);
        System.out.println("‐‐‐‐‐‐‐‐‐‐‐‐获得所有public的方法,不包括private,包括父类的‐‐‐‐‐‐‐‐‐‐‐‐‐");
        // 获得所有public的方法,包括父类的
        Method[] ms = c.getMethods();
        // 遍历方法数组
        for(Method m : ms) {
            System.out.println(m);
        }
        System.out.println("‐‐‐‐‐‐‐‐‐‐‐获得所有方法,包括private,不包括父类‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐");
        // 获得所有方法,包括private,不包括父
        Method[] ms2 = c.getDeclaredMethods();
        // 遍历方法数组
        for(Method m : ms2) {
            System.out.println(m);
        }
    }
}

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qGNThqsM-1582364395181)(assets/1550410644139.png)]

2.6 获取Class对象的Field信息

2.6.1 Field类概述

Field是属性类,类中的每一个属性(成员变量)都是Field的对象,通过Field对象可以给对应的成员变量赋值和取值。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZXXziuRa-1582364395181)(assets/1550410667129.png)]

2.6.2 Class类中与Field相关方法

1. Field getDeclaredField(String name)
根据属性名获得属性对象,包括private修饰的

2. Field getField(String name)
根据属性名获得属性对象,只能获取public修饰的

3. Field[] getFields()
获取所有的public修饰的属性对象,返回数组。

4. Field[] getDeclaredFields()
获取所有的属性对象,包括private修饰的,返回数组。

2.6.3 Field类中常用方法

void set(Object obj, Object value)
void setInt(Object obj, int i)
void setLong(Object obj, long l)
void setBoolean(Object obj, boolean z)
void setDouble(Object obj, double d)
Object get(Object obj)
int getInt(Object obj)
long getLong(Object obj)
boolean getBoolean(Object ob)
double getDouble(Object obj)
void setAccessible(true);暴力反射,设置为可以直接访问私有类型的属性。
Class getType(); 获取属性的类型,返回Class对象。
  • setXxx方法都是给对象obj的属性设置使用,针对不同的类型选取不同的方法。
  • getXxx方法是获取对象obj对应的属性值的,针对不同的类型选取不同的方法。

2.6.4 示例代码

public class Demo05 {
    public static void main(String[] args)throws Exception{
        // 获得Class对象
        Class c = Student.class;
        // 快速创建一个学生对象
        Student stu = (Student ) c.newInstance();
        // 获得public修饰Field对象
        Field f1 = c.getField("gender");
        // 通过f1对象给对象stu的gender属性赋值
        f1.set(stu,"风清扬");
        // 通过f1对象获取对象stu的gender属性值
        String gender = (String) f1.get(stu);
        System.out.println("性别:" + gender);
        // 获得private修饰Field对象
        Field f2 = c.getDeclaredField("age");
        // 注意:private的属性不能直接访问,需要暴力反射才可以
        // 设置取消权限检查(暴力反射)
        f2.setAccessible(true);
        // 通过f1对象给对象stu的age属性赋值
        f2.setInt(stu,30);
        // 通过f2对象获取对象stu的age属性值
        int age = f2.getInt(stu);
        System.out.println("年龄:" + age);
        System.out.println("‐‐‐‐‐‐‐获得所有public修饰的属性‐‐‐‐‐‐‐‐");
        // 获得所有public修饰的属性
        Field[] fs1 = c.getFields();
        // 遍历数组
        for(Field f : fs1) {
            System.out.println(f);
        }
        System.out.println("‐‐‐‐‐‐‐获得所有的属性,包括private修饰‐‐‐‐‐‐‐‐");
        // 获得所有的属性,包括private修饰
        Field[] fs2 = c.getDeclaredFields();
        // 遍历数组
        for(Field f : fs2) {
            System.out.println(f);
        }
    }
}

输出结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pkSBV5pv-1582364395181)(assets/1550410795499.png)]

2.7 反射案例

2.7.1 案例说明

编写一个工厂方法可以根据配置文件产任意类型的对象。

  • 例如有配置文件stu.properties,存储在项目的src文件夹下,内容如下:class=com.itheima.reflect.Student name=rose gender=女 age=18
  • 根据配置文件信息创建一个学生对象。

2.7.2 实现步骤分析

  1. 在项目src文件中新建一个包:com.reflect,并在该包下创建Student类。
  2. Student类的属性:String name,String gender,int age
  3. 定义一个工厂方法:createObject(),方法返回值类型为:Object
  4. 创建Properties集合并读取stu.properties文件中的内容到集合中。
  5. 根据class获得学生类全名,并通过反射技术获得Class对象。
  6. 通过调用Class对象的方法创建学生对象。
  7. 遍历Properties集合,利用反射技术给学生成员变量赋值。
  8. 返回封装好数据的学生对象。

2.7.3 案例代码

public class Demo06 {
    public static void main(String[] args){
        // 获取对象
        Student stu = (Student) createObject();
        // 输出对象
        System.out.println(stu);
    }
    /**
* 根据配置文件创建对象
*/
    public static Object createObject(){
        try {
            // 创建属性集合
            Properties pro = new Properties();
            // 从文件中加载内容到集合中
            pro.load(Demo06.class.getResourceAsStream("/stu.properties"));
            // 从集合中获得类名
            String className = pro.getProperty("class");
            // 通过反射获得Class对象
            Class c = Class.forName(className);
            // 快速创建对象
            Object obj = c.newInstance();
            // 遍历集合
            Set<String> names = pro.stringPropertyNames();
            for (String name : names) {
                // 判断name是否class
                if (name.equals("class")) continue;
                // 获得值
                String value = pro.getProperty(name);
                // name:成员变量名
                // 根据成员变量名获得对应的Field对象
                Field f = c.getDeclaredField(name);
                // 暴力反射
                f.setAccessible(true);
                // 获得成员变量的类型
                Class typeClass = f.getType();
                if(typeClass == int.class){ // 判断成员变量的数据类型是否是int类型
                    f.setInt(obj, Integer.parseInt(value));
                } else {
                    // 给f对象的赋值
                    f.set(obj, value);
                }
            }
            // 返回对象
            return obj;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

2.7.4 案例小结

该反射案例的目的是让同学们感受反射机制的强大之处,在后面即将学习的Spring框架中就会有大量根据配置文件信息创建对象的过程,其内部的原理和我们这个案例的原理是一样,有这个案例做基础,以后学到spring框架时就会容易理解了。

第3章 注解

3.1 注解的概述

3.1.1 注解的概念

  • 注解是JDK1.5的新特性。
  • 注解相当一种标记,是类的组成部分,可以给类携带一些额外的信息。
  • 标记(注解)可以加在包,类,字段,方法,方法参数以及局部变量上。
  • 注解是给编译器或JVM看的,编译器或JVM可以根据注解来完成对应的功能。

注解(Annotation)相当于一种标记,在程序中加入注解就等于为程序打上某种标记,以后,javac编译器、开发工具和其他程序可以通过反射来了解你的类及各种元素上有无何种 标记,看你的程序有什么标记,就去干相应的事,标记可以加在包、类,属性、方法,方法的参数以及局部变量上。

3.1.2 注解的作用

注解的作用就是给程序带入参数。

以下几个常用操作中都使用到了注解:

1、生成帮助文档:@author和@version
@author:用来标识作者姓名。
@version:用于标识对象的版本号,适用范围:文件、类、方法。
使用@author和@version注解就是告诉Javadoc工具在生成帮助文档时把作者姓名和版本号也标
记在文档中。如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MDayxRo2-1582364395181)(assets/1550411083003.png)]

2、编译检查:@Override
@Override:用来修饰方法声明。
用来告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败。如下图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QdGgaY4V-1582364395181)(assets/1550411099375.png)]

3、框架的配置(框架=代码+配置)
具体使用请关注框架课程的内容的学习。

3.1.3 常见注解

  1. @author:用来标识作者名,eclipse开发工具默认的是系统用户名。
  2. @version:用于标识对象的版本号,适用范围:文件、类、方法。
  3. @Override :用来修饰方法声明,告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译
    失败。

3.2 自定义注解

3.2 自定义注解

3.2.1 定义格式

public @interface 注解名{
}
如:定义一个名为Student的注解
public @interface Student {
}

以上定义出来的注解就是一个最简单的注解了,但这样的注解意义不大,因为注解中没有任何内容,就好像我们定义一个类而这个类中没有任何成员变量和方法一样,这样的类意义也是不大的,所以在定义注解时会在里面添加一些成员来让注解功能更加强大,这些成员就是属性。接下来就看看怎么给注解添加属性。

3.2.2 注解的属性

属性的作用

可以让用户在使用注解时传递参数,让注解的功能更加强大。

属性的格式

  • 格式1:数据类型 属性名();
  • 格式2:数据类型 属性名() default 默认值;

属性定义示例

public @interface Student {
    String name(); // 姓名
    int age() default 18; // 年龄
    String gender() default "男"; // 性别
}
// 该注解就有了三个属性:name,age,gender

属性适用的数据类型

  • 八种基本数据类型(int,float,boolean,byte,double,char,long,short)
  • String类型,Class类型,枚举类型,注解类型
  • 以上所有类型的一维数组

3.3 使用自定义注解

3.3.1 定义注解

3.3.1 定义注解

定义一个注解:Book

  • 包含属性:String value() 书名
  • 包含属性:double price() 价格,默认值为 100
  • 包含属性:String[] authors() 多位作者

代码实现

public @interface Book {
    // 书名
    String value();
    // 价格
    double price() default 100;
    // 多位作者
    String[] authors();
}

3.3.2 使用注解

1.定义类在成员方法上使用Book注解

public class BookShelf {
    @Book(value = "西游记",price = 998,authors = {"吴承恩","白求恩"})
    public void showBook(){
    }
}

2.使用注意事项

  • 如果属性有默认值,则使用注解的时候,这个属性可以不用赋值。
  • 如果属性没有默认值,那么在使用注解时一定要给属性赋值。

3.3.3 特殊属性value

当注解中只有一个属性且名称是value,在使用注解时给value属性赋值可以直接给属性值,无论value是单
值元素还是数组类型。

// 定义注解Book
public @interface Book {
    // 书名
    String value();
}
// 使用注解Book
public class BookShelf {
    @Book("西游记")
    public void showBook(){
    }
}public class BookShelf {
	@Book(value="西游记")
    public void showBook(){
    }
}

如果注解中除了value属性还有其他属性,且至少有一个属性没有默认值,则在使用注解给属性赋值时,
value属性名不能省略。

// 定义注解Book
public @interface Book {
    // 书名
    String value();
    // 价格
    double price() default 100;
    // 多位作者
    String[] authors();
}
// 使用Book注解:正确方式
@Book(value="红楼梦",authors = "曹雪芹")
public class BookShelf {
    // 使用Book注解:正确方式
    @Book(value="西游记",authors = {"吴承恩","白求恩"})
    public void showBook(){
    }
}
// 使用Book注解:错误方式
public class BookShelf {
    @Book("西游记",authors = {"吴承恩","白求恩"})
    public void showBook(){
    }
}
// 此时value属性名不能省略了。  

3.3.4 问题分析

现在我们已经学会了如何定义注解以及如何使用注解了,可能细心的同学会发现一个问题:我们定义的注解是可以使用在任何成员上的,比如刚刚Book注解的使用:

// 定义注解Book
public @interface Book {
    // 书名
    String value();
    // 价格
    double price() default 100;
    // 多位作者
    String[] authors();
}
// 使用Book注解:正确方式
@Book(value="红楼梦",authors = "曹雪芹")
public class BookShelf {
    // 使用Book注解:正确方式
    @Book(value="西游记",authors = {"吴承恩","白求恩"})
    public void showBook(){
    }
}
  • 此时Book同时使用在了类定义上或成员方法上,编译器也没有报错,因为默认情况下,注解可以用在任何地方,比如类,成员方法,构造方法,成员变量等地方。
  • 如果要限制注解的使用位置怎么办?那就要学习一个新的知识点:元注解。接下来就来看看什么是元注解以,及怎么使用。

3.4 注解之元注解

3.4 注解之元注解

3.4.1 元注解的概述

  • Java API提供的注解
  • 专门用来定义注解的注解。
  • 任何Java官方提供的非元注解的定义中都使用到了元注解。

3.4.2 常用元注解

  • @Target
  • @Retention

3.4.2.1 元注解之@Target

3.4.2.1 元注解之@Target

作用:指明此注解用在哪个位置,如果不写默认是任何地方都可以使用。

可选的参数值在枚举类ElemenetType中包括:

TYPE: 用在类,接口上
FIELD:用在成员变量上
METHOD: 用在方法上
PARAMETER:用在参数上
CONSTRUCTOR:用在构造方法上
LOCAL_VARIABLE:用在局部变量上
3.4.2.2 元注解之@Retention

作用:定义该注解的生命周期(有效范围)。

可选的参数值在枚举类型RetentionPolicy中包括

SOURCE:注解只存在于Java源代码中,编译生成的字节码文件中就不存在了。
CLASS:注解存在于Java源代码、编译以后的字节码文件中,运行的时候内存中没有,默认值。
RUNTIME:注解存在于Java源代码中、编译以后的字节码文件中、运行时内存中,程序可以通过反射获取该
注解。

3.4.3 元注解使用示例

@Target({ElementType.METHOD,ElementType.TYPE})
@interface Stu{
    String name();
}
// 类
@Stu(name="jack")
public class AnnotationDemo02 {
    // 成员变量
    @Stu(name = "lily") // 编译失败
    private String gender;
    // 成员方法
    @Stu(name="rose")
    public void test(){
    }
    // 构造方法
    @Stu(name="lucy") // 编译失败
    public AnnotationDemo02(){}
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d901MB1l-1582364395182)(assets/1550411963850.png)]

3.5 注解解析

3.4.1 什么是注解解析

通过Java技术获取注解数据的过程则称为注解解析。

3.4.2 与注解解析相关的接口

  • Anontation:所有注解类型的公共接口,类似所有类的父类是Object。
  • AnnotatedElement:定义了与注解解析相关的方法,常用方法以下四个:
boolean isAnnotationPresent(Class annotationClass); 判断当前对象是否有指定的注解,有则返回
true,否则返回false。
T getAnnotation(Class<T> annotationClass); 获得当前对象上指定的注解对象。
Annotation[] getAnnotations(); 获得当前对象及其从父类上继承的所有的注解对象。
Annotation[] getDeclaredAnnotations();获得当前对象上所有的注解对象,不包括父类的。

3.4.3 获取注解数据的原理

注解作用在那个成员上,就通过反射获得该成员的对象来得到它的注解。

如注解作用在方法上,就通过方法(Method)对象得到它的注解

// 得到方法对象
Method method = clazz.getDeclaredMethod("方法名");
// 根据注解名得到方法上的注解对象
Book book = method.getAnnotation(Book.class);

如注解作用在类上,就通过Class对象得到它的注解

// 获得Class对象
Class c = 类名.class;
// 根据注解的Class获得使用在类上的注解对象
Book book = c.getAnnotation(Book.class);

3.4.4 使用反射获取注解的数据

3.4.4.1 需求说明

定义注解Book,要求如下:

  • 包含属性:String value() 书名

  • 包含属性:double price() 价格,默认值为 100

  • 包含属性:String[] authors() 多位作者

  • 限制注解使用的位置:类和成员方法上

  • 指定注解的有效范围:RUNTIME

定义BookStore类,在类和成员方法上使用Book注解

定义TestAnnotation测试类获取Book注解上的数据

3.4.4.2 代码实现

注解Book

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Book {
    // 书名
    String value();
    // 价格
    double price() default 100;
    // 作者
    String[] authors();
}

BookStore类

@Book(value = "红楼梦",authors = "曹雪芹",,price = 998)
public class BookStore {
    @Book(value = "西游记",authors = "吴承恩")
    public void buyBook(){
    }
}

TestAnnotation类

public class TestAnnotation {
    public static void main(String[] args) throws Exception{
        System.out.println("‐‐‐‐‐‐‐‐‐获取类上注解的数据‐‐‐‐‐‐‐‐‐‐");
        test01();
        System.out.println("‐‐‐‐‐‐‐‐‐获取成员方法上注解的数据‐‐‐‐‐‐‐‐‐‐");
        test02();
    }
    /**
    * 获取BookStore类上使用的Book注解数据
    */
    public static void test01(){
        // 获得BookStore类对应的Class对象
        Class c = BookStore.class;
        // 根据注解Class对象获取注解对象
        Book book = (Book) c.getAnnotation(Book.class);
        // 输出book注解属性值
        System.out.println("书名:" + book.value());
        System.out.println("价格:" + book.price());
        System.out.println("作者:" + Arrays.toString(book.authors()));
    }

    /**
    * 获取BookStore类成员方法buyBook使用的Book注解数据
    */
    public static void test02() throws Exception{
        // 获得BookStore类对应的Class对象
        Class c = BookStore.class;
        // 获得成员方法buyBook对应的Method对象
        Method m = c.getMethod("buyBook");
        // 根据注解Class对象获取注解对象
        Book book = (Book) m.getAnnotation(Book.class);
        // 输出book注解属性值
        System.out.println("书名:" + book.value());
        System.out.println("价格:" + book.price());
        System.out.println("作者:" + Arrays.toString(book.authors()));
    }
}

输入结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3U6UNHEL-1582364395182)(assets/1550412323881.png)]

3.4.4.3 存在问题分析

TestAnnotation类在获取注解数据时处理得不够严谨,假如出现下面的其中一种情况:

  1. 把BookStore类或成员方法buyBook上的注解删除。
  2. 将Book注解的有效范围改为:CLASS。

再运行TestAnnotation类代码则会出现空指针异常,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5JyDJo3Z-1582364395182)(assets/1550412384969.png)]

原因分析如下图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0os7RMxG-1582364395182)(assets/1550412396987.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-onhpWZ8m-1582364395182)(assets/1550412419022.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X0wH7kCk-1582364395183)(assets/1550412428426.png)]

解决方案

在获取注解对象时,先判断是否有使用注解,如果有,才获取,否则就不用获取。

修改TestAnnotation类的代码,修改后如下

public class TestAnnotation {
    public static void main(String[] args) throws Exception{
        System.out.println("‐‐‐‐‐‐‐‐‐获取类上注解的数据‐‐‐‐‐‐‐‐‐‐");
        test01();
        System.out.println("‐‐‐‐‐‐‐‐‐获取成员方法上注解的数据‐‐‐‐‐‐‐‐‐‐");
        test02();
    }
    /**
* 获取BookStore类上使用的Book注解数据
*/
    public static void test01(){
        // 获得BookStore类对应的Class对象
        Class c = BookStore.class;
        // 判断BookStore类是否使用了Book注解
        if(c.isAnnotationPresent(Book.class)) {
            // 根据注解Class对象获取注解对象
            Book book = (Book) c.getAnnotation(Book.class);
            // 输出book注解属性值
            System.out.println("书名:" + book.value());
            System.out.println("价格:" + book.price());
            System.out.println("作者:" + Arrays.toString(book.authors()));
        }
    }
    /**
* 获取BookStore类成员方法buyBook使用的Book注解数据
*/
    public static void test02() throws Exception{
        // 获得BookStore类对应的Class对象
        Class c = BookStore.class;
        // 获得成员方法buyBook对应的Method对象
        Method m = c.getMethod("buyBook");
        // 判断成员方法buyBook上是否使用了Book注解
        if(m.isAnnotationPresent(Book.class)) {
            // 根据注解Class对象获取注解对象
            Book book = (Book) m.getAnnotation(Book.class);
            // 输出book注解属性值
            System.out.println("书名:" + book.value());
            System.out.println("价格:" + book.price());
            System.out.println("作者:" + Arrays.toString(book.authors()));
        }
    }
}

3.6 注解案例

3.5.1 案例说明

模拟Junit测试的@Test

3.5.2 案例分析

  1. 模拟Junit测试的注释@Test,首先需要编写自定义注解@MyTest,并添加元注解,保证自定义注解只能修饰
    方法,且在运行时可以获得。
  2. 然后编写目标类(测试类),然后给目标方法(测试方法)使用 @MyTest注解,编写三个方法,其中两个加
    上@MyTest注解。
  3. 最后编写调用类,使用main方法调用目标类,模拟Junit的运行,只要有@MyTest注释的方法都会运行。

3.5.3 案例代码

注解MyTest

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

目标类MyTestDemo

public class MyTestDemo {
    @MyTest
    public void test01(){
        System.out.println("test01");
    }
    public void test02(){
        System.out.println("test02");
    }
    @MyTest
    public void test03(){
        System.out.println("test03");
    }
}

调用类TestMyTest

public class TestMyTest {
    public static void main(String[] args) throws Exception{
        // 获得MyTestDemo类Class对象
        Class c = MyTestDemo.class;
        // 获得所有的成员方法对象
        Method[] methods = c.getMethods();
        // 创建MyTestDemo类对象
        Object obj = c.newInstance();
        // 遍历数组
        for (Method m:methods) {
            // 判断方法m上是否使用注解MyTest
            if(m.isAnnotationPresent(MyTest.class)){
                // 执行方法m
                m.invoke(obj);
            }
        }
    }
}

输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fyTVPmtg-1582364395183)(assets/1550412609163.png)]

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值