• 第二十三章 反射和注解


    1. 反射

            Java的反射机制是指在Java程序运行的过程中,对于任何一个类,都可以获得这个类的所有属性和方法,对于给定的一个对象, 都能够调用它的任意一个属性和方法,这种动态获取类的内容以及动态调用对象的方法称为反射机制。

            反射机制允许在对类未知的情况下,获取类相关信息的方式变得更加多样灵活,调用类中相应方法,是Java增加其灵活性与动态性的一种机制。

    2. 反射的常用类

    2.1 Class类(类的类型对象)

    Class 类的实例表示正在运行的Java程序中的类和接口;

    Class 没有公共构造方法,Class对象是在加载类时由Java虚拟机以及通过调用类加载器中的defineClass方法自动构造的。

     java代码在执行中有三个阶段:

    ①源代码(source)阶段;

    ②字节码(class)阶段,在源代码编译之后;

    ③运行阶段(runTime)阶段。

    2.1.1 获取Class对象的三种方式

    ①Class.forName("全类名"):把字节码文件加载到内存

    1. System.out.println("=====通过反射器加载类获取类的类型=====");
    2. Class clz2 = Class.forName("com.wen.entity.Student");

     ②类名.class:通过类名的class属性获取

    1. System.out.println("=====通过类型属性获取类的类型=====");
    2. Class clz1 = Student.class;

     ③对象.getClass():通过对象的getClass()获取

    1. System.out.println("=====通过实例对象的getClass获取类的类型=====");
    2. Class clz3 = new Student().getClass();
    3. System.out.println(clz3);

     拓展:双亲委派模型

            ①启动类加载器(Bootstrap ClassLoader),是属于虚拟机自身的一部分,用C++实现,主要负责加载\lib 目录中或者被 -Xbootclasspath 指定的路径中的且文件名是被虚拟机识别的文件,它等于所有类加载器的父亲;

            ②扩展类加载器(Extension ClassLoader),是Java实现的,独立于虚拟机,主要负责加载\lib\ex 目录中或者被 java.ext.dirs 系统变量所指定的路径的类库 ;

            ③应用程序类加载器(Application ClassLoader),Java实现的,独立于虚拟机,主要负责加载用户路径(classPath)上的类库,如果没有实现自定义的类加载器那么这个就是程序中默认的加载器。

    原理: 当一个类加载器收到类加载任务时,会先交给自己的父加载器去完成,因此最终加载任务都会传递到最顶层的BootstrapClassLoader,只有当父类加载器无法完成加载任务时,才会尝试自己来加载。

            具体:根据双亲委派模式,在加载类文件的时候,子类加载器首先将加载请求委托给它的父加载器,父加载器会检测自己是否已经加载过类,如果已经加载则加载过程结束,如果没有加载的话则请求继续向上传递直Bootstrap ClassLoader。请求向上委托过程中,如果始终没有检测到该类已经加载,则Bootstrap ClassLoader开始尝试从其对应路劲中加载该类文件,如果失败则由子类加载器继续尝试加载,直至发起加载请求的子加载器为止。

            采用双亲委派模式可以保证类型加载的安全性,不管是哪个加载器加载这个类,最终都是委 托给顶层的BootstrapClassLoader来加载的,只有父类无法加载自己才尝试加载,这样就可以保证任何的类加载器最终得到的都是同样一个Object对象。

    2.1.2 Class类的常用方法

    getName():以String的形式返回此Class对象所表示的实体(类,接口,数组类,基本类型或者void)名称;
    1. System.out.println("=====类名=====");
    2. Class clz = Class.forName("com.wen.entity.Student");
    3. System.out.println(clz.getName());
    getSimpleName():返回源代码中给出的基础类的简称;
    System.out.println(clz.getSimpleName());

    newInstance():创建此Class对象所表示的类的一个新实例

    与new()的区别:

            弱类型,效率低,只能调用无参构造,返回的是Object类型

    1. System.out.println("=====创建新实例对象=====");
    2. Object o = clz.newInstance();
    3. System.out.println(o);

     forName(String className):返回与带有给定字符串名的类或接口相关联的Class对象

     getConstructors():返回一个包含某些Constructor对象的数组,是次Class对象所表示的类的所有公共构造方法

    1. System.out.println("======类中的构造器=======");
    2. Constructor[] constructors = clz.getConstructors();
    3. for (Constructor c: constructors) {
    4. System.out.println(c);
    5. }

     getDeclaredField(String name) :返回一个Field对象,反映此 Class 对象所表示的类或接口的所有成员字段

    1. System.out.println("====student中的name属性(字段)====");
    2. Field field = clz.getDeclaredField("name");
    3. System.out.println(field);

     getDeclaredFields() :返回一个包含某些 Field 对象的数组,这些对象反映此 Class 对象所表示的类或接口的所有可访问字段

    1. System.out.println("=====类的所有可访问字段======");
    2. Field[] declaredFields = clz.getDeclaredFields();
    3. for (Field f:declaredFields) {
    4. System.out.println(f);
    5. }
    getDeclaredMethod(String name, Class... parameterTypes) : 返回一个 Method对象,它反映此 Class 对象所表示的类或接口的指定成员方法;有参要带参
    1. System.out.println("===类中的eat方法===");
    2. Method eat = clz.getDeclaredMethod("eat", String.class);
    3. System.out.println(eat);
    getDeclaredMethods() :返回一个包含某些 Method 对象的数组,这些对象反映此 Class对象表示的类或接口声明的所有方法,包含私有方法
    1. System.out.println("=====类中的所有方法=====");
    2. Method[] declaredMethods = clz.getDeclaredMethods();
    3. for (Method m:declaredMethods) {
    4. System.out.println(m);
    5. }
    1. System.out.println("======类中的public方法=======");
    2. Method[] methods = clz.getMethods();
    3. for (Method m: methods) {
    4. System.out.println(m);
    5. }

    2.2 Method类

            Method提供关于类或接口上单独某个方法(以及如何访问该方法)的信息,所反映的方方法可能是类方法或实例方法(包括抽象方法)

    getName() :以String形式返回此 Method 对象表示的方法名称;

    getParameterTypes():按照声明顺序返回 Class 对象的数组,这些对象描述了此Method 对象所表示的方法的形参类型;
    getReturnType() :返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型;
    invoke(Object obj, Object... args):对带有指定参数的指定对象调用由此 Method 对象表示的基础方法。
    1. Student student = new Student();
    2. Class aClass = Class.forName("com.wen.entity.Student");
    3. Method setName = aClass.getDeclaredMethod("setName", String.class);
    4. Class[] parameterTypes = setName.getParameterTypes();
    5. for (Class c:parameterTypes) {
    6. System.out.println(c);
    7. }
    8. System.out.println(setName.getName()+" "+setName.getReturnType());
    9. //调用方法
    10. setName.invoke(student,"张三");
    11. System.out.println(student);

    2.3 Field类

            Field提供有关类或接口的单个字段的信息,以及对它的动态访问权限。

    get(Object obj) :返回指定对象上此 Field 表示的字段的值;
    set(Object obj, Object value) :将指定对象变量上此Field对象表示的字段设置为指定的新值;
    getName():返回此 Field 对象表示的字段的名称
    1. Student student1 = new Student("张三","男",20);
    2. Field name = Student.class.getDeclaredField("name");
    3. System.out.println(name.getName());
    4. //设置可以访问私有字段
    5. name.setAccessible(true);
    6. System.out.println(name.get(student1));
    7. name.set(student1,"王五");
    8. System.out.println(name.get(student1));

    3. 通过反射实现对象的创建

    1. public class Test3 {
    2. public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
    3. Properties properties = new Properties();
    4. InputStream resourceAsStream = Test3.class.getClassLoader().getResourceAsStream("stu.properties");
    5. //将字节流转为字符流并设置编码格式,与properties配置文件的编码格式一致
    6. properties.load(new InputStreamReader(resourceAsStream,"utf-8"));
    7. //获取文件中的配置
    8. String cls = properties.getProperty("cls");
    9. String f1 = properties.getProperty("f1");
    10. String f2 = properties.getProperty("f2");
    11. String f3 = properties.getProperty("f3");
    12. //加载类
    13. Class c = Class.forName(cls);
    14. Object o = c.newInstance();
    15. //获取特定字段类型
    16. Field name = c.getDeclaredField("name");
    17. Field sex = c.getDeclaredField("sex");
    18. Field age = c.getDeclaredField("age");
    19. name.setAccessible(true);
    20. sex.setAccessible(true);
    21. age.setAccessible(true);
    22. //设置数据
    23. name.set(o,f1.split(":")[1]);
    24. sex.set(o,f2.split(":")[1]);
    25. age.set(o,Integer.valueOf(f3.split(":")[1]));
    26. System.out.println(o);
    27. }
    28. }

    4. 注解

    注解(Annotation),使用注释的方式加入一些程序信息;

    java.lang.annotation.Annotation接口是所有的Annotation都必须实现的接。

    4.1 系统内建的三个注解

    @Override:方法重写的注解

    @Deprecated:不赞成使用的注解

    @SuppressWarnings:压制安全警告的注解

    1. @Override
    2. public String toString() {
    3. return "Student{" +
    4. "name='" + name + '\'' +
    5. ", sex='" + sex + '\'' +
    6. ", age=" + age +
    7. '}';
    8. }

     使用@SuppressWarnings之前:

    使用之后:

     

    @SuppressWarnings中的关键字
    关键字描述
    deprecation使用了不赞成使用的类或者方法时的警告
    unchecked执行了未检查的转换时的警告
    fallthrough当switch程序块直接通往下种情况而没有break时的警告
    path在类路径,源文件路径等中又不存在的路径时警告
    serial当在可序列化的类上缺少serialVersionUID时的警告
    finally在任何finally子句不能正常完成时的警告
    all关于以上所有情况的警告

    4.2 系统元注解

            用于标记注解的注解;通过元注解,可以标记我们自定义注解使用目标(类,属性,方法,构造,局部变量...),还可以标记注解的保留阶段(源代码,字节码,运行时)

    元注解说明
    @Target用于指定被修饰的Annotation可用于什么地方
    @retention表示需要在什么级别/阶段保存该注解信息
    @Documented将此注解包含在JavaDoc中
    @Inherited允许子类继承父类中的注解

    4.2.1 @Target目标设定

            可用的ElementType参数:

    类型说明
    ANNOTATION_TYPE指定该策略的Annotation只能修饰Annotation
    CONSTRUCTOR只能修饰构造器
    FIELD只能修饰成员变量
    LOCAL_VARIABLE只能修饰局部变量
    METHOD只能修饰方法定义
    PACKAGE只能修饰包定义
    PARAMETER只能修饰参数
    TYPE可以修饰类,接口或枚举类型

     4.2.2 @Retention和RetentionPolicy

            定义一个注解(Annottion)的保存范围:

    三个范围:

            source,class,runtime

    4.3 自定义注解

    语法:

    public @interface 注解名{

            数据类型  变量名();

    数据类型:只能是8中基本类型,String,基本类型的数组,枚举

    注解分类:

    ①标记注解:没有成员变量的注解类型,这种注解仅仅提供存在与否的标识信息,如:@Override;

    ②元数据注解:包含成员变量注解类型,这种注解可以接收更多的元数据;

    1. //自定义的注解
    2. public @interface MyAnnotation {
    3. int num() default 100;
    4. String value();
    5. String value1();
    6. String[] values() default {"a","b"};
    7. }

     测试注解是否存在:

    1. public class Test5 {
    2. public static void main(String[] args) {
    3. //判断目标类是否有对应的注解
    4. //要使得运行时对应注解依然有效,则需要在注解上设置元注解:@Retention(RetentionPolicy.RUNTIME)
    5. boolean b = Student.class.isAnnotationPresent(MyAnnotation.class);
    6. System.out.println(b);
    7. }
    8. }
    1. @Target(TYPE)
    2. @Retention(RetentionPolicy.RUNTIME)
    3. public @interface MyAnnotation {
    4. int num() default 20;
    5. String value();
    6. String value1();
    7. String[] values() default {"a","b"};
    8. }

    4.4 使用反射获取注解,设置对象值

    1. public class Test6 {
    2. public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException {
    3. Class stuClass = Class.forName("com.wen.entity.Student");
    4. if(stuClass.isAnnotationPresent(MyAnnotation.class)){
    5. MyAnnotation annotation = stuClass.getAnnotation(MyAnnotation.class);
    6. System.out.println(annotation.num());
    7. System.out.println(annotation.value());
    8. System.out.println(annotation.value1());
    9. Object o = stuClass.newInstance();
    10. Field name = stuClass.getDeclaredField("name");
    11. Field sex = stuClass.getDeclaredField("sex");
    12. Field age = stuClass.getDeclaredField("age");
    13. name.setAccessible(true);
    14. sex.setAccessible(true);
    15. age.setAccessible(true);
    16. name.set(o,annotation.value());
    17. sex.set(o,annotation.value1());
    18. age.set(o,annotation.num());
    19. String introduce = (String)stuClass.getDeclaredMethod("toString").invoke(o);
    20. System.out.println(introduce);
    21. }
    22. }
    23. }

  • 相关阅读:
    弘辽科技:拼多多商家如何自己提升销量?提升销量需要注意什么?
    每天技术扩展记录
    【Azure 存储服务】访问Azure Blob File遇见400-Condition Headers not support错误的解决之路
    快来跟我一起抢先看看未来世界的出行,体验未来城市吧~
    故障管理:谈谈我对故障的理解
    力扣每日练习3.14
    C语言【文件】
    tf.nest
    在Visual Studio中查看C项目使用的C语言版本
    最刁钻的阿里面试官总结的面试者常用面试题,看看你会哪些?
  • 原文地址:https://blog.csdn.net/m0_71674778/article/details/126267293