Java学习笔记_11_注解和反射

注解Annotation

什么是注解

  • 和注释comment一样,Annotation可以对程序做出解释,不过Annotation可以被其他程序(如compiler等)读取
  • 注解的格式一般为:@注释名 当然还可以加上一点参数值(@)

内置注解

在JavaSE中内置了三个标准注解。下面就简单介绍下

  • @Override:表示方法重载
  • @Deprecated:表示方法已弃用(过时),不建议使用,可能会有更好的替代,但不是不能使用
  • @SuppressWarnings:抑制警告,需要传递已定义好的参数
    • @SuppressWarnings(“all”)
    • @SuppressWarnings(“unchecked”)
    • @SuppressWarnings(value={“unchecked”,”“deprecation”})

元注解

meta-annotation:元注解,负责注解其他注解,在Java中定于了标准的元注解(在java.lang.annotation包中可以找到)

  • @Target:指示注释类型适用的上下文(即被描述的注解可以用在何处)
  • @Retention:表示要保留带注释类型的注释多长时间(一般有三个级别:source、class、runtime,这三个级别的生命周期依次递增)
  • @Inherited:表示一个注释类型是自动继承的
  • @Documented:表明一个类型的注释被javadoc默认文件

自定义注解

  • 格式:@interface 注解名{定义内容}

    • 定义内容中的每一个方法实际上是申明了一个配置参数
    • 方法的名称就是参数的名称,返回值类型就是参数的类型
    • 注解元素必须有值,可以用default来申明参数的默认值
  • 【代码示例:自定义注解】

    package com.lee.Lesson01;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    public class TestCustomizedAnnotation {
        @MyAnnotation(age = 18, name = "lee", id = 1)
        public void test() {
            
        }
        
        @Target(value = {ElementType.METHOD})
        @Retention(value = RetentionPolicy.RUNTIME)
        @interface MyAnnotation {
            //注解的参数   参数类型+参数名
            String name() default "";
    
            int age() default 0;
    
            int id() default -1;
        }
    }
    
    

反射读取注解

  • 显然通过反射机制读取注解前,我们需要 定义注解,并在类中使用注解(后面会详细讲述反射)

    Class.forName("完整包名").getAnnotations();
    //获取该类的注解
    

    当然还有其他其他方法,详情可见官方API(Class)

反射Reflection

说在前面

提及反射机制的话,不可避免地会提及到静态语言和动态语言

  • 动态语言
    • 运行时可以改变其结构的语言
    • 常见的动态语言:C#、JavaScript、PHP、Python
  • 静态语言
    • 运行时结构不可变的语言
    • Java不是动态语言,但由于反射机制的存在,让Java获得类似动态语言的特性,使得Java编程更加灵活

什么是反射

  • 在类加载完毕后,会在堆内存中生成一个Class类型的对象(一个类只有一个对象),这个对象包含了类的全部的结构信息。通过这个对象我们可以看到类的结构,这就是反射机制
  • 就像是一面镜子,我们每个人(类)都会在镜子中生成一个镜像(Class对象),我们想获取一个人的信息(结构信息),正常的话可以通过渠道问本人(正常方式),也可以透过镜子中该人的反射镜像获取信息(反射方式)
    • 正常方式:通过渠道(引入需要的包类名,实例化)找到人在那,就可以取得想要的信息(获得实例化对象)
    • 反射方式:先想办法得到镜像(对象实例化,然后使用方法获得Class模板),在镜像中找想要的信息(获得完整的包类名称)

反射机制的功能

  • 前面提到过,我们通过反射可以获取任何类的内部信息,那么得到这些信息后我们可以做点啥了?

    • 在运行时判断任意一个对象所属的类
    • 在运行时构造任意一个类的对象
    • 在运行时判断任意一个类所具有的Field和Method
    • 在运行时获取泛型信息
    • 在运行时处理注解
    • 生成动态代理
    • 在运行时调用任意一个对象的Field和Method
  • 显然反射机制的优点在于可以动态的创建对象和编译,但是这类操作有点迂回的意思,所以在性能上会有一定的影响

  • 如何用反射:当然是官方文档了,java.lang.reflect罗列了我们所用到的Java反射类

Class类

  • 上面我们提到过每个类都有一个Class对象,这个Class对象保存了类的全部结构信息,那么什么是Class类了?

    • 解释有点套娃的意思,Class类是一个描述类的类,且Class对象只能由系统建立对象(一个加载的类在JVM中只会一个Class实例)
  • 为什么要讨论Class类了?

    • 因为通过Class类可以完整地得到一个类中地所有被加载的结构,它是Reflection的根源,只有得到相对应的Class对象,我们才能动态加载运行的类
  • 如何获取Class对象?

    • 【代码示例:获取Class对象的方法】

      package com.lee.Lesson01;
      
      public class TestReflect02 {
          public static void main(String[] args) throws ClassNotFoundException {
      
              Person person = new Student();
              System.out.println("我是谁:" + person.name);
      
              //获得Class办法一:通过对象获取
              Class aClass = person.getClass();
              //获得Class办法二:通过字符串(包名+类名)获取
              Class<?> aClass1 = Class.forName("com.lee.Lesson01.Student");
              //获得Class办法三:通过类.class获得
              Class<Person> aClass2 = Person.class;
              //获得Class办法四:只针对内置的基本数据类型
              Class<Integer> aClass3 = Integer.TYPE;
      
              //获得父类类型
              Class<?> aClass4 = aClass1.getSuperclass();
      
          }
      
      }
      
      class Person {
          public String name;
      
          @Override
          public String toString() {
              return "Person{" +
                      "name='" + name + '\'' +
                      '}';
          }
      
          public Person(String name) {
              this.name = name;
          }
      
          public Person() {
          }
      }
      
      class Student extends Person {
          public Student() {
              this.name = "学生";
          }
      }
      
      class Teacher extends Person {
          public Teacher() {
              this.name = "老师";
          }
      }
      
      
  • 上面提供了几种获取Class对象的方法,那么问题来了,那些类型可以由Class对象了?

    • 答案很简单,类型能.class的,就有Class对象,分别有

      1. 类:class(外部类、成员(成员内部类、静态内部类)、局部内部类、匿名内部类)
      2. 接口:interface
      3. 数组(元素类型和维度一样的话,是同一个Class)
      4. 枚举:enum
      5. 注解:@interface
      6. 基本数据类型
      7. void
    • 【代码示例:那些类型有Class对象】

      package com.lee.Lesson01;
      
      import java.lang.annotation.ElementType;
      
      //看看那些类型可以有Class对象
      public class TestReflect03 {
          public static void main(String[] args) {
              Class<Object> aClass = Object.class;
              Class<Comparable> aClass1 = Comparable.class;
              Class<String> aClass2 = String.class;
              Class<Integer> aClass3 = int.class;
              Class<int[][]> aClass4 = int[][].class;
              Class<ElementType> aClass5 = ElementType.class;
              Class<Override> aClass6 = Override.class;
              Class<Void> aClass7 = void.class;
              Class<Class> aClass8 = Class.class;
      
      
              //只要元素类型和维度一样  就是同一个Class
              int[] a = new int[10];
              int[] b = new int[12];
              System.out.println(a.getClass() == b.getClass());
      
          }
      }
      
      

类的初始化(补充知识)

  • 当程序主动使用某个类时,如果该类还未加载到内存中,系统就会通过以下步骤将类初始化

    • 类的加载:将class文件字节码内容加载到内存中,并将这些数据转换成方法区的运行时数据,然后生成代表该类的Class对象(有ClassLoader负责,可以区官方文档查看详细解释)
    • 类的链接:将Java类的二进制代码合并到JVM的运行状态之中的一个过程
    • 类的初始化:执行类加载器(构造类信息)方法的过程
  • 什么时候会发生类的初始化了?

    • 类的主动引用(一定会)

      • main方法肯定会被初始化
      • new一个对象的时候
      • 调用类的静态成员和静态方法(final常量除外)
      • 反射调用类
      • 初始化一个类时,若父类没被初始化,优先初始化父类(先父后子)

      有主动引用,那么一定就有被动引用咯

    • 类的被动引用(不会初始化)

      • 通过入组定义类引用,不会触发此类的初始化
      • 引用常量不会触发此类的初始化(常量在链接阶段就存在常量池中了)
      • 当访问一个静态域时,只有真正声明这个域的类才会被初始化(如:通过子类引用父类的静态变量时,不会导致子类初始化)

Class对象能为我们做点啥

即反射机制能给我们做点啥,上面其实已经简答说到过,这里就说下对应的方法

  • 创建类的对象(找对象是有条件的)

    • 有车有房(类必须有一个无参的构造器)

    • 户名归她(类的构造器访问权限得足够)

    • 记得有后(要想构造器中传递参数)

    • clazz.getDeclaredConstructor().newInstance();
      
    • clazz:Class对象

  • 调用指定的方法()

    1. clazz.getMethod(“指定方法名”)获得指定方法

    2. 若方法为private,需要调用setAccessible(true)方法,取消语言访问检查

    3. invoke激活方法

  • 操作泛型

    • 泛型回顾:Java采用泛型擦除的机制(一旦编译完成,和泛型有关的类型将全部擦除)引入泛型(Java中泛型仅给编译器使用),用来确保数据的安全性以及避免强转问题

    • 为了能够通过反射操作这些类型,Java新增了ParameterizedType、GenericArrayType、TypeVariable、WildcardType(详细见官方文档)

  • 操作注解

    • getAnnotation:如果存在指定类型的元素注解,则返回该元素的注解,否则为空
    • getAnnotations:返回该元素上的注解。如果该元素上没有注解,返回值是一个长度为0的数组

版权声明:本文为qq_38735990原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。