JAVA笔记 :注解和反射

本文详细介绍了Java中的注解(Annotation)及其使用,包括内置注解、自定义注解和元注解,强调了注解在框架中的重要性。此外,还深入讲解了反射机制,包括如何获取类的反射对象、Class对象的特性、类的加载过程、通过反射创建对象及调用方法和属性。最后,提到了获取泛型信息和注解信息的方法,展示了注解在实际应用中的价值。
摘要由CSDN通过智能技术生成


一、注解

1.1 概述

  • 注解(Annotaion)是JDK5.0开始引入的技术
  • 可以用在 包、类、方法、属性等上面,相当于给它们添加了额外的信息。
  • 可以通过反射机制实现对注解信息的访问
  • 注解是所有框架的底层
  • 注解是给程序看的,注释是给程序员看的

1.2 内置注解

  • java.lang包下定义了3个注解

    1. @Override - 检查该方法是否是重载方法。如果发现其父类,或者是引用的接口中并没有该方法时,会报编译错误。

    2. @Deprecated - 标记过时方法。如果使用该方法,会报编译警告。

    3. @SuppressWarnings - 指示编译器去忽略注解中声明的警告。与前面两个注解有所不同,该注解需要添加一个参数才能正确使用,这些参数都是已经定义好了的,我们选择性的使用就好。

      @SuppressWarnings(“all”)
      @SuppressWarnings(“unchecked”)

      @SuppressWarnings(value={“unchecked”,“deprecation”})

1.3 自定义注解

  • 自定义一个注解

    @interface myAnnotation {
      String value();
    }
    
    1. 使用[权限修饰符] @interface自定义一个注解,自动继承java.lang.annotaion.Annotaion 接口

    2. String value();表面上是个方法,实际上是生命了一个配置参数,在使用时需要传值。

    3. 可以通过default给参数进行默认赋值,在使用时如果没有默认值则必须进行传值。

      @interface myAnnotation {
        String value() default "";
      }
      
    4. 只有一个成员时,一般参数名设置为value

  • 注解的使用和传值

    • 定义如下注解:

      @interface myAnnotation {
        int id();
        String name();
        String sex();
      }
      
    • 在一个类中使用注解

      @ myAnnotation(id = 1001,name="tom",sex = "man")
      class UserInfo{
        
      }
      
      								1. 使用注解时通过`key=value`的形式传值
      								1. 如果不指定`key`,则默认传递给注解中的`value`参数。
      

1.4 元注解

元注解就是作用在注解上的注解,定义在java.lang.annotaion包下。

  1. @Target:用开说明注解的使用范围。

    1. 源代码中的定义:

      public @interface Target {
          /**
           * Returns an array of the kinds of elements an annotation interface
           * can be applied to.
           * @return an array of the kinds of elements an annotation interface
           * can be applied to
           */
          ElementType[] value();
      }
      
    2. 源码中的ElementType是一个枚举类型,定义在java.lang.annotation中,此枚举类型的常量提供了注释可能出现在Java程序中的语法位置的简单分类。 这些常量用于Target元注释,以指定写入给定类型注释的合法位置。

      
      public enum ElementType {
       
          TYPE, // 类、接口、枚举类
       
          FIELD, // 成员变量(包括:枚举常量)
       
          METHOD, // 成员方法
       
          PARAMETER, // 方法参数
       
          CONSTRUCTOR, // 构造方法
       
          LOCAL_VARIABLE, // 局部变量
       
          ANNOTATION_TYPE, // 注解类
       
          PACKAGE, // 可用于修饰:包
       
          TYPE_PARAMETER, // 类型参数,JDK 1.8 新增
       
          TYPE_USE // 使用类型的任何地方,JDK 1.8 新增
       
      
    3. 使用

      @Target({ElementType.METHOD,ElementType.ANNOTATION_TYPE})
      @interface myAnnotation {
      
      }
      
  2. @Retention:描述注解保留的时间范围,一共有三种策略,定义在RetentionPolicy枚举中:

    
    public enum RetentionPolicy {
     
        SOURCE,    // 源文件保留
        CLASS,       // 编译期保留,默认值
        RUNTIME   // 运行期保留,可通过反射去获取注解信息
    }
    
    
    @Retention(RetentionPolicy.CLASS)
    public @interface ClassPolicy {
     
    
    
  3. Documented注解的作用是:描述在使用 javadoc 工具为类生成帮助文档时是否要保留其注解信息。

  4. @Inherited表示允许子类继承父类中的注解。

二、反射

2.1 概述

在程序运行中动态地获取类的相关属性,同时调用对象的方法和获取属性,这种机制被称之为Java反射机制。

  • 实现原理

Java反射是Java实现动态语言的关键,也就是通过反射实现类动态加载

静态加载: 在编译时加载相关的类,如果找不到类就会报错,依赖性比较强

动态加载:在运行时加载需要的类,在项目跑起来之后,调用才会报错,降低了依赖性

例子:静态加载,如下代码,如果找不到类的情况,代码编译都不通过

User user = new User();

而动态加载,就是反射的情况,是可以先编译通过的,然后在调用代码时候,也就是运行时才会报错

 Class<?> cls = Class.forName("com.example.core.example.reflection.User");Object obj = cls.newInstance();

java中的反射允许程序在执行期借助jdk中Reflection API来获取类的内部信息,比如成员变量、成员方法、构造方法等等,并能操作类的属性和方法

java中反射的实现和jvm和类加载机制有一定的关系,加载好类之后,在jvm的堆中会产生一个class类型的对象,这个class类包括了类的完整结构信息,通过这个class对象就可以获取到类的结构信息,所以形象地称之为java反射。

img

2.2 获取反射对象

  • 定义一个实体类

    public class User {
      private String name;
      private int age;
      private String sex;
    
      public User() {
      }
    
      public User(String name, int age, String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
      }
    
      @Override
      public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                '}';
      }
    }
    
    
  • 通过反射获取类的class对象

    public static void main(String[] args) throws ClassNotFoundException {
        Class c1 = Class.forName("com.a001.User");
        System.out.println(c1);//class com.a001.User
      }
    
    1. 一个类在内存中只有一个Class对象:

      Class c1 = Class.forName("com.a001.User");
      Class c2 = Class.forName("com.a001.User");
      System.out.println(c1 == c1);//true
      
    2. 类被加载后,;类的整个结果都在Class对象中。

  • Object类中定义了getClass方法,可以通过类的对象获取类的Class对象。

    public static void main(String[] args) throws ClassNotFoundException {
        Class c1 = Class.forName("com.a001.User");
        Class c2 = new User().getClass();
        System.out.println(c1 == c2);//true
      }
    
  • 每一个类都有class属性,通过类名.class属性获得Class的对象(比较高效)

    public static void main(String[] args) throws ClassNotFoundException {
        Class c1 = com.a001.User.class
      }
    
  • 基本包装类型包装类都有一个TYPE属性

    public static void main(String[] args) throws ClassNotFoundException {
        Class c1 = Intger.TYPE
      }
    

2.3 Class对象

  • Class对象存储了一个类的相关信息

  • Class本身也是个类,但对应的对象不是被new出来的,而是系统创建的。

  • 一个Class对象对应一个加载到JVM中的一个.class文件

  • 每个类的实例都划记得自己由哪个Class实例生成的,可以通过getClass方法生成。

  • Class类中提供的方法

    在这里插入图片描述

  • 那些类型可以有Class对象

    • 类:外部类、成员(成员内部类、静态内部类)、局部内部类、匿名内部类
    • 接口
    • 枚举
    • 注解
    • 基本数据类型
    • void
    		Class c1=Object.class;
        Class c2=Comparable.class;
        Class c3=int[][].class;
        Class c4= ElementType.class;
        Class c5=Target.class;
        Class c6=Integer.class;
        Class c7=void.class;
        Class c8=Class.class;
    

2.4 类的加载内存分析

存放new出来的对象和数组
可以被所有的线程共享,不会存放别的对象的引用
存放基本变量类型,包含这个基本类型的具体数值
对象的引用
可以被所有的线程共享
包含了所有的class和static变量
Java内存
方法区
  • 类的加载大致过程

    1. 类的加载(Load):将.class文件读入内存,并将这件静态数据转换成方法区的运行时数据,并为之创建一个Class对象代表这个类。
    2. 类的链接(Link):将类的二进制数据合并到JRE中。
      1. 验证:确保类的加载信息是否符合规范,没有安全方面的问题
      2. 准备:正式为类变量(static)分配内存并设置初始值,这些内存在方法区中分配
      3. 解析:虚拟机中常量池中的符号引用(常量名)转化为直接引用(地址)的过程。(即把所有常量的引用替换成真实的数据)
    3. 类的初始化(Initialize):JVM负责对类进行初始化。
      • 执行类构造器clinit()方法的过程,其方法体是所有类中的 static成员合并而成的(相同的代码会被替换)。
      • 若父类没有初始化,先触发父类的初始化。
      • 虚拟机保证clinit()方法在多线程中被正确加锁和同步。
  • 类初始化的时机

    • 类的主动引用(一定会发生类的初始化)
      • 当虚拟机启动,会发生main方法所在的类
      • new一个类的对象
      • 调用类的静态成员(除了final常量)和静态方法
      • 使用java.lang.reflect包中的方法对类进行反射调用
      • 初始化子类时,若父类没有初始化,先触发父类的初始化。
    • 类的被动引用(不会发生类的初始化)
      • 当访问一个静态域时,只有真正声明这个域的类才会初始化,如通过子类引用父类的静态变量,不会导致子类的初始化。
      • 通过数组定义类引用,不会触发类的初始化。
      • 引用常量不会触发此类的初始化(常量在链接阶段就存入常量池中了)
  • 类加载器

    ​ 类加载器的作用:将.class文件读入内存,并将这件静态数据转换成方法区的运行时数据,并为之创建一个Class对象代表这个类,==该对象作为作为访问方法区类数据的入口。==JVM预定义有三种类加载器,当一个 JVM启动的时候,Java开始使用如下三种类加载器:

    • 根类加载器(bootstrap class loader):它用来加载 Java 的核心类,由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。

      ​ 在lib目录下找到rt.jar,并用解压软件打开,里面java/long 目录下就是对应的java.long包,java目录下还能看到其他常用的类库(Math,io,time)。根类加载器就是加载这些包的。

    • 扩展类加载器(extensions class loader):它负责加载JRE的扩展目录,==lib/ext==或者由java.ext.dirs系统属性指定的目录中的JAR包的类。由Java语言实现,父类加载器为null

    • 系统类加载器(system class loader):被称为系统(也称为应用)类加载器,它负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH换将变量所指定的JAR包和类路径。程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都以此类加载器作为父加载器。由Java语言实现,父类加载器为ExtClassLoader

     // 测试某个类是由哪个类加载器加载的
        ClassLoader classLoader1 = ClassLoaderTest.class.getClassLoader();
        System.out.println(classLoader1);//jdk.internal.loader.ClassLoaders$AppClassLoader@251a69d7
        ClassLoader classLoader2 = Object.class.getClassLoader();
        System.out.println(classLoader2);//null 因为无法直接获取
    
    //获得系统加载类在那些路径加载类的
    System.out.println(System.getProperty("java.class.path"));
    

2.5 获取运行时类的完整结构

img

  • 定义一个类
class User {
  private String name;
  private int age;
  private String sex;

  public User() {}

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

  @Override
  public String toString() {
    return "User{" + "name='" + name + '\'' + ", age=" + age + ", sex='" + sex + '\'' + '}';
  }
}
  • 获取类对象
 Class c1 = User.class;
  • 获取类名
System.out.println(c1.getName()); // com.a001.User
System.out.println(c1.getSimpleName()); // User
  • 获取属性
Field[] field = c1.getFields();
System.out.println(Arrays.toString(field));
Field[] declaredFields = c1.getDeclaredFields(); // []
System.out.println(Arrays.toString(declaredFields));
// [private java.lang.String com.a001.User.name, private int com.a001.User.age, private
// java.lang.String com.a001.User.sex]
Field name = c1.getDeclaredField("name");
System.out.println(name); // private java.lang.String com.a001.User.name
获取所有的public属性,包括继承过来的获取在本类声明的所有属性
获取满足条件的所有属性getFields()getDeclaredFields()
根据属性名获取属性getField(String name)getDeclaredField(String name)
  • 获取方法
Method[] methods = c1.getMethods();
System.out.println(Arrays.toString(methods));
// [public java.lang.String com.a001.User.toString(), public final void
// java.lang.Object.wait(long,int) throws java.lang.InterruptedException, public final void
// java.lang.Object.wait() throws java.lang.InterruptedException, public final native void
// java.lang.Object.wait(long) throws java.lang.InterruptedException, public boolean
// java.lang.Object.equals(java.lang.Object), public native int java.lang.Object.hashCode(),
// public final native java.lang.Class java.lang.Object.getClass(), public final native void
// java.lang.Object.notify(), public final native void java.lang.Object.notifyAll()]
Method[] declaredMethods = c1.getDeclaredMethods();
System.out.println(Arrays.toString(declaredMethods));
//[public java.lang.String com.a001.User.toString()]
获取所有的public方法,包括继承过来的获取在本类声明的所有方法,包括重写的
获取满足条件的所有方法getMethods()getDeclaredMethods()
根据方法名获取方法getMethod(String name,*参数列表)getDeclaredMethod(String name,*参数列表)

注; 方法名+参数列表才能定位到具体的方法,尤其是对于重载的方法而言

public class Main {

  public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
    Class c1 = Test.class;
    System.out.println(c1.getDeclaredMethod("func", int.class, String.class));
    //public void com.a001.Test.func(int,java.lang.String)
  }
}

class Test {
  public void func(int a) {}

  public void func(String b) {}

  public void func(int a, String b) {}
}

  • 获得构造器
获取在本类声明的所有的public构造器获取在本类声明的所有构造器
获取满足条件的所有方法getMethods()getDeclaredMethods()
根据方法名获取方法getMethod(String name,*参数列表)getDeclaredMethod(String name,*参数列表)
Class userClass = User.class;
System.out.println(Arrays.toString(userClass.getConstructors()));
//[public com.a001.User(), public com.a001.User(java.lang.String,int,java.lang.String)]
System.out.println(Arrays.toString(userClass.getDeclaredConstructors()));
//[public com.a001.User(), public com.a001.User(java.lang.String,int,java.lang.String)]

System.out.println(userClass.getDeclaredConstructor(String.class, int.class, String.class));
//public com.a001.User(java.lang.String,int,java.lang.String)

2.6 通过反射创建一个对象

  • newInstance()

    Class userClass = User.class;
    // 创建一个对象
    User user1 = (User) userClass.newInstance();
    System.out.println(user1);//User{name='null', age=0, sex='null'}
    /*
    分析:1. newInstance通过调用无参构造器创建了对象
         2. 该方法已经弃用
     */
    
  • 通过获得类的构造器来创建对象

     Class userClass = User.class;
        // 获取指定的构造器
    Constructor declaredConstructor = userClass.getDeclaredConstructor(String.class, int.class, String.class);
    		//根据构造器创建对象
    User user2 = (User) declaredConstructor.newInstance("Tom", 20, "man");
    System.out.println(user2); //User{name='Tom', age=20, sex='man'}
    

2.7 通过反射调用对象的属性和方法

Class userClass = User.class;
User user = new User("tom", 12, "man");
// 获得对应的方法
Method toString = userClass.getDeclaredMethod("toString", null);
// 传入指定的对象和参数值(若有)调用方法  invoke(翻译:激活)
System.out.println(toString.invoke(user));
//User{name='tom', age=12, sex='man'}
Class userClass = User.class;
User user = new User("tom", 12, "man");
Field name = userClass.getDeclaredField("name");
name.setAccessible(true); // 关闭权限检测,可以对私有属性进行修改
name.set(user, "tom2");
System.out.println(name.get(user));

总结:

操作方法
通过构造器创建实例constructor.newInstance()
调用方法method.invoke()
获取、修改属性field.get() field.get() (对私有属性要关闭权限检查)

2.8 获取范型信息

  • Java中采用范型擦除机制,即范型仅在编译阶段使用,一旦编译完成后,所有和范型相关的类型全部擦除掉。

  • 为了在反射中操作这些范型型,java新增了ParameterizedType,GenericArrayTypeTypeVariableWildcardType等操作范型的类:

public class Main {
  public static void main(String[] args) throws NoSuchMethodException {
    Class c1 = Main.class;
    // 获得方法
    Method test = c1.getMethod("Test", Map.class, List.class);
    System.out.println(test);
    // public static java.util.ArrayList com.a001.Main.Test(java.util.Map,java.util.List)
    /*
    分析:修饰符  public static
         返回值: java.util.ArrayList
         方法名  com.a001.Main.Test
         参数列表  java.util.Map,java.util.List
     */
    // 获得方法中参数列表中的范型信息
    Type[] genericParameterTypes = test.getGenericParameterTypes();
    System.out.println(Arrays.toString(genericParameterTypes));
    // [java.util.Map<java.lang.String, java.lang.String>, java.util.List<java.lang.Integer>]

    // 获得具体的范型
    for (Type genericParameterType : genericParameterTypes) {
      // 判断获取的类型是不是参数化类型
      if (genericParameterType instanceof ParameterizedType) {
        // 强制转化成真实类型
        Type[] actualTypeArguments =
            ((ParameterizedType) genericParameterType).getActualTypeArguments();
        for (Type actualTypeArgument : actualTypeArguments) {
          System.out.println(actualTypeArgument);
          /*
          class java.lang.String
          class java.lang.String
          class java.lang.Integer
           */
        }
      }
    }
  }

  public static ArrayList<Character> Test(Map<String, String> map, List<Integer> list) {
    return new ArrayList<Character>();
  }
}

2.9 获取注解信息

  • 定义注解

    
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface TableUser {
        String value();
    }
    
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface FiledUser {
        String colName();
        String type();
        int length();
    }
    
  • 定义类,并使用注解

    @TableUser("db_user")
    public class User {
        @FiledUser(colName = "db_id",type = "int",length =4)
        private int id;
        @FiledUser(colName = "db_name",type = "varchar",length =3)
        private  String name;
        @FiledUser(colName = "db_age",type = "int",length =2)
        private int age;
    
        public User() {
        }
    
        public User(int id, String name, int age) {
            this.id = id;
            this.name = name;
            this.age = age;
        }
    }
    
    
  • 获取注解信息

    public class Main {
      public static void main(String[] args) throws NoSuchFieldException {
        Class classUser = com.a001.User.class;
    
        // 获得类上的注解
        Annotation[] annotations = classUser.getAnnotations();
        System.out.println(Arrays.toString(annotations));
        // [@com.a001.TableUser("db_user")]
    
        // 获得指定的注解
        TableUser annotation = (TableUser) classUser.getAnnotation(TableUser.class);
        // 获取注解中对应的valud值
        System.out.println(annotation.value()); //db_user
    
        //获取属性上的注解
        //1. 获取属性
        Field id = classUser.getDeclaredField("id");
        // 2. 根据属性获得对应的注解
        FiledUser annotation1 = id.getAnnotation(FiledUser.class);
        System.out.println(annotation1.colName());
        System.out.println(annotation1.type());
        System.out.println(annotation1.length());
        /*
        db_id
        int
         4
         */
      }
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bestwangyulu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值