day21【反射、注解】

反射、注解

  • 第一章 反射
    • 1.1反射的概述
    • 1.2Class对象的获取方式
    • 1.3Class类常用方法
    • 1.4反射之操作构造方法
    • 1.5反射之操作成员方法
    • 1.6反射之操作成员变量
  • 第二章 注解
    • 2.1JDK提供的三个基本的注解
    • 2.2自定义注解
    • 2.3使用注解并给注解属性赋值
    • 2.4给注解属性赋值的注意事项
    • 2.5元注解
    • 2.6注解解析
    • 2.7实操--完成注解的MyTest案例
  • 总结
  • 练习模块
    • 第一题
    • 第二题:
    • 第三题

第一章 反射

类的加载

  • 当我们的程序在运行后,第一次使用某个类的时候,会将此类的class文件读取到内存,并将此类的所有信息存储到一个Class对象中
    day21【反射、注解】_第1张图片
    类的加载时机
  1. 创建类的实例。

  2. 类的静态变量,或者为静态变量赋值。

  3. 类的静态方法。

  4. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。

  5. 初始化某个类的子类。

  6. 直接使用java.exe命令来运行某个主类。

    以上六种情况的任何一种,都可以导致JVM将一个类加载到方法区。

public class Test {
    public static void main(String[] args) throws Exception{
        // 类的加载时机
        //  1. 创建类的实例。
        //  Student stu = new Student();

        // 2. 类的静态变量,或者为静态变量赋值。
        // Person.country = "中国";

        // 3. 类的静态方法。
        // Person.method();

        // 4. 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。
        // Class c = Class.forName("com.itheima.demo1_类的加载.Student");

        //  5. 初始化某个类的子类。
        // Zi zi = new Zi();

        // 6. 直接使用java.exe命令来运行某个主类。

        // 获取类加载器:
        // 获取Student类的类加载器
        System.out.println(Student.class.getClassLoader());// AppClassLoader
        // 获取String类的类加载器
        System.out.println(String.class.getClassLoader());// null
        // API中说明:一些实现可能使用null来表示引导类加载器。 如果此类由引导类加载器加载,则此方法将在此类实现中返回null

        System.out.println(Student.class.getClassLoader().getParent());// PlatformClassLoader
        System.out.println(Student.class.getClassLoader().getParent().getParent());// null 引导类加载器加载

    }
}

类加载器

类加载器:是负责将磁盘上的某个class文件读取到内存并生成Class的对象。

  • Java中有三种类加载器,它们分别用于加载不同种类的class:
    • 启动类加载器(Bootstrap ClassLoader):用于加载系统类库\bin目录下的class,例如:rt.jar。
    • 扩展类加载器(Extension ClassLoader):用于加载扩展类库\lib\ext目录下的class。
    • 应用程序类加载器(Application ClassLoader):用于加载我们自定义类的加载器。
public class Test{
    public static void main(String[] args){
       System.out.println(Test.class.getClassLoader());//sun.misc.Launcher$AppClassLoader
        System.out.println(String.class.getClassLoader());//null(API中说明:一些实现可能使用null来表示引导类加载器。 如果此类由引导类加载器加载,则此方法将在此类实现中返回null。 )
    }
}

1.1反射的概述

反射的引入

  • 问题:IDEA中的对象是怎么知道类有哪些属性,哪些方法的呢?
 通过反射技术对象类进行了解剖得到了类的所有成员。

反射的概念

 反射是一种机制,利用该机制可以在程序运行过程中对类进行解剖并操作类中的所有成员(成员变量,成员方法,构造方法)

使用反射操作类成员的前提

要获得该类字节码文件对象,就是Class对象

反射在实际开发中的应用

* 开发IDE(集成开发环境),比如IDEA,Eclipse
* 各种框架的设计和学习 比如Spring,Hibernate,Struct,Mybaits....
  • 通过反射技术去获取一个类的成员变量,成员方法,构造方法…,并可以访问

1.2Class对象的获取方式

  • 方式1: 通过类名.class获得
  • 方式2:通过对象名.getClass()方法获得
  • 方式3:通过Class类的静态方法获得: static Class forName(“类全名”)
    • 每一个类的Class对象都只有一个。

示例代码

public class Test {
    public static void main(String[] args) throws Exception{
          /*
            Class对象的获取:
                通过类名.class获得
                通过对象名.getClass()方法获得
                通过Class类的静态方法获得: static Class forName("类全名")
           */
        // 1.方式一:通过类名.class获得
        Class<Student> c1 = Student.class;
        System.out.println(c1);

        // 2.方式二:通过对象名.getClass()方法获得
        Student stu = new Student();
        Class<? extends Student> c2 = stu.getClass();
        System.out.println(c2);

        // 3.方式三:通过Class类的静态方法获得: static Class forName("类全名")
        Class<?> c3 = Class.forName("com.itheima.demo2_Class对象的获取.Student");
        System.out.println(c3);

        // 问题:一个类只有一个字节码对象(Class对象)
        System.out.println(c1 == c2);// true
        System.out.println(c1 == c3);// true
    }
}

1.3Class类常用方法

  • String getSimpleName(); 获得类名字符串:类名
  • String getName(); 获得类全名:包名+类名
  • T newInstance() ; 创建Class对象关联类的对象
public class ReflectDemo02 {
    public static void main(String[] args) throws Exception {
        // 获得Class对象
        Class c = Student.class;
        // 获得类名字符串:类名
        System.out.println(c.getSimpleName());
        // 获得类全名:包名+类名
        System.out.println(c.getName());
        // 创建对象
        Student stu = (Student) c.newInstance();
        System.out.println(stu);
    }
}

1.4反射之操作构造方法

Constructor类概述

反射之操作构造方法的目的
* 获得Constructor对象来创建类的对象。

Constructor类概述
* 类中的每一个构造方法都是一个Constructor类的对象

通过反射获取类的构造方法

Class类中与Constructor相关的方法

  1. Constructor getConstructor(Class… parameterTypes)
    * 根据参数类型获得对应的Constructor对象。
    * 只能获得public修饰的构造方法
  2. Constructor getDeclaredConstructor(Class… parameterTypes)
    * 根据参数类型获得对应的Constructor对象
    * 可以是public、protected、(默认)、private修饰符的构造方法。
  3. Constructor[] getConstructors()
    获得类中的所有构造方法对象,只能获得public的
  4. Constructor[] getDeclaredConstructors()
    获得类中的所有构造方法对象
    可以是public、protected、(默认)、private修饰符的构造方法。

通过反射执行构造方法

Constructor对象常用方法

  1. T newInstance(Object… initargs)
    根据指定的参数创建对象
  2. void setAccessible(true)
    设置"暴力反射"——是否取消权限检查,true取消权限检查,false表示不取消

示例代码

package com.itheima.demo4_反射之构造方法;

/**
 * @Author:pengzhilin
 * @Date: 2020/5/14 10:13
 */
public class Student {
    // 属性
    public String name;// 姓名
    public String sex;// 性别
    public int age;// 年龄

    // 构造方法
    public Student() {
    }

    public Student(String name, String sex, int age) {
        this.name = name;
        this.sex = sex;
        this.age = age;
    }

    private Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 成员方法

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

public class Test1 {
    public static void main(String[] args) throws Exception{
        /*
            - 通过反射获取类的构造方法,并执行构造方法创建该类的对象
                - Constructor类概述
                    概述:类中的每一个构造方法都是一个Constructor类的对象
                    通过反射获取类的构造方法,其实就是获取该构造方法对于的Constructor对象

                - 通过反射获取类的构造方法: 使用Class类中的方法
                   我们每一个类都会有一个对于的Class对象,所以该类中的构造方法就在我们的Class对象中,
                   所以可以使用Class对象获取构造方法
                   Class类中与Constructor相关的方法
                        1. Constructor getConstructor(Class... parameterTypes)  获取单个public修饰的构造方法
                                * 根据参数类型获得对应的Constructor对象。
                                * 只能获得public修饰的构造方法
                         2. Constructor getDeclaredConstructor(Class... parameterTypes) 获取单个构造方法
                                * 根据参数类型获得对应的Constructor对象
                                * 可以是public、protected、(默认)、private修饰符的构造方法。
                         3. Constructor[] getConstructors() 获取所有public修饰的构造方法
                                获得类中的所有构造方法对象,只能获得public的
                         4. Constructor[] getDeclaredConstructors() 获取所有构造方法
                                获得类中的所有构造方法对象
                                可以是public、protected、(默认)、private修饰符的构造方法。
                    记忆方法:
                        1. 加了s的就是获取多个,不加s的就是获取单个
                        2. 加了Declared的就是获取任意修饰符修饰的构造方法,不加就只能获取public修饰的构造方法

                 小结:
                     Constructor getDeclaredConstructor(Class... parameterTypes) 获取单个构造方法
                     Constructor[] getDeclaredConstructors() 获取所有构造方法

                - 通过反射执行构造方法:使用Constructor对象中的方法,来执行该对象表示的构造方法
                    Constructor对象常用方法
                        1. T newInstance(Object... initargs)
                            根据指定的参数创建对象
                        2. void setAccessible(true)
                           设置"暴力反射"——是否取消权限检查,true取消权限检查,false表示不取消

         */
        // 0.获取Student类的Class对象
        Class<?> c = Class.forName("com.itheima.demo4_反射之构造方法.Student");

        // 1.获取单个public修饰的构造方法
        // Constructor getConstructor(Class... parameterTypes)  获取单个public修饰的构造方法
        //    参数:传入你要获取的构造方法的参数类型的Class对象

        // 获取Student类的空参构造方法
        Constructor<?> cons1 = c.getConstructor();
        System.out.println(cons1);

        // 获取Student类的带有三个参数的构造方法,并且参数的类型顺序为:String,String,int
        Constructor<?> cons2 = c.getConstructor(String.class,String.class, int.class);

        // public Student(String name,String sex,int.class);
        // public Student(String name,int.class,String sex);

        System.out.println(cons2);

        // 2.获取单个非public修饰的构造方法
        // Constructor getDeclaredConstructor(Class... parameterTypes) 获取单个构造方法
        //    参数:传入你要获取的构造方法的参数类型的Class对象

        // 获取Student类的带有2个参数的构造方法,并且参数的类型顺序为:String,int
        Constructor<?> cons3 = c.getDeclaredConstructor(String.class, int.class);
        System.out.println(cons3);

        System.out.println("============================================");
        // 3.获取多个public修饰的方法
        // Constructor[] getConstructors() 获取所有public修饰的构造方法
        Constructor<?>[] conArr1 = c.getConstructors();
        for (Constructor<?> con : conArr1) {
            System.out.println(con);
        }

        System.out.println("============================================");
        // 4.获取所有修饰的方法
        // Constructor[] getDeclaredConstructors() 获取所有构造方法
        Constructor<?>[] conArr2 = c.getDeclaredConstructors();
        for (Constructor<?> con : conArr2) {
            System.out.println(con);
        }

    }
}



public class Test2 {
    public static void main(String[] args) throws Exception{
        /*
            - 获取类的构造方法:
                    Constructor getDeclaredConstructor(Class... parameterTypes) 获取单个构造方法
                     Constructor[] getDeclaredConstructors() 获取所有构造方法

            - 通过反射执行构造方法:使用Constructor对象中的方法,来执行该对象表示的构造方法
                Constructor对象常用方法
                    1. T newInstance(Object... initargs)
                        根据指定的参数创建对象
                        参数: 传入执行该构造方法需要的实际参数

                    2. void setAccessible(boolean b)
                       设置"暴力反射"——是否取消权限检查,true取消权限检查,false表示不取消
         */
        // 获取Student类的Class对象
        Class<?> c = Class.forName("com.itheima.demo4_反射之构造方法.Student");

        // 1.获取public修饰的构造方法对象,并执行该对象表示的构造方法来创建对象
        // 1.1 获取public修饰的构造方法对象  public Student(String name, String sex, int age)
        Constructor<?> cons1 = c.getDeclaredConstructor(String.class, String.class, int.class);

        // 1.2 执行该对象表示的构造方法来创建对象
        Object obj = cons1.newInstance("张三", "女", 18);// 真正返回的是Student对象
        System.out.println(obj);// Student{name='张三', sex='女', age=18}

        System.out.println("==========================================");
        // 2.获取private修饰的构造方法对象,并执行该对象表示的构造方法来创建对象
        // 2.1 获取private修饰的构造方法对象
        Constructor<?> cons2 = c.getDeclaredConstructor(String.class, int.class);

        // 2.2 执行该对象表示的构造方法来创建对象
        // 设置"暴力反射"
        cons2.setAccessible(true);

        Object obj2 = cons2.newInstance("李四", 19);
        System.out.println(obj2);
    }
}

1.5反射之操作成员方法

Method类概述

反射之操作成员方法的目的
    * 操作Method对象来调用成员方法
Method类概述
    * 每一个成员方法都是一个Method类的对象。

通过反射获取类的成员方法

Class类中与Method相关的方法
* Method getMethod(String name,Class...args);
    * 根据方法名和参数类型获得对应的构造方法对象,只能获得public* Method getDeclaredMethod(String name,Class...args);
    * 根据方法名和参数类型获得对应的构造方法对象,包括publicprotected(默认)private* Method[] getMethods();
    * 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的

* Method[] getDeclaredMethods();
    * 获得类中的所有成员方法对象,返回数组,只获得本类的,包括publicprotected(默认)private

通过反射执行成员方法

Method对象常用方法
*  Object invoke(Object obj, Object... args)
    * 调用指定对象obj的该方法
    * args:调用方法时传递的参数
*  void setAccessible(true)
    设置"暴力访问"——是否取消权限检查,true取消权限检查,false表示不取消

示例代码

ackage com.itheima.demo5_反射之操作成员方法;

/**
 * @Author:pengzhilin
 * @Date: 2020/5/14 10:13
 */
public class Student {
    public void show1(){
        System.out.println("public 修饰的show1方法,无参数...");
    }

    public void show1(String str,int num){
        System.out.println("public 修饰的show1方法,2个参数...");
        System.out.println("str:"+str+",num:"+num);
    }

    public void show2(){
        System.out.println("public 修饰的show2方法...");
    }

    private void show3(){
        System.out.println("private 修饰的show3方法...");
    }
}

package com.itheima.demo5_反射之操作成员方法;

import java.lang.reflect.Method;


public class Test1 {
    public static void main(String[] args) throws Exception {
        /*
            反射之操作成员方法:
                - 通过反射获取类的成员方法,并通过反射技术执行成员方法
                    - Method类概述
                            每一个成员方法都是一个Method类的对象。

                    - 通过反射获取类的成员方法
                            Class类中与Method相关的方法
                                * Method getMethod(String name,Class...args); 获取单个public修饰的成员方法
                                    * 根据方法名和参数类型获得对应的构造方法对象,只能获得public的

                                * Method getDeclaredMethod(String name,Class...args); 获取任意修饰符修饰的单个成员方法
                                    * 根据方法名和参数类型获得对应的构造方法对象,包括public、protected、(默认)、private的

                                * Method[] getMethods();  获取所有public修饰的成员方法
                                    * 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的

                                * Method[] getDeclaredMethods(); 获取所有成员方法
                                    * 获得类中的所有成员方法对象,返回数组,只获得本类的,包括public、protected、(默认)、private的

                        记忆:
                            Method getDeclaredMethod(String name,Class...args); 获取任意修饰符修饰的单个成员方法\
                            Method[] getDeclaredMethods(); 获取所有成员方法

                    - 通过反射执行成员方法
         */
        // 1.获取Student类的Class对象
        Class<?> c = Class.forName("com.itheima.demo5_反射之操作成员方法.Student");

        // 2.获取单个成员方法
        // 2.1 获取单个public修饰的方法
        // Method getMethod(String name,Class...args);
        // 第一个参数: 方法名
        // 第二个参数: 该方法的参数类型的Class对象,如果没有参数就不传入
        Method show1M1 = c.getMethod("show1");// 第一个show1方法
        System.out.println(show1M1);

        Method show1M2 = c.getMethod("show1", String.class, int.class);
        System.out.println(show1M2);

        // 2.2 获取单个private修饰的成员方法
        // Method getDeclaredMethod(String name,Class...args);
        // 第一个参数: 方法名
        // 第二个参数: 该方法的参数类型的Class对象,如果没有参数就不传入
        Method show3M = c.getDeclaredMethod("show3");
        System.out.println(show3M);

        System.out.println("=======================");

        // 3.获取多个public修饰的成员方法
        // 3.1 获取所有public修饰的成员方法
        // Method[] getMethods();
        Method[] mArr1 = c.getMethods();
        for (Method method : mArr1) {
            System.out.println(method);
        }

        System.out.println("=======================");

        // 3.2 获取所有的成员方法
        // Method[] getDeclaredMethods();
        Method[] mArr2 = c.getDeclaredMethods();
        for (Method method : mArr2) {
            System.out.println(method);
        }

    }
}
package com.itheima.demo5_反射之操作成员方法;

import java.lang.reflect.Method;


public class Test2 {
    public static void main(String[] args) throws Exception {
        /*
             Method getDeclaredMethod(String name,Class...args); 获取任意修饰符修饰的单个成员方法\
             Method[] getDeclaredMethods(); 获取所有成员方法

            通过反射技术执行成员方法:
                Method对象常用方法
                    *  Object invoke(Object obj, Object... args)
                        * 参数1: 调用该方法的对象
                        * 参数2:调用方法时传递的实际参数,如果方法没有参数,就不传
                    *  void setAccessible(true)
                        设置"暴力访问"——是否取消权限检查,true取消权限检查,false表示不取消
         */
        // 0.获取Student类的Class对象
        Class<?> c = Class.forName("com.itheima.demo5_反射之操作成员方法.Student");

        // 1.通过反射获取public修饰的成员方法对象,并通过反射执行该方法
        // 1.1 通过反射获取public修饰的成员方法对象
        Method show1M = c.getDeclaredMethod("show1", String.class, int.class);

        // 1.2 通过反射执行该方法
        Student stu = new Student();// 使用反射创建对象:通过反射获取构造方法对象,执行该构造方法
        show1M.invoke(stu,"itheima",14);

        System.out.println("=======================");
        // 2.通过反射获取private修饰的成员方法对象,并通过反射执行该方法
        // 2.1 通过反射获取private修饰的成员方法对象
        Method show3M = c.getDeclaredMethod("show3");

        // 2.2 通过反射执行该方法
        // 设置暴力反射
        show3M.setAccessible(true);
        show3M.invoke(stu);
    }
}

1.6反射之操作成员变量

Field类概述

反射之操作成员变量的目的
    * 通过Field对象给对应的成员变量赋值和取值

Field类概述
    * 每一个成员变量都是一个Field类的对象。

通过反射获取类的成员变量

Class类中与Field相关的方法
* Field getField(String name);
    *  根据成员变量名获得对应Field对象,只能获得public修饰
* Field getDeclaredField(String name);
    *  根据成员变量名获得对应Field对象,包括publicprotected(默认)private* Field[] getFields();
    * 获得所有的成员变量对应的Field对象,只能获得public* Field[] getDeclaredFields();
    * 获得所有的成员变量对应的Field对象,包括publicprotected(默认)private

通过反射访问成员变量

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对应的属性值的,针对不同的类型选取不同的方法。

示例代码

package com.itheima.demo6_反射之操作成员变量;


public class Student {
    public String name;// 姓名
    public int age;// 年龄
    private String sex;// 性别

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

public class Test1 {
    public static void main(String[] args) throws Exception{
        /*
            反射之操作成员变量:
                - Field类概述 : 每一个成员变量都是一个Field类的对象。
                - 通过反射获取类的成员变量
                        Class类中与Field相关的方法
                            * Field getField(String name);获取单个public修饰的成员变量
                                *  根据成员变量名获得对应Field对象,只能获得public修饰
                            * Field getDeclaredField(String name);获取单个任意修饰符修饰的成员变量
                                *  根据成员变量名获得对应Field对象,包括public、protected、(默认)、private的
                            * Field[] getFields();获取所有的public修饰的成员变量
                                * 获得所有的成员变量对应的Field对象,只能获得public的
                            * Field[] getDeclaredFields();获取所有的成员变量
                                * 获得所有的成员变量对应的Field对象,包括public、protected、(默认)、private的
                     结论:
                        Field getDeclaredField(String name);获取单个任意修饰符修饰的成员变量
                            参数:成员变量名  字符串类型
                        Field[] getDeclaredFields();获取所有的成员变量

                - 通过反射访问成员变量
         */
        // 通过反射获取类的成员变量
        // 1.获取Student类的Class对象
        Class<?> c = Class.forName("com.itheima.demo6_反射之操作成员变量.Student");

        // 2. 获取单个成员变量的Field对象
        // 2.1 获取单个public修饰的成员变量
        Field nameF = c.getDeclaredField("name");
        System.out.println(nameF);

        // 2.2 获取单个private修饰的成员变量
        Field sexF = c.getDeclaredField("sex");
        System.out.println(sexF);

        System.out.println("==============================================");

        // 3.获取所有的成员变量的Field对象
        Field[] fieldArr = c.getDeclaredFields();
        for (Field field : fieldArr) {
            System.out.println(field);
        }


    }
}

public class Test2 {
    public static void main(String[] args) throws Exception {
        /*
               通过反射获取类的成员变量
                        Field getDeclaredField(String name);获取单个任意修饰符修饰的成员变量
                            参数:成员变量名  字符串类型
                        Field[] getDeclaredFields();获取所有的成员变量

               通过反射访问成员变量:
                Field对象常用方法
                    为成员变量设置值的方法
                        void  set(Object obj, Object value)    记住
                        参数1: 要赋值属性的对象
                        参数2: 属性具体的值

                    获取成员变量值的方法
                        Object get(Object obj)      记住
                        参数: 要获取属性值的对象
                        返回值: 属性具体的值

                    void setAccessible(true);暴力反射,设置为可以直接访问私有类型的属性。  记住

                    Class getType(); 获取属性的类型,返回Class对象。
         */
        // 通过反射获取类的成员变量
        // 1.获取Student类的Class对象
        Class<?> c = Class.forName("com.itheima.demo6_反射之操作成员变量.Student");

        // 2. 获取单个成员变量的Field对象
        // 2.1 获取单个public修饰的成员变量
        Field nameF = c.getDeclaredField("name");
        // 2.2 通过反射为name属性赋值
        Student stu = new Student();
        nameF.set(stu,"张三");

        Field ageF = c.getDeclaredField("age");
        // ageF.set(stu,18);
        ageF.setInt(stu,19);

        System.out.println(stu);// Student{name='张三', age=18, sex='null'}

        // 2.3 获取某个属性的值
        System.out.println(nameF.get(stu));// 张三
        System.out.println(ageF.get(stu));// 19

        // 2.2 获取单个private修饰的成员变量
        Field sexF = c.getDeclaredField("sex");
        // 暴力反射
        sexF.setAccessible(true);
        sexF.set(stu,"李四");

        // 获取
        System.out.println(sexF.get(stu));// 李四


        // 获取nameF属性的类型
        Class<?> c1 = nameF.getType();
        System.out.println(c1);// class java.lang.String


    }
}

第二章 注解

注解概述

  • 注解(annotation),是一种代码级别的说明,和类 接口平级关系.

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

    • 我们之前使用过的注解:

      ​ 1).@Override:子类重写方法时——编译时起作用

      ​ 2).@FunctionalInterface:函数式接口——编译时起作用

      ​ 3).@Test:JUnit的测试注解——运行时起作用

注解的作用

  • 生成帮助文档**:**@author和@version

  • 执行编译期的检查 例如:@Override

  • 框架的配置(框架=代码+配置)

    • 具体使用请关注框架课程的内容的学习。

小结

  1. 注解用在“源码中”,作为一个“标记”。给“注解解析器”看的,告诉“注解解析器”怎样编译、运行下面的代码。
  2. 开发中,我们一般都是使用注解

2.1JDK提供的三个基本的注解

​ @Override:描述方法的重写.

​ @SuppressWarnings:压制\忽略警告.

​ @Deprecated:标记过时

2.2自定义注解

自定义注解语法

public @interface 注解名{
     属性
}
  • 示例代码
/**
 * 定义了注解
 *
 */
public @interface Annotation01 {

}

注解属性

格式

  • 数据类型 属性名();

属性类型

​ 1.基本类型

​ 2.String

​ 3.Class类型

​ 4.注解类型

​ 5. 枚举类型

​ 6.以上类型的一维数组类型

  • 示例代码
public @interface Annotation01 {
    // 1.基本数据类型(4类8种)
    int a();
    double b();

    // 2.String类型
    String c();

    // 3.Class类型
    Class d();

    // 4.注解类型
    Annotation02 f();
    
    // 5.枚举类型
    Sex e();
    // 6.以上类型的一维数组类型
    int[] g();
    double[] h();
    String[] i();
    Sex[] j();
    Annotation02[] k();
}

2.3使用注解并给注解属性赋值

使用注解:
如果一个注解中有属性,那么使用注解的时候一定要给注解属性赋值
如果一个注解没用属性,那么就不需要给注解属性赋值,直接使用即可
如何给注解属性赋值:
@注解名(属性名=值,属性名2=值2)

public @interface MyAnnotation1 {
    // 不定义注解属性
}
public @interface MyAnnotation2 {
    int a();

    String[] b();
}

@MyAnnotation1()
public class Test1 {
    @MyAnnotation1
    public static void main(String[] args) {
        /*
            使用注解:
                不带属性的注解: 注解中没有定义属性,或者里面的属性已经给了默认值
                     在需要使用该注解的地方,写上 @注解名 即可 或者 @注解名()


         */
    }
}

@MyAnnotation2(a=18,b={"itheima","itcast"})
public class Test2 {

    @MyAnnotation2(a=18,b={"itheima","itcast"})
    public static void main(String[] args) {
        /*
             使用注解:
                带有属性的注解: 注解中定义了属性,并这些属性没有给默认值
                    标准格式: @注解名(属性名=属性值,属性名=属性值,....)
         */

    }
}


2.4给注解属性赋值的注意事项

  • 一旦注解有属性了,使用注解的时候,属性必须有值
  • 若属性类型是一维数组的时候,当数组的值只有一个的时候可以省略{}
  • 如果注解中只有一个属性,并且属性名为value,那么使用注解给注解属性赋值的时候,注解属性名value可以省略
  • 注解属性可以有默认值 格式:属性类型 属性名() defaul t 默认值;
public @interface MyAnnotation1 {
    int a();
}

public @interface MyAnnotation2 {
    int[] arr();
}

public @interface MyAnnotation3 {
    int value();
}

public @interface MyAnnotation33 {
    String[] value();
}

public @interface MyAnnotation4 {
    int a() default 10;
}

public class Test {
    public static void main(String[] args) {
        /*
            给注解属性赋值的注意事项:
                - 一旦注解有属性了,使用注解的时候,属性必须有值
                - 若属性类型是一维数组的时候,当数组的值只有一个的时候可以省略{}
                - 如果注解中只有一个属性,并且属性名为value,那么使用注解给注解属性赋值的时候,注解属性名value可以省略
                - 注解属性可以有默认值  格式:属性类型 属性名() defaul t 默认值;

         */
    }

    //  注解属性可以有默认值  格式:属性类型 属性名() defaul t 默认值;
    //@MyAnnotation4
    //@MyAnnotation4()
    @MyAnnotation4(a = 100)
    public static void method4(){

    }

    // 若属性类型是一维数组的时候,当数组的值只有一个的时候可以省略{}
    //如果注解中只有一个属性,并且属性名为value,那么使用注解给注解属性赋值的时候,注解属性名value可以省略
    //@MyAnnotation33(value={"itheima","itcast"})
    //@MyAnnotation33(value={"itheima"})
    //@MyAnnotation33(value="itheima")
    @MyAnnotation33("itheima")
    public static void method33(){

    }

    // 如果注解中只有一个属性,并且属性名为value,那么使用注解给注解属性赋值的时候,注解属性名value可以省略
    //@MyAnnotation3(value=10)
    @MyAnnotation3(10)
    public static void method3(){

    }

    // 若属性类型是一维数组的时候,当数组的值只有一个的时候可以省略{}
    // @MyAnnotation2(arr={10,20,30})
    // @MyAnnotation2(arr={10})
    @MyAnnotation2(arr=10)
    public static void method2(){

    }

    // 一旦注解有属性了,使用注解的时候,属性必须有值
    @MyAnnotation1(a = 10)
    public static void method1(){

    }

}

2.5元注解

什么是元注解

​ 定义在注解上的注解

常见的元注解

​ @Target:表示该注解作用在什么上面(位置),默认注解可以在任何位置. 值为:ElementType的枚举值

​ METHOD:方法

​ TYPE:类 接口

​ FIELD:字段

​ CONSTRUCTOR:构造方法声明

​ @Retention:定义该注解保留到那个代码阶段, 值为:RetentionPolicy类型,默认只在源码阶段保留

​ SOURCE:只在源码上保留(默认)

​ CLASS:在源码和字节码上保留

​ RUNTIME:在所有的阶段都保留

.java (源码阶段) ----编译—> .class(字节码阶段) ----加载内存–> 运行(RUNTIME)

案例:

// 标准格式:
//@Target(value={ElementType.TYPE,ElementType.FIELD,ElementType.METHOD,ElementType.LOCAL_VARIABLE})
//@Retention(value= RetentionPolicy.RUNTIME)
    // 省略格式
@Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD,ElementType.LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation1 {// 默认该注解可以在任何位置使用

}

@MyAnnotation1
public class Test1 {

    @MyAnnotation1
    int age = 18;

    @MyAnnotation1
    public static void main(String[] args) {
       
        @MyAnnotation1
        int num1 = 10;// 局部变量

        // jdk中的@Override
        // @Override // 编译报错
        int num2 = 10;// 局部变量

    }

    @Override
    public String toString() {
        return super.toString();
    }
}

2.6注解解析

java.lang.reflect.AnnotatedElement接口: Class、Method、Field、Constructor等实现了AnnotatedElement

  • T getAnnotation(ClassannotationType):得到指定类型的注解引用。没有返回null。

  • boolean isAnnotationPresent(Class annotationType):判断指定的注解有没有。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation1 {
    String name();

    int age();

}


public class Test {

    @MyAnnotation1(name="张三",age=18)
    public void show1(){
        System.out.println("show1方法执行了....");
    }

    public void show2(){
        System.out.println("show2方法执行了....");
    }

    public static void main(String[] args) throws Exception{
        /*
            java.lang.reflect.AnnotatedElement接口: Class、Method、Field、Constructor等实现了AnnotatedElement
            - T getAnnotation(Class annotationType):得到指定类型的注解引用。没有返回null。
            - boolean isAnnotationPresent(Class annotationType):判断指定的注解有没有。

         */
        // 需求:1.获取show1方法上面的注解对象
        // 1.1 得到Test类的Class对象
        Class<?> c = Class.forName("com.itheima.demo12_注解解析.Test");

        // 1.2 获得show1方法的Method对象
        Method show1M = c.getDeclaredMethod("show1");

        // 1.3 根据Method对象调用getAnnotation()方法得到注解对象
        MyAnnotation1 a1 = show1M.getAnnotation(MyAnnotation1.class);
        System.out.println(a1.name());
        System.out.println(a1.age());

        System.out.println("======================");

        // 2.需求: 判断某个方法上是否有MyAnnotation1注解
        // 判断show1方法上是否有MyAnnotation1注解
        boolean res1 = show1M.isAnnotationPresent(MyAnnotation1.class);
        System.out.println(res1);// true

        // 判断show2方法上是否有MyAnnotation1注解
        Method show2M = c.getDeclaredMethod("show2");
        boolean res2 = show2M.isAnnotationPresent(MyAnnotation1.class);
        System.out.println(res2);// false

    }
}

2.7实操–完成注解的MyTest案例

需求

​ 在一个类(测试类,TestDemo)中有三个方法,其中两个方法上有@MyTest,另一个没有.还有一个主测试类(MainTest)中有一个main方法. 在main方法中,让TestDemo类中含有@MyTest方法执行. 自定义@MyTest, 模拟单元测试.

思路分析

  1. 定义两个类和一个注解

  2. 在MainTest的main()方法里面:

    ​ //1.获得TestDemo字节码对象
    ​ //2.反射获得TestDemo里面的所有的方法
    ​ //3.遍历方法对象的数组. 判断是否有@MyTest(isAnnotationPresent)
    ​ //4.有就执行(method.invoke())

代码实现

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

}
  • TestDemo.java
public class TestDemo {
    @MyTest
    public void show1(){
        System.out.println("show1方法执行了...");
    }

    @MyTest
    public void show2(){
        System.out.println("show2方法执行了...");
    }

    public void show3(){
        System.out.println("show3方法执行了...");
    }

}

  • MainTest.java
public class MainTest {

    public static void main(String[] args) throws Exception {
        // 让第一个类中含有@MyTest注解的方法执行
        // 1.获取TestDemo类的字节码对象
        Class<TestDemo> clazz = TestDemo.class;

        // 2.使用字节码对象获取该类中所有方法对象
        Method[] methods = clazz.getDeclaredMethods();

        // 3.循环遍历所有方法对象
        for (Method method : methods) {
            // 4.在循环中,判断遍历出来的方法对象是否含有@MyTest注解
            boolean res = method.isAnnotationPresent(MyTest.class);
            if (res) {
                // 5.如果有,就调用该方法执行
                method.invoke(clazz.newInstance());
            }
        }

    }

}

总结

1.能够通过反射技术获取Class字节码对象
三种方式:
1).对象名.getClass();
2).类名.class;
3).Class.forName(“全名限定的类名”)

2.能够通过反射技术获取构造方法对象,并创建对象。
1).public Constructor getDeclaredConstructor(Class … params):
public Constructor getDeclaredConstructors():
2).创建对象:
con.newInstance(Object … parmas);
3.能够通过反射获取成员方法对象,并且调用方法。
1).public Method getDeclaredMethod(String methodName,Class … params)
public Method getDeclaredMethods()
2).调用方法:
method.invoke(Object target,Object … params)
4.能够通过反射获取属性对象,并且能够给对象的属性赋值和取值。
1).public Field getDeclaredField(String fieldName):
public Field getDeclaredFields():
2).设置属性值:
Field对象调用set(Object obj,Object obj)
获取属性值:
Field对象调用get(Object obj)
setAccessible(true); // 暴力反射
5.能够说出注解的作用
用在“源码中”,作为一个“标记”。给“注解解析器”看的,告诉“注解解析器”怎样编译、运行下面的代码。
6.能够自定义注解和使用注解
1).自定义注解:
public @interface MyTest{
属性格式: 数据类型 属性名();
}
2).使用注解:
@MyTest(属性名=属性值,属性名=属性值)
class Student{
}
7.能够说出常用的元注解及其作用
` 1).“元注解”:也是注解,只能用在“注解的定义上”,它用来对新定义的注解进行一些规定:
A).注解的使用位置:
@Target注解:
属性的几个可选值:
ElementType.TYPE : 可以用在“类”、“接口”上;
ElementType.FIELD:用在“成员属性”上。
ElementType.METHOD:用在“成员方法”上。
ElementType.CONSTRUCTOR:用在“构造方法”上。
ElementType.LOCAL_VARIABLE:用在“局部变量”上。
ElementType.PARAMETER:用在“方法的形参”上。

	B).注解的生命周期:
		@Retention注解:
			RetentionPolicy.SOURCE:注解只能出现在“源码”中。编译后就没有了。例如:@Override
			RetentionPolicy.CLASS:注解会出现在“源码”,“class文件”中。但运行时不会被加载到内存。
			RetentionPolicy.RUNTIME:注解会出现在“源码”、“class文件”、“运行时内存”。@Test

8.能够解析注解并获取注解中的数据
见案例
- T getAnnotation(Class annotationType):得到指定类型的注解引用。没有返回null。
- boolean isAnnotationPresent(Class annotationType):判断指定的注解有没有。
9.能够完成注解的MyTest案例
见案例

练习模块

第一题

需求:按要求完成下面两个方法的方法体

1.写一个方法,此方法可将obj对象中名为propertyName的属性的值设置为value. public void setProperty(Object obj, String propertyName, Object value){}

  1. 写一个方法,此方法可以获取obj对象中名为propertyName的属性的值

public Object getProperty(Object obj, String propertyName){}

public class Test06 {
    public static void main(String[] args) throws Exception {
        Student student = new Student(30,"张三");
        setProperty(student, "age", 25);
        System.out.println(student);

        Object obj = getProperty(student, "name");
        System.out.println(obj);
    }

    //给对象obj的名字为propertyName的属性设置为value
    public static void setProperty(Object obj, String propertyName, Object value) throws Exception{
        //1.获取obj的字节码对象
        Class clazz = obj.getClass();
        //2.获取propertyName属性对应的Field对象
        Field propertyNameField = clazz.getDeclaredField(propertyName);
        //3.设置成可访问的
        propertyNameField.setAccessible(true);
        //4.调用set方法给对象赋值
        propertyNameField.set(obj, value);

    }
    //给对象obj的名字为propertyName的属性设置为value
    public static Object getProperty(Object obj, String propertyName) throws Exception{
        //1.获取obj的字节码对象
        Class clazz = obj.getClass();
        //2.获取propertyName属性对应的Field对象
        Field propertyNameField = clazz.getDeclaredField(propertyName);
        //3.设置成可访问的
        propertyNameField.setAccessible(true);
        //4.调用get方法获取该对象对应属性的值
        return propertyNameField.get(obj);
    }
}

class Student {
    private int age;
    private String name;
    public Student() {

    }

    public Student(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student [age=" + age + ", name=" + name + "]";
    }
}

第二题:

需求

  1. 定义一个Person类,包含私有属性name、age,getXxx和setXxx方法和有参满参构造方法。

  2. 使用反射的方式创建一个实例、调用构造函数初始化name、age。使用反射方式调用setName方法对姓名进行设置,不使用setAge方法直接使用反射方式对age赋值。

public class Test07 {
    public static void main(String[] args) throws Exception, SecurityException {
        //1.获取Person类的字节码对象
        Class clazz = Person.class;
        //2.利用反射获取有参构造方法
        Constructor constructor  = clazz.getConstructor(int.class,String.class);
        //3.调用构造方法,给属性初始化
        Person person =  (Person)constructor.newInstance(30,"灭绝师太");
        System.out.println(person);
        //4.使用反射方式调用setName方法对名称进行设置
        Method setNameMethod = clazz.getMethod("setName", String.class);
        setNameMethod.invoke(person, "张三丰");
        //5.不使用setAge方法直接使用反射方式对age赋值。
        Field ageField = clazz.getDeclaredField("age");
        ageField.setAccessible(true);
        ageField.set(person, 50);
        System.out.println(person);
    }
}
class Person {
    private int age;
    private String name;
    public Person() {
    }
    public Person(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Person [age=" + age + ", name=" + name + "]";
    }
}

第三题

需求:定义一个注解:Book

​ * 包含属性:String value() 书名

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

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

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

  2. 解析获得该成员方法上使用注解的属性值。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Book{
    String value();
    double price();
    String[] authors();
}
public class Test12 {
    @Book(value = "西游记", price = 99, authors = {"吴承恩", "白求恩"})
    public void sell() {

    }

    public static void main(String[] args) throws Exception {
        Class c = Test12.class;
        Method m = c.getMethod("sell");
        if (m.isAnnotationPresent(Book.class)) {
            Book b = m.getAnnotation(Book.class);
            System.out.println(b);
        }
    }
}

你可能感兴趣的:(day21【反射、注解】)