Skip to content

day14【反射和注解】

今日内容介绍

java
反射
类加载器
注解
java
能够通过反射技术获取Class字节码对象
能够通过反射技术获取构造方法对象,并创建对象
能够通过反射获取成员方法对象,并且调用方法
能够知道类加载器的作用
能够自定义注解X能够使用自定义注解

第一章 类加载器

1.1 类的加载时机

名词解释: 就是类什么时候进到内存了!!!你觉得 什么代码的运行就代表着类已经进入内存了!

java
类的加载时机
  遵循 用的时候加载,不用不加载。
  归纳总结为以下6点
  1:创建类的对象。
  2: 调用静态方法。
  3: 访问静态变量。
  4: 初始化某个子类。
  5: java 命令 运行某个类的时候。
  6: 使用反射形式 成功 java.lang.Class的数据(字节码文件对象)
java
package com.itheima.classloader;

public class Person {
    //静态变量
    public static int age=100;
    //静态代码块  --如果执行类该类的静态代码块就证明了 该类一定被加载了
    static{
        System.out.println("Person的静态代码块执行了....");
    }
    package com.itheima.classloader;

public class Student extends Person{

    static {
        System.out.println("这是 Student的静态代码块...");
    }
}

    package com.itheima.classloader;

public class Demo01 {

    public static void main(String[] args) throws ClassNotFoundException {
        //类的加载时机
        //  遵循 用的时候加载,不用不加载。
        //  归纳总结为以下6点
        //  1:创建类的对象。
//       new Person();
        //  2: 调用静态方法。
//        Person.method();
        //  3: 访问静态变量。
//        int age = Person.age;
        //  4: 初始化某个子类。
//        new Student();
        //  5: java 命令 运行某个类的时候。
        //  6: 使用反射形式 成功 java.lang.Class的数据(字节码文件对象)
        // 反射形式  获取字节码文件对象
        Class clazz = Class.forName("com.itheima.classloader.Person");
    }
}

    //静态方法
    public static void method(){
        System.out.println("这里是Person类的静态方法");
    }
}

类加载的过程

1628820970859

加载 链接 初始化

链接包含 验证 准备 解析

java
一个类的加载过程
   1:类被使用到的时候,类文件加载到内存中,形成一个Class类型对象。
   2:检验语法性,给静态成员开辟空间,解析有没有用到其他的类型,需要检测其他类型有没有在内存。(没在就加载)
   3:完成了静态成员的初始化。(静态变量的赋值  完成静态代码块的执行  以及把静态方法放到内存上) .

2.2 类的加载器

java
类加载器:
    作用:
       负责把.class文件加载到内存中的方法区。
       把class文件转换成了Class类型对象。(字节码对象)
    研究一下 为什么 类能被加载 而且能保证加载一次!!双亲委派机制保证的。
类加载器组成:(不同的类加载器 是负责加载不同路径下的类的!!)
     引导类加载器:BootstrapClassLoader
           负责java核心类的加载 (System String  Long Math...)
           用c++写的。
     扩展类加载器:ExtClassLoader
           负责的是 jre扩展目录下的jar包的加载
              也就是 jre lib 有个ext的目录
     应用类加载器:AppClassLoader
          负责的是 我们自己定义的类和第三方jar包中的类。
     自定义类加载器:
           加载你自己指定的一些类。

      Person类 要被加载
      Person类 要被加载
      先去找应用类加载器询问应用类加载器--询问扩展类加载器有没有加载过--询问引导类加载器
                       引导类想加载--看一下路径是不是符合加载的路径 不是
                       扩展类加载器--看一下路径是不是符合  不是
                       引用类加载器--符合  执行加载
       Person类有 String属性呢
          询问应用类加载器--询问扩展类加载器有没有加载过--询问引导类加载器
                       引导类想加载--看一下路径是不是符合加载的路径 符合 它来加载 然后就结束了

      Student
          String
             询问应用类加载器--询问扩展类加载器有没有加载过--询问引导类加载器 加载过 不加载

      双亲委派机制的目的:保证类可以被加载且只能加载一次。

1628820970859

第三章 反射

3.1 Class 类的介绍

java
我们的字节码文件Xxx.class进入到内存中,就变成了一个 字节码文件对象,该对象的类型就是Class.
    Class类型就是用来表示 字节码文件的一种类型,每个字节码文件都只有唯一的对应的 字节码文件对象。
哪这个字节码文件对象中有什么呢?
  .java文件中写了什么字节码文件对象中就有什么!!
  .java文件有什么?
     成员变量
     构造方法
     成员方法
  字节码文件对象中也是有
    成员变量
    构造方法---构造器
    成员方法
    只不过 它们的样子不是以前的样子了!!
    以前  成员变量  private int age;---自己的格式样子
         构造方法  public 类名(){}
         成员方法  public void show(){}
    在字节码文件中变样了,变成什么样子了呢?java面向对象语言。也就是你的成员变量,成员方法,构造方法都变成了对象形式了!!也就是 成员变量有自己的类型了!!!成员方法自己的类型了!!!构造方法也有自己的类型!!

1642216704767

3.2 获取字节码文件对象的方式

获取 class 对象方式作用应用场景
Class.forName("全类名")通过指定的字符串路径获取多用于配置文件,将类名定义在配置文件中。读取文件,加载类
类名.class通过类名的属性 class 获取多用于参数的传递
对象.getClass()通过对象的 getClass()方法获取多用于对象的获取字节码的方式

User

java
package com.itheima.classobject;

import java.util.Objects;

public class User {
    private String name;
    private int age;

    public User() {
    }


    private User(String name) {
        this.name = name;
    }

    public User(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 "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
java
package com.itheima.refect01;

public class GetClassDemo01 {

    public static void main(String[] args) throws ClassNotFoundException {
        //想玩反射得先获取 字节码对象
        // 怎么获取有三种方式
        //1:通过 Class类型的一个静态方法 Class forName("包名+类名")
             // 这个方法是 根据包名+类名 找到类文件加载到 内存中 并返回该类型的字节码对象
//        Class clazz = Class.forName("com.itheima.refect01.User");
//
//        System.out.println(clazz);
        //2: 通过类名.class    就是类的字节码
          //  根据包名+类名 找到类文件 加载内存中 并返回该类型的字节码对象
//        Class clazz = User.class;
//        System.out.println(clazz);

        // 3: 通过对象的.getClass()方法   当对象产生  字节码对象一定有了
           // 对象.getClass() 获取对象运行时的类---在内存中就是字节码对象
        User u = new User();
        Class clazz= u.getClass();
        System.out.println(clazz);
    }
}
java
获取字节码文件对象三种形式
    1:Class.forName("包名+类名")  用的最多 因为灵活
    2: 类名.class    简单
    3: 对象.getClass()

3.3 反射获取构造方法

Class 类中与 Constructor 相关方法

1628826641553

获取构造操作

java
package com.itheima.classobject;

import java.lang.reflect.Constructor;

public class Demo02 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        //先获取字节码文件对象
        Class clazz = Class.forName("com.itheima.classobject.User");

        // 1:通过字节码文件对象 获取该类下 所有的公共的构造方法
        // 注意 现在的构造方法不再是一个格式了 而是对象形式。
        Constructor[] constructors = clazz.getConstructors();
        //这个数组有什么 ? 有该类的 所有公共构造
//        for (Constructor constructor : constructors) {
//            System.out.println(constructor);
//        }
        // 如果我们想要所有的构造,不管你是公共的还是私有的 忽略权限修饰符去寻找构造
        Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
//        for (Constructor declaredConstructor : declaredConstructors) {
//            System.out.println(declaredConstructor);
//        }

        //能不能获取指定的构造方法?
        //可以   构造方法的名称就是类名,是不用写的,但是 构造是不是有多个 以参数区分
        // 只能获取公共的指定的构造 获取的时候要传递 参数的类型!!
        Constructor constructor = clazz.getConstructor(String.class, int.class);
        System.out.println(constructor);
        //可以找到私有的构造  同样也能找公共的
        Constructor declaredConstructor = clazz.getDeclaredConstructor(String.class);
        System.out.println(declaredConstructor);
    }
}

Constructor 中构建对象的方法

1628826665985

反射创建对象---公共的无参的

java
package com.itheima.refect01;

import java.lang.reflect.Constructor;

public class ContructDemo02 {
   //获取到 User类的 空参构造对象形式,然后 构建一个对象出来。
    public static void main(String[] args) throws Exception {
        // 1:获取字节码对象
        Class clazz = Class.forName("com.itheima.refect01.User");

        //2:获取User的空参构造 是公共的
        Constructor con = clazz.getConstructor();
        System.out.println(con);
        //3: 有了 空参构造   Constructor con
        // 根据构造 产生一个 User的实例
        /*
          这个方法就相当于 new 类名()
         Object newInstance(Object... initargs)
          使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,
          并用指定的初始化参数初始化该实例。

         */
        Object o = con.newInstance();
        // o 就是 一个 com.itheima.refect01.User 类型对应的一个对象
        System.out.println(o);

    }
}

反射创建对象---公共的有参的

java
	package com.itheima.refect01;

import java.lang.reflect.Constructor;

public class ContructDemo03 {
   //获取到 User类的 空参构造对象形式,然后 构建一个对象出来。
    public static void main(String[] args) throws Exception {
        // 1:获取字节码对象
        Class clazz = Class.forName("com.itheima.refect01.User");

        //2:获取User的构造 是公共的
        Constructor con = clazz.getConstructor(String.class,int.class);
                 //字节码 去 "找" 不是执行 指定的构造方法
                 // 怎么找 这个参数是来帮我们找构造的
                //我要找一个公共的构造  第一参数类型是String 第二个参数类型是int
        System.out.println(con);
        //3: 有了  带参构造  Constructor con  第一个参数String类型 第二个参数是int
        // 根据构造 产生一个 User的实例
        /*
          这个方法就相当于
         Object newInstance(Object... initargs)
          使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,
          并用指定的初始化参数初始化该实例。
         */
        Object o = con.newInstance("张三",18);//new User("张三",18)
        //  通过反射的形式 调用指定构造  构建对象
        System.out.println(o);


    }
}

快捷的创建一个无参的对象

java
package com.itheima.refect01;

import java.lang.reflect.Constructor;
/*
   Class中的方法
      Object  newInstance();
*/
public class ContructDemo04 {
   //获取到 User类的 空参构造对象形式,然后 构建一个对象出来。
    public static void main(String[] args) throws Exception {
        // 1:获取字节码对象
        Class clazz = Class.forName("com.itheima.refect01.User");

//        //2:获取User的空参构造 是公共的
//        Constructor con = clazz.getConstructor();
//        System.out.println(con);
//        //3: 有了 空参构造   Constructor con
//        Object o = con.newInstance();
//        System.out.println(o);
        //上面两步合成一步  只针对空参构造 获取对象
        // 先要找到空参构造   根据空参构造newInstance()
        Object o = clazz.newInstance();
        System.out.println(o);

    }
}

反射私有构造创建对象

java
package com.itheima.refect01;

import java.lang.reflect.Constructor;
// 暴力反射: 获取到私有的内容 (放弃了权限的检查)可以直接完成调用。
public class ContructDemo05 {
   //获取到 User类的 空参构造对象形式,然后 构建一个对象出来。
    public static void main(String[] args) throws Exception {
        // 1:获取字节码对象
        Class clazz = Class.forName("com.itheima.refect01.User");

       //2: 获取一个私有的构造 比如获取 参数类型是 String 哪个构造
        Constructor con = clazz.getDeclaredConstructor(String.class);
        /*
           con代表 哪个 带有一个参数的构造
                private User(String name) {
                    this.name = name;
                }
                当我们不做任何操作
                  私有的构造能找到  但是就不能执行
                  因为 该构造被 private修饰了
                  想不想 在反射状态下调用私有的东西
                  只要让 private  失效就可以
                    贿赂一下。放弃 权限修饰符的检查
                      在他们的父类中
                      public void setAccessible(boolean flag)
值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。
值为 false 则指示反射的对象应该实施 Java 语言访问检查。
         */
        // 取消java的权限语法检查
        con.setAccessible(true);

      //3: 创建对象  反射形式 构建对象
        Object o = con.newInstance("jack");

        System.out.println(o);

    }
}

总结

1628826852356

3.4 反射成员方法

java
package com.itheima.method;

/**
 * 像这样的类 一般称为 javabean类 -- 毛豆
 *       1:具有属性--一般私有的  就是说明不允许外界直接访问
 *       2:提供 set get方法
 *       3: 必须要有空参构造,可以拥有满参构造。
 */
public class User {
    private String name;
    private int age;

    public User() {
    }

    public User(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;
    }

    // 如果不重写toString 出来的地址值 如果重写就会出现我们设置的格式展示

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    public void eat(){
        System.out.println("用户吃土");
    }

    private void sleep(String address){
        System.out.println("用户在"+address+"睡觉");
    }

    public String show(){
        return "姓名"+name;
    }

    public int sum(int a,int b){
        return a+b;
    }
}

1628844009554

1628844018451

获取多个方法的测试

java
package com.itheima.reflect02;

import java.lang.reflect.Method;


public class MethodReflect01 {
   /*
   Method是方法的对象形式

      Method[]  getMethods()
        获取所有public的方法,包括父类的。
       Method[]  getDeclaredMethods()
        获取所有的方法,不包括父类定义了 只包含本类中。

       Method getMethod(String methodName,T...argsType);
           获取指定的方法
            怎么指定呢?
                必须要传名字
            因为方法存在重载,
                必须传递该方法的参数列表类型
    */
    public static void main(String[] args) throws Exception{

        // 方法对象怎么获取呢?
        //1:获取字节码对象
        Class clazz = Class.forName("com.itheima.reflect02.User");

        //2.1 获取所有公共的 方法 包含父类
        Method[] methods = clazz.getMethods();
        System.out.println(methods.length);
        for (Method method : methods) {
            System.out.println(method);
        }
        System.out.println("======================");
         // 获取自己定义的所有方法 不包含父类定义的
        Method[] methods1 = clazz.getDeclaredMethods();
        System.out.println(methods1.length);
        for (Method method : methods1) {
            System.out.println(method);
        }
        System.out.println("=================");


    }
}

获取指定方法

java
package com.itheima.reflect02;

import java.lang.reflect.Method;


public class MethodReflect01 {
   /*
   Method是方法的对象形式

      Method[]  getMethods()
        获取所有public的方法,包括父类的。
       Method[]  getDeclaredMethods()
        获取所有的方法,不包括父类定义了 只包含本类中。

       Method getMethod(String methodName,T...argsType);
           获取指定的方法
            怎么指定呢?
                必须要传名字
            因为方法存在重载,
                必须传递该方法的参数列表类型
    */
    public static void main(String[] args) throws Exception{

        // 方法对象怎么获取呢?
        //1:获取字节码对象
        Class clazz = Class.forName("com.itheima.reflect02.User");

        System.out.println("=================");
       //如果获取的方法是 public void play() 空参的 无返回值的方法
        Method play = clazz.getMethod("play");
        System.out.println("找到的play方法:"+play);

       // 如果方法是  public void com.itheima.reflect02.User.sleep(java.lang.String)
        Method sleep = clazz.getMethod("sleep", String.class);
        System.out.println("找到的sleep方法:"+sleep);

        //如果方法是 public int com.itheima.reflect02.User.sum(int,int)
        Method sum = clazz.getMethod("sum", int.class, int.class);
        System.out.println("找到sum方法:"+sum);

        //如果方法 private void com.itheima.reflect02.User.show()
        Method show = clazz.getDeclaredMethod("show");
        System.out.println("找到show方法:"+show);

    }
}

1628844116527

反射获取成员方法并运行

java
package com.itheima.reflect02;

import java.lang.reflect.Method;


public class MethodReflect02 {
   /*
    现在已经找到 方法了 怎么执行呢?

    */
    public static void main(String[] args) throws Exception{

        //以前怎么执行方法
//        User user = new User();
//        user.play();
//        user.sleep("张三");
//        int sum1 = user.sum(2, 3);

        // 方法对象怎么获取呢?
        //1:获取字节码对象
        Class clazz = Class.forName("com.itheima.reflect02.User");

        //2:快捷的生成一个空参对象
        Object o = clazz.newInstance();//多态形式

        //如果获取的方法是 public void play() 空参的 无返回值的方法
        Method play = clazz.getMethod("play");
        System.out.println("找到的play方法:"+play);
        //反射形式下 怎么 执行方法呢?
       /*
        Object invoke(Object obj, Object... args)
            就是使用 方法对象形式  来执行方法
                play.invoke()
                   第一个参数 就是对象 方法所属对象。就是咱们以前 user
                   第二个为什么是可变参数  代表的是 方法的参数值
                       user.sleep("张三")
                       user.sum(1,3)
        */
       play.invoke(o);

       // 如果方法是  public void com.itheima.reflect02.User.sleep(java.lang.String)
        Method sleep = clazz.getMethod("sleep", String.class);
        System.out.println("找到的sleep方法:"+sleep);
        // 方法.invoke(对象,参数)
        Object returnOb = sleep.invoke(o, "张小华");
        System.out.println("sleep没有返回值:"+returnOb);

        //如果方法是 public int com.itheima.reflect02.User.sum(int,int)
        Method sum = clazz.getMethod("sum", int.class, int.class);
        System.out.println("找到sum方法:"+sum);

        Object retuenValue = sum.invoke(o, 2, 3);
        //  方法反射执行之后 方法的返回值 会以返回值的形式的出现 只不过用Object
        System.out.println("计算的结果:"+retuenValue);

        //如果方法 private void com.itheima.reflect02.User.show()
        Method show = clazz.getDeclaredMethod("show");
        System.out.println("找到show方法:"+show);
        //取消权限检查
        show.setAccessible(true);
        //怎么执行私有方法
        show.invoke(o);

    }
}

3.5 反射案例

1642236554793

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

实现:

  1. 配置文件 usemethod.properties

    properties
    className=com.itheima.pojo.Teacher
    methodName=teach

    我们可以在 src 下创建该配置文件

    1642236727374

  2. 反射

    在你的代码中完成文件的读取,根据 className 的值 构建一个对象,根据 methodName 再调用指定方法。

java
  需求:
           读取 usemethod.properties
              解析 键 className
                   键 methodName
              className是咱们某个类的 权限定类名(包名+类名)
              methodName 是方法名

            然后根据 权限定类名 构建该类型对象
                根据方法名 实现 反射调用

步骤:

  1. 将需要创建的对象的全类名和需要执行的方法定义在配置文件中

  2. 在程序中加载读取配置文件

  3. 使用反射技术来加载类文件进内存

  4. 创建对象

  5. 执行方法

    java
    package com.itheima.usemethod;
    
    import java.io.FileNotFoundException;
    import java.io.FileReader;
    import java.lang.reflect.Method;
    import java.util.Properties;
    
    /**
     * 在模拟框架
     *     我们把框架代码写好,之后配置文件中写什么,
     *     框架就能帮我调什么?
     *     意思就是 不改变 java代码的前提下,我们稍微修改配置文件
     *     就可以实现不同方法的调用!
     */
    public class Demo {
    
        public static void main(String[] args) throws Exception {
           // 1:解析配置文件 day15\\src\\usemethod.properties
            // 这种文件 怎么解析 使用 Properties集合
            Properties pp = new Properties();
            //2 里面有load(输入流)  完成数据读取到集合中
            FileReader fr = new FileReader("day15\\src\\usemethod.properties");
    
            pp.load(fr);
    
            //3 根据键找到指定的值
            String className = pp.getProperty("className");
            String methodName = pp.getProperty("methodName");
    
            //4 根据className找到类 获取字节码对象
            Class clazz = Class.forName(className);
            //5:找到指定的方法
            Method method = clazz.getMethod(methodName);
    
            //6:反射执行之前 先获取 一个 实例
            Object obj = clazz.newInstance();
            //7:反射执行方法
            method.invoke(obj);
    
        }
    }

    读取优化

    java
    package com.itheima.usemethod;
    
    import java.io.FileReader;
    import java.lang.reflect.Method;
    import java.util.Properties;
    import java.util.ResourceBundle;
    
    /**
     * 在模拟框架
     *     我们把框架代码写好,之后配置文件中写什么,
     *     框架就能帮我调什么?
     *     意思就是 不改变 java代码的前提下,我们稍微修改配置文件
     *     就可以实现不同方法的调用!
     */
    public class Demo2 {
    
        public static void main(String[] args) throws Exception {
           // 1: 我们要求配置文件必须在src下 或者maven程序的resources下
           //  这个类 是 来自 java.util.ResourceBundle
            // 怎么用  特别简单  你保证你的配置文件是.properties结尾,并且在src
            ResourceBundle bundle = ResourceBundle.getBundle("usemethod");
    
            //3 根据键找到指定的值
            String className = bundle.getString("className");
            String methodName = bundle.getString("methodName");
    
            //4 根据className找到类 获取字节码对象
            Class clazz = Class.forName(className);
            //5:找到指定的方法
            Method method = clazz.getMethod(methodName);
    
            //6:反射执行之前 先获取 一个 实例
            Object obj = clazz.newInstance();
            //7:反射执行方法
            method.invoke(obj);
    
        }
    }

第四章 注解

4.1 注解概述

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

作用分类

  • 编写文档:通过代码里标识的注解生成文档【例如,生成文档 doc 文档】
  • 代码分析:通过代码里标识的注解对代码进行分析【例如,注解的反射】
  • 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【例如,Override】

常见注解

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

4.2 自定义注解

定义格式

java
元注解
public @interface 注解名称{
	属性列表;
}

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

java
public @interface MyAnno extends java.lang.annotation.Annotation {}

任何一个注解,都默认的继承 Annotation 接口。

注解的属性

  1. 属性的作用

    • 可以让用户在使用注解时传递参数,让注解的功能更加强大。
  2. 属性的格式

    • 格式 1:数据类型 属性名();
    • 格式 2:数据类型 属性名() default 默认值;
  3. 属性定义示例

    java
    public @interface Student {
      String name(); // 姓名
      int age() default 18; // 年龄
      String gender() default "男"; // 性别
    }
    // 该注解就有了三个属性:name,age,gender
  4. 属性适用的数据类型

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

4.3 使用自定义注解

使用格式:

​ @注解名(属性名=属性值,属性名=属性值,属性名=属性值...)

定义注解

  1. 定义一个注解:Book
    • 包含属性:String value() 书名
    • 包含属性:double price() 价格,默认值为 100
    • 包含属性:String[] authors() 多位作者
  2. 代码实现
java
public @interface Book {
    // 书名
    String value();
    // 价格
    double price() default 100;
    // 多位作者
    String[] authors();
}

使用注解

java
/**
 * @author itheima
 * @version 1.0
 */
public class BookShelf {

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

    }
}

使用注意事项

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

特殊属性 value

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

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

4.4 注解之元注解----了解

默认情况下,注解可以用在任何地方,比如类,成员方法,构造方法,成员变量等地方。如果要限制注解的使用位置怎么办?那就要学习一个新的知识点**:元注解**。

  • @Target
  • @Retention

元注解之@Target

  • 作用:指明此注解用在哪个位置,如果不写默认是任何地方都可以使用。
    • 可选的参数值在枚举类ElemenetType中包括:
properties
 TYPE: 用在类,接口上
 FIELD:用在成员变量上
 METHOD: 用在方法上
 PARAMETER:用在参数上
 CONSTRUCTOR:用在构造方法上
 LOCAL_VARIABLE:用在局部变量上

元注解之@Retention

  • 作用:定义该注解的生命周期(有效范围)。
    • 可选的参数值在枚举类型 RetentionPolicy 中包括
properties
SOURCE:注解只存在于Java源代码中,编译生成的字节码文件中就不存在了。
CLASS:注解存在于Java源代码、编译以后的字节码文件中,运行的时候内存中没有,默认值。
RUNTIME:注解存在于Java源代码中、编译以后的字节码文件中、运行时内存中,程序可以通过反射获取该注解。

4.5 注解解析

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

与注解解析相关的接口

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

获取注解数据的原理

注解作用在那个成员上,就通过反射获得该成员的对象来得到它的注解。
  • 如注解作用在方法上,就通过方法(Method)对象得到它的注解。

    java
     // 得到方法对象
     Method method = clazz.getDeclaredMethod("方法名");
     // 根据注解名得到方法上的注解对象
     Book book = method.getAnnotation(Book.class);
  • 如注解作用在类上,就通过 Class 对象得到它的注解。

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

4.6 模拟 Junit

案例分析

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

注解 MyTest

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

目标类 Demo

java
package com.itheima.annotationtest;

public class Demo {

    public void show1(){
        System.out.println("我是show1");
    }
    @MyTest
    public void show2(){
        System.out.println("我是show2");
    }
    @MyTest
    public void show3(){
        System.out.println("我是show3");
    }
}

调用类 JunitDemo

java
package com.itheima.annotationtest;

import java.lang.reflect.Method;

public class JunitDemo {
    //程序入口
    public static void main(String[] args) throws Exception{
     //解析 Demo类 如何发现 它的方法上有@MyTest 就反射执行该方法
     // 要解析 Demo  获取其字节码文件对象
        Class clazz = Demo.class;
        // 快捷方式创建对象
        Object o = clazz.newInstance();

        // 获取 Demo类中所有的方法
        Method[] methods = clazz.getMethods();
        //遍历得到每一个方法
        for (Method method : methods) {
            // 看该方法上 是否有指定的注解!!
            boolean flag = method.isAnnotationPresent(MyTest.class);//看 该方法上有么有MyTest这个注解类型

            if(flag==true){
                //有  方法反射执行
                method.invoke(o);
            }
        }
    }
}

Released under the MIT License.