javaSE注释于反射

Annotation-注解

1、概述

Annotation 是 JDK 5.0 引入的技术。

  1. 作用

    • 对程序做出解释(该作用类似 注释-comment);
    • 可以被程序读取(如 编译器)。
  2. 格式:@注解名 (参数)

    // 实例
    @Override
    @SuppressWarnings(value = "unchecked")
    
  3. 使用范围

    • 在package、class、method、field等的上方使用;
    • 可以通过反射机制来访问。

2、内置注解

  1. @Override:重写超类方法;
  2. @Deprecated:方法不建议使用;
  3. @SuppressWarnings:抑制编译时的警告信息。

3、元注解

元注解 (meta-annotation) 负责注解其它的注解

Java 定义了 4 个元注解:

  1. @Documented:生成 JavaDoc 文档
  2. @Retention:保留级别(在哪里有效)
    • SOURCE:源码
    • CLASS:类
    • RUNTIME:运行时
  3. @Target:使用范围(在哪里使用)
    • TYPE:类类型,如 Interface、Class
    • FILED:成员变量
    • METHOD
    • PARAMETER
    • CONSTRUCTOR
    • LOCAL_VARIABLE
    • ANNOTATION_TYPE
    • PACKAGE
    • 1.8 引入
      • TYPE_PARAMETER
      • TYPE_USE
  4. @Inherited:子类可以继承父类中的该注解

4、自定义注解

使用 @interface 自定义注解,自动实现接口java.lang.annotation.Annotaion

  1. 声明格式

    @元注解
    public @interface 注解名{
        参数类型 参数名();
    }
    
  2. 方法,实际上是声明一个参数;

    • 方法名即参数名;
    • 方法返回值类型即参数返回值类型,且只能是基本类型
    • 可以通过 default 来声明默认值,通常用空串和 0;
  3. 如果只有一个参数,参数名为 value。在使用注解的时候可以省略 value=""

实例1.1

  • @Documented:无
  • @Retention:源码范围;
  • @Target:METHOD (方法);
  • @Inherited:无
  • 参数个数:2
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MyAnnotation {
    int id();
    String name();
}

测试

  1. 注解只能在方法上使用,在其它位置使用会报错

    • 在类上使用

      image-20211217210413695

    • 在本地变量使用

      image-20211217210604453

  2. 使用时必须给参数赋值,并且指明参数名(可以不按参数的声明顺序来赋值)

    • 不赋值 / 没有全部赋值

      image-20211217210854964

    • 没有指明参数名

      image-20211217211037400

实例1.2

  • 在实例1的基础上,设置参数默认值。
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface MyAnnotation {

    int id() default 0;
    String name() default "";
}

测试

  • 没有显式赋值的参数,为默认值

    image-20211217211453337

  • 给部分参数赋值

    image-20211217211508064

实例2

  • @Documented:有
  • @Retention:运行时范围;
  • @Target:TYPE (类型)、方法 (METHOD);(用数组把多个级别括起来)
  • @Inherited:有
  • 参数个数:1,以 value 作为属性名
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface MyAnnotation2 {

    String value();
}

测试

image-20211217212723530

  1. 在类、方法上方都可以使用;
  2. 使用时可以省略参数名:即无需指定 (value ="xxx") ,直接 ("xxx") 赋值。

Reflection-反射

1、概述

动态语言 & 静态语言

  • 动态语言:运行时可以改变结构,如 Object-C、C#、JS、PHP、Python;
  • 静态语言:相对于动态语言,运行时结构不可变,如 Java、C、C++。

反射

Java 可以称为准动态语言,反射机制使 Java 具有动态性。

image-20211220220022180

  • 使用 Reflection API 获取类的内部信息,操作对象的内部属性和方法;

  • 功能

    1. 运行时判断对象所属的类;

    2. 运行时构造类的对象;

    3. 运行时判断 / 调用类的成员变量和方法;

    4. 运行时获取泛型信息;

    5. 运行时处理注解;

    6. 生成动态代理;

      ...

  • 优缺点

    • 优点:动态创建对象和编译,体现出很大的灵活性;
    • 缺点:对性能有影响。反射是一种解释操作,即告诉 JVM 要做什么,这类操作慢于直接执行相同的操作。

主要API

  • java.lang.Class:类
  • java.lang.refect.Method:方法
  • java.lang.reflect.Field:成员变量
  • java.lang.reflect.Constructor:构造器

2、反射对象-Class类

一个类被加载之后,在堆内存的方法区中产生一个 Class实例

  1. 每个类的实例,都可以获取自己的 Class类;
    • Student类的实例可以通过 getClass()方法,获取 Class对象;
  2. 每个类只有一个 Class对象实例,且 Class 本身也是一个类;
    • 即使 Student类 有很多个实例:student1、student2等,其 Class对象 是唯一的;
  3. Class对象中包含了类的所有结构:
    • 成员变量、方法、构造器、父类、实现的接口、注解等;
  4. Class类 是 Reflection 的根源
    • 只有获取到 Class对象 才能动态加载和运行对应的类。

Class类的常用方法

  1. 获取Class对象:forName

  2. 获取父类Class对象:getSuperClass

  3. 获取实现的接口:getInterfaces

  4. 获取类加载器:getClassLoader

  5. 创建实例:newInstance

  6. 对象内部

    1. 获取构造器:getConstructors
    2. 获取方法:getMethod
    3. 获取属性:getDeclaredFields
    4. 获取类名:getName

获取Class实例

获取方式

  1. (已知类名):类名.class
  2. (已知类的实例):实例名.getClass()
    • getClass()方法 在 Object类 中定义,因此所有类都可以调用。
  3. (已知全类名):Class.forName( 全类名 )
  4. (已知子类Class对象):子类.getSuperclass()
    • 注:子类也可以通过以上方式,获取Class对象
  5. ClassLoader获取

e.g.

以 Person 类为例,其中 Student 是 Person 的子类

@Test
public void testGetInstance() throws ClassNotFoundException {
    Person p = new Person();
    // 1、已知类名
    Class<Person> class1 = Person.class;
    // 2、已知类的实例
    Class<? extends Person> class2 = p.getClass();
    // 3、已知全限类名
    Class<?> class3 = Class.forName("indi.jaywee.Person");
    // 4、已知子类Class:假设Student.class是已知的
    Class<? super Student> class4 = Student.class.getSuperclass();

image-20211221000816670

不同类型的Class实例

  1. 类 (class):Class本身、外部类、内部类、局部内部类、匿名列不累
  2. 接口 (interface)
  3. 数组:一维、二维、...
  4. 枚举 (enum)
  5. 注解 (annotation)
  6. 基本数据类型 (primitive type)
  7. void

e.g.

// 类
Class<Class> class1 = Class.class;
Class<Person> class11 = Person.class;
// 接口
Class<Comparable> class2 = Comparable.class;
// 数组
Class<int[]> class3 = int[].class;
Class<int[][]> class33 = int[][].class;
// 枚举
Class<ElementType> class4 = ElementType.class;
// 注解
Class<Override> class5 = Override.class;
// 基本数据类型
Class<Double> class6 = Double.class;
// void
Class<Void> class7 = void.class;

image-20211221005232180

3、JVM-类加载机制

简单介绍类加载机制,具体需要学习 JVM

Java内存

    • 存放 new 的对象和数组;
    • 线程共享;
    • 存放基本变量类型(包含基本类型的具体数值);
    • 存放引用对象的变量(包含该引用在堆中的具体地址);
  1. 方法区
    • 包含所有的 class 和 static 变量;
    • 线程共享。

类加载过程

当程序主动使用某个类时,如果该类还未被加载到内存中,系统会通过以下步骤处理类:加载、链接、初始化。

加载 (Load):

  • ClassLoader 将类的 class文件 加载到内存中,将静态数据转换成方法区的运行时数据结构,并在堆中生成该类对应的 java.lang.Class 对象,作为方法区中类数据的访问入口。

链接 (Link):将类的二进制代码合并到 JRE 中;

  • 验证:确保加载的类信息符合 JVM 规范,没有安全方面的问题;
  • 准备:在方法区中给类变量(static) 分配内存并设置默认初始值;
  • 解析:将常量池内的符号引用(常量名)替换为直接引用(地址);

初始化 (Initialize):执行类构造器的<clinit>()方法

  • <clinit>()的产生:在编译期自动收集并合并类中所有类变量的赋值动作和静态代码块中的语句;
  • 当初始化一个类时,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化;
  • JVM 会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步。

e.g.

class Demo {
    public Demo() {
        System.out.println("构造器初始化");
    }
    static {
        System.out.println("静态代码块初始化");
        m = 300;
    }
    static int m = 100;
}

// 测试
@Test
public void test() {
    Demo demo = new Demo();
    System.out.println(demo.m);
}
  1. 加载:ClassLoader 将 Demo.class 加载到内存中,生成对应的 Class对象;

  2. 链接:验证类信息、给类变量m分配内存并设置默认初始值0;

  3. 解析:<clinit>() 对类变量m的赋值动作、静态代码块中的语句,进行合并

    <clinit>(){
        System.out.println("静态代码块初始化");
        m = 300;
        m = 100;
    }
    

结论

  • 静态代码块和静态变量在类的初始化阶段就被加载,其加载的优先级高于构造器;
  • clinit()的生成和结果,可以理解为:
    • 静态变量的赋值顺序和结果,由代码中的声明前后顺序依次赋值。

类初始化

类的主动引用:触发类的初始化

  1. JVM 启动时,初始化 main 方法所在的类;
  2. new 一个类的对象;
  3. 调用类的静态方法和静态成员(final常量除外);
  4. 使用 java.lang.reflect 的方法对类进行反射调用;
  5. 类初始化时,如果其父类没有被初始化,先对父类初始化;

类的被动引用:不发生类初始化

  1. 访问静态域时,只有真正声明这个域的类才会被初始化;
    • 如:通过子类引用父类的静态变量,不会导致子类初始化;
  2. 访问常量不会触发此类的初始化:
    • 因为常量在链接阶段就存入调用类的常量池中了。
  3. 定义类引用的数组,不会触发此类的初始化;
    • 如:定义 Student数组,不会触发Student类的初始化;

类加载器-ClassLoader

作用

将类的 class文件 加载到内存中,将静态数据转换成方法区的运行时数据结构,并在堆中生成该类对应的 java.lang.Class 对象,作为方法区中类数据的访问入口。

类缓存

  • 标准的 JavaSE类加载器可以按要求查找类,某个类被加载后将维持加载(即缓存)一段时间;
  • JVM 的 gc机制 可以回收这些 Class对象;

等级

  1. Bootstrap ClassLoader:启动类(根)加载器
  2. EXT ClassLoader:扩展类加载器
  3. App ClassLoader:应用类(系统类)加载器
  4. Custom ClassLoader:自定义加载器

image-20211220171114248

4、获取运行时类的完整结构

Class对象中包含了类的所有结构,通过反射可以获取到这些结构。

e.g.

@Test
public void test() throws NoSuchFieldException, NoSuchMethodException {
    Class<Person> personClass = Person.class;

    /* 类名
        getName():全限类名
        getSimpleName():简单类名
     */
    String name = personClass.getName();
    String simpleName = personClass.getSimpleName();

    /* 成员变量
        getFields():所有public权限的成员变量
        getDeclaredFields():所有任意权限的成员变量
        getField():指定public权限的成员变量
        getDeclaredField():指定任意权限的成员变量
     */
    Field[] fields = personClass.getFields();
    Field[] declaredFields = personClass.getDeclaredFields();
    // Field field1 = personClass.getField("name");
    Field declaredField = personClass.getDeclaredField("name");

    /* 类的方法
        getMethods():所有public权限的方法:包括父类
        getDeclaredMethods():所有任意权限的方法:不包括父类
        getMethod():指定public权限的方法:包括父类(需要传入参数类型)
        getDeclaredMethod():指定任意权限的方法:包括父类(需要传入参数类型)

     */
    Method[] methods = personClass.getMethods();
    Method[] declaredMethods = personClass.getDeclaredMethods();
    Method setName = personClass.getMethod("setName", String.class);
    Method setAge = personClass.getDeclaredMethod("setAge", int.class);

    /* 构造器:同上
        getConstructors():所有public权限的构造器:包括父类
        getDeclaredConstructors():所有任意权限的构造器:不包括父类
        getConstructor():指定public权限的构造器:包括父类(需要传入参数类型)
        getDeclaredConstructor():指定任意权限的构造器:包括父类(需要传入参数类型)
     */
    Constructor<?>[] constructors = personClass.getConstructors();
    Constructor<?>[] declaredConstructors = personClass.getDeclaredConstructors();
    Constructor<Person> constructor = personClass.getConstructor(null);
    // Constructor<Person> declaredConstructor = personClass.getDeclaredConstructor(String.class);

}

Constructor-创建实例

通过Class对象的 newInstance() 方法,可以创建类的实例对象。

  1. 该方式默认使用类的无参构造器;
  2. 要求类必须有无参构造器,且访问权限足够。

通过构造器的 newInstance() 方法,也可以创建类的实例化对象

  1. 获取本类的指定形参的构造器:getDeclaredConstructor (Class<?>... parameterTypes);
  2. 实例化对象:调用构造器的 newInstance (Object ... initargs) 方法,传递参数。

e.g.

@Test
public void testGetInstance() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
    Class<?> personClass = Person.class;

    // 无参构造
    Person person1 = (Person) personClass.newInstance();
    // 无参构造
    Constructor<?> constructor = personClass.getDeclaredConstructor(null);
    Person person2 = (Person) constructor.newInstance(null);
    // 有参构造
    Constructor<?> constructor1 = personClass.getDeclaredConstructor(String.class, int.class);
    Person person3 = (Person) constructor1.newInstance("Jaywee", 7);

    System.out.println(person1);
    System.out.println(person2);
    System.out.println(person3);
}

Method-调用方法

  1. 获取本类的指定形参的方法:通过 Class对象的 getDeclaredMethod (Class<?>... parameterTypes) 方法获取;
  2. 调用方法:调用方法的 invoke (Object obj, Object... args) 方法
    • obj:被调用方法所属的对象;
    • args:方法参数;
    • 返回值:对应原方法的返回值,若原方法无返回值则为null;
  3. 注意
    • 原方法为静态方法,形参 Object obj 可为 null;
    • 原方法形参列表为空, Object[] args 可为 null;
    • 原方法权限为 private,需要先调用 setAccessible (true) 方法开启访问权限,才可访问 invoke() 方法。

e.g.

分别以 getter 和 setter 为例

@Test
public void testInvokeMethod() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
    Class<Person> personClass = Person.class;
    // 创建实例
    Person person = personClass.newInstance();
    // 获取并调用getter
    Method getAge = personClass.getDeclaredMethod("getAge", null);
    int age = (int) getAge.invoke(person);
    // 获取并调用setter
    Method setName = personClass.getDeclaredMethod("setName", String.class);
    setName.invoke(person, "Jaywee");
}

Field-获取成员变量

  1. 获取本类的指定成员变量:通过 Class对象的 getDeclaredField (String name) 方法获取;
  2. 调用成员变量的相关方法:
    • set (Object obj, Object value):相当于 obj.setXxx (value)
    • get (Object obj):相当于 obj.getXxx()
  3. 注意:如果成员变量的权限为 private,需要先调用 setAccessible (true) 方法开启访问权限,才可访问私有变量的方法。

e.g.

@Test
public void testGetField() throws IllegalAccessException, InstantiationException, NoSuchFieldException {
    Class<Person> personClass = Person.class;
    // 创建实例
    Person person = personClass.newInstance();
    // 获取name属性
    Field name = personClass.getDeclaredField("name");
    name.setAccessible(true);

    // 相当于调用person.setName("Jaywee")
    name.set(person,"Jaywee");
    // 相当于调用person.getName()
    Object o = name.get(person);
    System.out.println(o);
}

setAccessible()

Constructor、Method、Field对象都有 setAccessible() 方法

public void setAccessible(boolean flag)
  • 作用:启动或禁用访问安全检查;
  • true:允许访问,即禁用安全检查
    • 使得私有成员得以访问;
    • 提高反射效率,如果有使用到反射的代码需要被频繁使用,需设置 true;
  • false:禁止访问,即开启安全检查(默认值)
posted @   扬帆起航$  阅读(41)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示