反射和注解

发布 | 2024-08-11 | JAVA

反射和注解

javalang.Class类

Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。

要想解剖一个类,必须先要获取到该类的Class对象。而剖析一个类或用反射解决具体的问题就是使用相关API(1)java.lang.Class(2)java.lang.reflect.*。所以,Class对象是反射的根源。

哪些类型可以获取Class对象

所有Java类型

用代码示例

//(1)基本数据类型和void
例如:int.class
     void.class
//(2)类和接口
例如:String.class
    Comparable.class
//(3)枚举
例如:ElementType.class
//(4)注解
例如:Override.class
//(5)数组
例如:int[].class

获取Class对象的四种方式

(1)类型名.class

要求编译期间已知类型

(2)对象.getClass()

获取对象的运行时类型

(3)Class.forName(类型全名称)

可以获取编译期间未知的类型

(4)ClassLoader的类加载器对象.loadClass(类型全名称)

可以用系统类加载对象或自定义加载器对象加载指定路径下的类型

public class TestClass {
    @Test
    public void test05() throws ClassNotFoundException{
        Class c = TestClass.class;
        ClassLoader loader = c.getClassLoader();
        
        Class c2 = loader.loadClass("com.atguigu.test05.Employee");
        Class c3 = Employee.class;
        System.out.println(c2 == c3);
    }
    
    @Test
    public void test03() throws ClassNotFoundException{
        Class c2 = String.class;
        Class c1 = "".getClass();
        Class c3 = Class.forName("java.lang.String");
        
        System.out.println(c1 == c2);
        System.out.println(c1 == c3);
    }
}

反射的应用

获取类型的详细信息

可以获取:包、修饰符、类型名、父类(包括泛型父类)、父接口(包括泛型父接口)、成员(属性、构造器、方法)、注解(类上的、方法上的、属性上的)

示例代码获取常规信息:

public class TestClassInfo {
    public static void main(String[] args) throws NoSuchFieldException, SecurityException {
        //1、先得到某个类型的Class对象
        Class clazz = String.class;
        //比喻clazz好比是镜子中的影子
        
        //2、获取类信息
        //(1)获取包对象,即所有java的包,都是Package的对象
        Package pkg = clazz.getPackage();
        System.out.println("包名:" + pkg.getName());
        
        //(2)获取修饰符
        //其实修饰符是Modifier,里面有很多常量值
        /*
         * 0x是十六进制
         * PUBLIC           = 0x00000001;  1    1
         * PRIVATE          = 0x00000002;  2    10
         * PROTECTED        = 0x00000004;  4    100
         * STATIC           = 0x00000008;  8    1000
         * FINAL            = 0x00000010;  16    10000
         * ...
         * 
         * 设计的理念,就是用二进制的某一位是1,来代表一种修饰符,整个二进制中只有一位是1,其余都是0
         * 
         * mod = 17          0x00000011
         * if ((mod & PUBLIC) != 0)  说明修饰符中有public
         * if ((mod & FINAL) != 0)   说明修饰符中有final
         */
        int mod = clazz.getModifiers();
        System.out.println(Modifier.toString(mod));
        
        //(3)类型名
        String name = clazz.getName();
        System.out.println(name);
        
        //(4)父类,父类也有父类对应的Class对象
        Class superclass = clazz.getSuperclass();
        System.out.println(superclass);
        
        //(5)父接口们
        Class[] interfaces = clazz.getInterfaces();
        for (Class class1 : interfaces) {
            System.out.println(class1);
        }
        
        //(6)类的属性,  你声明的一个属性,它是Field的对象
/*        Field clazz.getField(name)  根据属性名获取一个属性对象,但是只能得到公共的
        Field[] clazz.getFields();  获取所有公共的属性
        Field clazz.getDeclaredField(name)  根据属性名获取一个属性对象,可以获取已声明的
        Field[] clazz.getDeclaredFields()    获取所有已声明的属性
        */
        Field valueField = clazz.getDeclaredField("value");
//        System.out.println("valueField = " +valueField);
        
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field field : declaredFields) {
            //修饰符、数据类型、属性名    
            int modifiers = field.getModifiers();
            System.out.println("属性的修饰符:" + Modifier.toString(modifiers));
            
            String name2 = field.getName();
            System.out.println("属性名:" + name2);
            
            Class<?> type = field.getType();
            System.out.println("属性的数据类型:" + type);
        }
        System.out.println("-------------------------");
        //(7)构造器们
        Constructor[] constructors = clazz.getDeclaredConstructors();
        for (Constructor constructor : constructors) {
            //修饰符、构造器名称、构造器形参列表  、抛出异常列表
            int modifiers = constructor.getModifiers();
            System.out.println("构造器的修饰符:" + Modifier.toString(modifiers));
            
            String name2 = constructor.getName();
            System.out.println("构造器名:" + name2);
            
            //形参列表
            System.out.println("形参列表:");
            Class[] parameterTypes = constructor.getParameterTypes();
            for (Class parameterType : parameterTypes) {
                System.out.println(parameterType);
            }
            
            //异常列表
            System.out.println("异常列表:");
            Class<?>[] exceptionTypes = constructor.getExceptionTypes();
            for (Class<?> exceptionType : exceptionTypes) {
                System.out.println(exceptionType);
            }
        }
        System.out.println("=--------------------------------");
        //(8)方法们
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method method : declaredMethods) {
            //修饰符、返回值类型、方法名、形参列表 、异常列表 
            int modifiers = method.getModifiers();
            System.out.println("方法的修饰符:" + Modifier.toString(modifiers));
            
            Class<?> returnType = method.getReturnType();
            System.out.println("返回值类型:" + returnType);
            
            String name2 = method.getName();
            System.out.println("方法名:" + name2);
            
            //形参列表
            System.out.println("形参列表:");
            Class[] parameterTypes = method.getParameterTypes();
            for (Class parameterType : parameterTypes) {
                System.out.println(parameterType);
            }
            
            //异常列表
            System.out.println("异常列表:");
            Class<?>[] exceptionTypes = method.getExceptionTypes();
            for (Class<?> exceptionType : exceptionTypes) {
                System.out.println(exceptionType);
            }
        }
        
    }
}

创建任意引用类型的对象

两种方式:

1、直接通过Class对象来实例化(要求必须有无参构造)

2、通过获取构造器对象来进行实例化

方式一的步骤:

(1)获取该类型的Class对象(2)创建对象

    @Test
    public void test2()throws Exception{
        Class<?> clazz = Class.forName("com.atguigu.test.Student");
        //Caused by: java.lang.NoSuchMethodException: com.atguigu.test.Student.<init>()
        //即说明Student没有无参构造,就没有无参实例初始化方法<init>
        Object stu = clazz.newInstance();
        System.out.println(stu);
    }
    
    @Test
    public void test1() throws ClassNotFoundException, InstantiationException, IllegalAccessException{
//        AtGuigu obj = new AtGuigu();//编译期间无法创建
        
        Class<?> clazz = Class.forName("com.atguigu.test.AtGuigu");
        //clazz代表com.atguigu.test.AtGuigu类型
        //clazz.newInstance()创建的就是AtGuigu的对象
        Object obj = clazz.newInstance();
        System.out.println(obj);
    }

方式二的步骤:

(1)获取该类型的Class对象(2)获取构造器对象(3)创建对象

如果构造器的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)

示例代码:

public class TestNewInstance {
    @Test
    public void test3()throws Exception{
        //(1)获取Class对象
        Class<?> clazz = Class.forName("com.atguigu.test.Student");
        /*
         * 获取Student类型中的有参构造
         * 如果构造器有多个,我们通常是根据形参【类型】列表来获取指定的一个构造器的
         * 例如:public Student(int id, String name) 
         */
        //(2)获取构造器对象
        Constructor<?> constructor = clazz.getDeclaredConstructor(int.class,String.class);
        
        //(3)创建实例对象
        // T newInstance(Object... initargs)  这个Object...是在创建对象时,给有参构造的实参列表
        Object obj = constructor.newInstance(2,"张三");
        System.out.println(obj);
    }
    
}

操作任意类型的属性

(1)获取该类型的Class对象
Class clazz = Class.forName("com.atguigu.bean.User");

(2)获取属性对象
Field field = clazz.getDeclaredField("username");

(3)设置属性可访问

field.setAccessible(true);

(4)创建实例对象:如果操作的是非静态属性,需要创建实例对象
Object obj = clazz.newInstance();

(4)设置属性值

field.set(obj,"chai");
(5)获取属性值
Object value = field.get(obj);

如果操作静态变量,那么实例对象可以省略,用null表示

示例代码:

public class TestField {
    public static void main(String[] args)throws Exception {
        //1、获取Student的Class对象
        Class clazz = Class.forName("com.atguigu.test.Student");
        
        //2、获取属性对象,例如:id属性
        Field idField = clazz.getDeclaredField("id");
        
        //3、如果id是私有的等在当前类中不可访问access的,我们需要做如下操作
        idField.setAccessible(true);
        
        //4、创建实例对象,即,创建Student对象
        Object stu = clazz.newInstance();
                
        //5、获取属性值
        /*
         * 以前:int 变量= 学生对象.getId()
         * 现在:Object id属性对象.get(学生对象)
         */
        Object value = idField.get(stu);
        System.out.println("id = "+ value);
        
        //6、设置属性值
        /*
         * 以前:学生对象.setId(值)
         * 现在:id属性对象.set(学生对象,值)
         */
        idField.set(stu, 2);
        
        value = idField.get(stu);
        System.out.println("id = "+ value);
    }
}

调用任意类型的方法

(1)获取该类型的Class对象
Class clazz = Class.forName("com.atguigu.service.UserService");
(2)获取方法对象
Method method = clazz.getDeclaredMethod("login",String.class,String.class);
(3)创建实例对象
Object obj = clazz.newInstance();
(4)调用方法
Object result = method.invoke(obj,"chai","123);

如果方法的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)

如果方法是静态方法,实例对象也可以省略,用null代替

示例代码:

public class TestMethod {
    @Test
    public void test()throws Exception {
        // 1、获取Student的Class对象
        Class<?> clazz = Class.forName("com.atguigu.test.Student");
        
        //2、获取方法对象
        /*
         * 在一个类中,唯一定位到一个方法,需要:(1)方法名(2)形参列表,因为方法可能重载
         * 
         * 例如:void setName(String name)
         */
        Method method = clazz.getDeclaredMethod("setName", String.class);
        
        //3、创建实例对象
        Object stu = clazz.newInstance();
        
        //4、调用方法
        /*
         * 以前:学生对象.setName(值)
         * 现在:方法对象.invoke(学生对象,值)
         */
        method.invoke(stu, "张三");
        
        System.out.println(stu);
    }
}

注解

注解概述

定义:Java的注解(Annotation)是代码中的元数据,用于为代码元素(如类、方法、变量等)提供额外的信息,这些信息可以被编译器或运行时环境读取和处理。

元数据(Metadata)是关于数据的数据,具体来说,它是用来描述类、方法、字段等的信息的数据。元数据可以包含类的名称、父类、接口实现、方法的参数和返回类型、字段的类型和访问修饰符等。这些信息的存在使得程序可以在运行时动态地获取和使用这些信息,从而实现更加灵活和智能的编程。

作用

  • 编写文档:通过代码里标识的注解生成文档【例如,生成文档doc文档 param】
  • 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【例如,Override】
  • 取代配置文件:减少开发的代码量

常见注解

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

自定义注解

定义格式
public @interface 注解名称{
    属性列表;
}

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

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

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

注解的属性
  1. 属性的作用

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

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

    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类型,枚举类型,注解类型。
    • 以上所有类型的一维数组。

使用自定义注解

在程序中使用(解析)注解的步骤(获取注解中定义的属性值):

  1. 获取注解定义的位置的对象 (Class,Method,Field)
  2. 获取指定的注解 getAnnotation(Class)
  3. 调用注解中的抽象方法获取配置的属性值

使用格式:

@注解名(属性名=属性值,属性名=属性值,属性名=属性值...)
定义注解
  1. 定义一个注解:Book

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

使用注解

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

    }
}

使用注意事项

  • 如果属性有默认值,则使用注解的时候,这个属性可以不用赋值。
  • 如果属性没有默认值,那么在使用注解时一定要给属性赋值。
特殊属性value
  1. 当注解中只有一个属性且名称是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(){
    
        }
    }
  2. 如果注解中除了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属性名不能省略了。

注解之元注解

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

  • @Target
  • @Retention

元注解之@Target

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

    • 可选的参数值在枚举类ElemenetType中包括:
 TYPE: 用在类,接口上
 FIELD:用在成员变量上
 METHOD: 用在方法上
 PARAMETER:用在参数上
 CONSTRUCTOR:用在构造方法上
 LOCAL_VARIABLE:用在局部变量上

元注解之@Retention

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

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

注解解析

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

与注解解析相关的接口

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

获取注解数据的原理

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

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

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

使用反射获取注解的数据

需求说明
  1. 定义注解Book,要求如下:

    • 包含属性:String value() 书名
    • 包含属性:double price() 价格,默认值为 100
    • 包含属性:String[] authors() 多位作者
    • 限制注解使用的位置:类和成员方法上
    • 指定注解的有效范围:RUNTIME
  2. 定义BookStore类,在类和成员方法上使用Book注解
  3. 定义TestAnnotation测试类获取Book注解上的数据
代码实现

注解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 {
}

TestAnnotation类

public class TestAnnotation {
    public static void main(String[] args)  throws Exception{
        System.out.println("---------获取类上注解的数据----------");
        test();
    }

    /**
     * 获取BookStore类上使用的Book注解数据
     */
    public static void test(){
        // 获得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()));
        }
}

标签
没有标签

© 著作权归作者所有

本文由 趣代码Blog 创作,采用 知识共享署名4.0 国际许可协议进行许可,本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。

评论关闭