Java注解与反射

1. 注解

可以在包package,类class,方法method,属性field上使用,相当于添加了额外的辅助信息。可以通过反射机制实现对这些元数据的访问

1.1 内置注解

在java.lang包中包含了很多的注解,其中常用的三个如下:

  1. Override,只修饰方法,声明一个方法重写超类的方法。
  2. Deprecated,修饰方法、属性、类,表示它废弃不建议使用
  3. SupperessWarnings,都可以修饰,需要添加一个参数才可以使用。作用是抑制警告,参数定义好了。"all"所有警告,"unchecked"未检查类型转换的警告。这些参数可以单独使用,也可以组合。
    由于只定义了一个参数,且参数名为value,则在传入参数时可以省略 "参数="

1.2 元注解

作用是注解其他的注解,,在Java中定义了四个标准的meta-annotation类型,用来对其他annotation作说明。

  1. @Target:用于描述使用范围,参数传入ElementType.(TYPE,FIELD,METHOD...),可组合。
  2. @Retention:指定被注解的注解如何保留,描述声明周期。SOURCE<CLASS<RUNTIME
    • RetentionPolicy.SOURECE,保留在源代码,编译后的字节码不包含。
    • RetentionPolicy.CLASS,保留在编译后的字节码中,但在运行时不可访问。
    • RetentionPolicy.RUNTIME,保留在编译后的字节码,并在运行中可用过反射访问。

1.3 自定义注解

使用@interface声明即可,自动继承java.lang.annotation.Annotation接口。分析如下:

  1. @interface用来声明一个注解
  2. 其中的每一个方法实际上是声明了一个配置参数,方法的名词就是参数的名称
  3. 返回值类型就是参数的类型(基本类型,Class,String,已定义的枚举类型,注解类型,以上的一维数组)
  4. 使用default声明参数的默认值
  5. 如果只有一个参数成员,一般参数名为value,这样传参可省略名字。
  6. 定义注解的元素时,如果不指定默认值,那么这些元素在使用注解时是必须要提供值的。
@Myinnotation(hk=4)
public class test {
}
@Target({ElementType.FIELD,ElementType.TYPE})
@interface Myinnotation{
    String[] value() default {"hello","hk"};
    int hk();
}

2.反射

在加载完类后,在堆内存产生一个CLASS对象,通过这个对象可以看到类的结构,故称为反射。
反射机制是框架设计的灵魂。可以在运行时动态地获取类的信息并操作类或对象。
优点是灵活,缺点是性能差。通过反射可以实现如下功能:

  1. 动态创建对象,通过反射无需在编译时知道类的类型。
  2. 动态调用方法,反射可以调用方法包括私有方法。
  3. 获取和设置字段值,也包括私有
  4. 调用私有构造函数,从而创建实例
  5. 获取类的注解和注解信息。

2.1 常用API

  1. java.lang.Class,反射的核心类,代表一个类或接口的运行时信息。提供了许多方法来获取类的信息。类的名称、父类、实现的接口、字段、方法、构造函数等。
  2. java.lang.reflect.Field,表示类的字段(成员变量),提供了方法来获取和设置字段的值,获取字段的名称、类型、修饰符等。
  3. java.lang.reflect.Method,表示类的方法,提供了方法来调用类的方法,以及获取方法的名称、参数类型、返回类型等。
  4. java.lang.reflect.Constructor,表示类的构造方法,提供了方法创建类的实例,以及获取构造函数的参数类型,修饰符等。

2.2 五种class对象获取方法

  1. 通过new对象的.getClass方法
  2. 通过Class静态方法.forName()路径获得
  3. 通过类名.class方法获取
  4. 基本类型的包装类有一个TYPE属性可以通过该属性获得class对象
  5. 通过子类class对象的getSuperClass获取父类的对象。
public class FiveGetClass {
    public static void main(String[] args) throws ClassNotFoundException {
        Person person = new Person();
        //1. 通过对象的getClass
        System.out.println(person.getClass().hashCode());
        //2. 通过路径forName
        System.out.println(Class.forName("org.example.annotationandreflection.Person").hashCode());
        //3. 通过子类的class对象getSuperClass
        Class student = Class.forName("org.example.annotationandreflection.Student");
        System.out.println(student.getSuperclass().hashCode());
        //4. 通过类名.class
        System.out.println(Person.class.hashCode());
        //5. 包装类的Type
        Class type = Integer.TYPE;
        System.out.println(type);
    }
}
class Person{ }
class Student extends Person{ }

2.3 Class类的常用方法

  1. getName():获取类的全限定名。
  2. getSimpleName():获取类的简单名。
方法名 作用
Class getSuperClass()
Class[] getInterfaces()
ClassLoader getClassLoader()
Constructor[] getConstructors()
Method getMethod(String name,Class...) 获取指定的方法,有参数的话传入int.class,String.class等,之后使用method.invoke进行调用。
Field[] getDeclaredFields() 获取本类的所有字段包括private
Method[] getDeclaredMethods() 获取奔雷的所有方法。
newInstance() 通过默认构造函数创建类的实例,该方法要求类要有一个可访问的无参构造函数
boolean isInterface/Array/Enum 是否是什么类型

2.4 类加载内存分析

image

加载过程

  1. Load类的加载:将类的class文件读入内存,将静态数据转换成方法区的运行时数据结构,然后创建一个代表这个类的java.lang.Class对象。
  2. Link类的链接:验证类信息没有错误;准备,正式为类变量(static)分配内存并设置初始值。
  3. Initialize类的初始化:执行类的构造器方法(clinit),并执行静态变量赋值和静态代码块。
    • 虚拟机保证多线程的同步
    • 初始化一个类时,如果其父类还没有初始化,则会先触发父类的初始化。

2.5 什么时候发生类的初始化?

  1. 类的主动引用(一定发生类的初始化)
    • 虚拟机启动先初始化 main所在的类
    • new 一个类的对象
    • 调用类的静态成员(除了final常量)和静态方法
    • java.lang.reflect包的方法对类进行反射调用
    • 初始化一个类,父类没有初始化,先初始化父类
  2. 类的被动引用(不发生初始化)
    • 当访问静态域时,只有真正声明这个域的类会初始化。例如子类引用父类的静态变量,子类不会初始化。
    • 通过数组定义类引用,不会触发类的初始化
    • 引用常量不会触发类的初始化。(常量在链接阶段存入)
         //1.主动引用
        Son son=new Son();
        //2.反射也会产生主动引用
        Class.forName("com.kuang.reflection.Son");
        //不会产生类的引用的方法 ,子类继承父类的
         System.out.println(Son.b);
         Son[] array=new Son[5];

2.6 类加载器的作用

负责将类的字节码加载到内存,并转化为可执行的运行时对象。 采用双亲委派机制,当需要加载一个类时,类加载器首先会检查自身是否已加载过该类,如果没有,则委托给父类加载器进行加载。这种层次结构可以实现类的隔离和版本控制,防止如果有人想替换系统级别的类。

  1. 统类加载器(System Class Loader):也称为应用程序类加载器(Application Class Loader),是负责加载应用程序类路径(Classpath)上的类的加载器。它是 JVM 默认的类加载器,负责加载用户自定义的类和第三方类库等
  2. 扩展类加载器(Extension Class Loader):扩展类加载器是负责加载 Java 扩展目录(jre/lib/ext)下的类的加载器。它用于加载扩展类库,通常由 Java 扩展机制使用。
  3. 启动类加载器(Bootstrap Class Loader):启动类加载器是 JVM 的一部分,它负责加载核心 Java 类库(如 java.lang 包中的类)。它是 JVM 的内部组件,通常由本地代码实现,无法直接在 Java 代码中引用。

2.7 获取类的运行时结构

有一些方法或属性设置了private,则需要取消安全检测。调用setAccessible(true);

package com.kuang.reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

//获得类的信息
public class Test08 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
        Class c1 = Class.forName("com.kuang.reflection.User");

        //获得类的名字
        System.out.println(c1.getName());//获得包名+类名
        System.out.println(c1.getSimpleName());//获得类名

        //获得类的属性
        System.out.println("======================");
        Field[] fields = c1.getFields();//只能找到public属性

        fields = c1.getDeclaredFields();//找到全部的属性
        for (Field field : fields) {
            System.out.println(field);

        }
        Field name = c1.getDeclaredField("name");
        System.out.println(name);

        //获得类的方法
        System.out.println("======================");
        Method[] methods = c1.getMethods();//获得本类及其父类的全部public方法
        for (Method method : methods) {
            System.out.println("正常的:" + method);

        }
        methods = c1.getDeclaredMethods();//获得本类的所有方法
        for (Method method : methods) {
            System.out.println("getDeclaredMethods" + method);

        }
        //获得指定方法
        //重载
        Method getName = c1.getMethod("getName", null);
        Method setName = c1.getMethod("setName", String.class);
        System.out.println(getName);
        System.out.println(setName);


        //获得指定的构造器
        System.out.println("======================");
        Constructor[] constructors = c1.getConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }
        constructors = c1.getDeclaredConstructors();
        for (Constructor constructor : constructors) {
            System.out.println("#" + constructor);
        }
        //获得指定的构造器
        Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
        System.out.println("指定:" + declaredConstructor);
    }
}

2.8 通过反射创建对象,并操作

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

// 动态的创建,通过反射
public class Demo2 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        Class c1 = Class.forName("GetClassStructure.User");

        // 构造一个对象 newInstance() 必须有无参构造,也可以通过构造器
        User user1 = (User) c1.newInstance();
        System.out.println(user1); //user{name='null', id=0, age=0}

        // 通过构造器创建对象
        Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
        User user = (User) declaredConstructor.newInstance("姓名", 1, 2);
        System.out.println(user); //user{name='姓名', id=1, age=2}

        // 通过反射调用方法
        User user3 = (User) c1.newInstance();
        // 通过反射获取方法
        Method setName = c1.getDeclaredMethod("setName", String.class);
        // invoke激活 (对象,方法的值)
        setName.invoke(user3,"invoke名字");
        System.out.println(user3.getName()); //invoke名字

        // 通过反射操作属性
        User user4 = (User) c1.newInstance();
        Field name = c1.getDeclaredField("name");
        // 私有属性不能被赋值,需要关闭Java语言检测
        name.setAccessible(true);
        name.set(user4,"set名字");
        System.out.println(user4.getName()); //set名字
    }
}

2.9 获取注解信息

在ORM框架中

java 数据库
表结构
属性 字段
对象 一行记录
package com.text;
 
import java.lang.annotation.*;
 
//反射操作注解
public class test38 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        //创建Class对象
        Class<?> c1 = Class.forName("com.text.Student2");
        //通过Class对象获得类注解
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
        //获得指定注解值
        Table annotation = c1.getAnnotation(Table.class);
        System.out.println(annotation.value());
 
        //获得类中的指定注解
        java.lang.reflect.Field id = c1.getDeclaredField("id");//指定字段
        Field annotation1 = id.getAnnotation(Field.class);//指定注解
        System.out.println(annotation1.listName());
        System.out.println(annotation1.type());
        System.out.println(annotation1.length());
 
    }
}
 
@Table("staff_table")
class Student2 {
    @Field(listName = "id", type = "int", length = 10)
    private int id;
    @Field(listName = "name", type = "String", length = 3)
    private String name;
    @Field(listName = "age", type = "varchar", length = 3)
    private int age;
 
    public Student2() {
    }
 
    public Student2(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
 
    public int getId() {
        return id;
    }
 
    public void setId(int id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public int getAge() {
        return age;
    }
 
    public void setAge(int age) {
        this.age = age;
    }
}
 
//修饰表
@Target(ElementType.TYPE) //表示作用于类
@Retention(RetentionPolicy.RUNTIME) //表示它会被加载进入到JVM中
@interface Table {
    String value();//如果只有一个可以用value来命名
}
 
//修饰字段
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Field {
    String listName();
 
    String type();
 
    int length();
}
 
posted @ 2023-07-10 16:52  不準  阅读(34)  评论(0)    收藏  举报