JAVA注解与反射:看这篇文章就够了2

简介: JAVA注解与反射:看这篇文章就够了

二、Java反射

java中的字节码:Java源代码经过虚拟机编译器编译后产生的文件(即扩展为.class的文件),它不面向任何特定的处理器,只面向虚拟机。

1.反射的定义

反射机制

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.

什么是反射

反射就是把java类中的各种成分映射成一个个的Java对象

2.Class类

Class 类的实例表示正在运行的 Java 应用程序中的类和接口。也就是jvm中有N多的实例每个类都有该Class对象。(包括基本数据类型)

Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass 方法自动构造的。也就是这不需要我们自己去处理创建,JVM已经帮我们创建好了

(具体可查看源码,或查看Java api详解)

所有类型的Class对象

Class c1 = Object.class;    //类
        Class c2 = Comparable.class;    //接口
        Class c3 = String.class;    //一维数组
        Class c4 = int[][].class;   //二维数组
        Class c5 = Override.class;  //注解
        Class c6 = ElementType.class;   //枚举
        Class c7 = Integer.class;   //基本数据类型
        Class c8 = void.class;  //void
        Class c9 = Class.class; //Class
        //只要元素类型与维度一致,就是同一个Class
        int[] a = new int[10];
        int[] b = new int[100];
        int[][] c = new int[10][10];
        System.out.println(a.getClass().hashCode());
        System.out.println(b.getClass().hashCode());
        System.out.println(c.getClass().hashCode());

3.获取反射对象Class五种方式

通过Object类的方法getClass()继承给所有子类

在Object源码中存在getClass方法:

public final native Class<?> getClass(); //final修饰无法重写 native原生的

任何数据类型都有静态的class属性

示例:

Class c2 = User.class;

通过Class类的静态方法:forName(String className)

示例:

Class c3 = Class.forName("cn.com.reflection.User");

基本内置类型的包装类都有一个Type属性

示例:

Class c4 = Integer.TYPE;

通过子类Class找到父类Class

示例:

Class c4 = User.class.getSuperclass();

4.类的加载内存分析

加载

加载,是指Java虚拟机查找字节流(查找.class文件),并且根据字节流创建java.lang.Class对象的过程。这个过程,将类的.class文件中的二进制数据读入内存,放在运行时区域的方法区内。然后在堆中创建java.lang.Class对象,用来封装类在方法区的数据结构。

类加载阶段:

(1)Java虚拟机将.class文件读入内存,并为之创建一个Class对象。

(2)任何类被使用时系统都会为其创建一个且仅有一个Class对象。

(3)这个Class对象描述了这个类创建出来的对象的所有信息,比如有哪些构造方法,都有哪些成员方法,都有哪些成员变量等。

链接

链接包括验证、准备以及解析三个阶段。

(1)验证阶段。主要的目的是确保被加载的类(.class文件的字节流)满足Java虚拟机规范,不会造成安全错误。

(2)准备阶段。负责为类的静态成员分配内存,并设置默认初始值。

(3)解析阶段。将类的二进制数据中的符号引用替换为直接引用。

初始化

初始化,则是为标记为常量值的字段赋值的过程。换句话说,只对static修饰的变量或语句块进行初始化。

如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。

如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。

具体说法如下:

执行类构造器<clinit>()方法的过程。类构造器<clinit>()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。

当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步。

虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步。

public class test03 {
    public static void main(String[] args) {
        A a = new A();
        System.out.println(a.m);
    }
    /*
    1.加载到内存,会产生一个类对应的Class对象
    2.链接,结束后 m = 0;
    3.初始化
        <clinit>() {
            System.out.println("A类静态代码块初始化");
            m = 300;
            m = 100;
        }
     m = 100;
     */
}
class A {
    static {
        System.out.println("A类静态代码块初始化");
        m = 300;
    }
    static int m = 100;
    public A() {
        System.out.println("A类无参构造初始化");
    }
}

5.分析类初始化

类的主动引用(一定会发生初始化)

当虚拟机启动,先初始化main方法所在的类

new一个类的对象

调用类的静态成员(除了final常量)和静态方法

使用java,lang.reflect包的方法对类进行反射调用

当初始化一个类,如果其父类未被初始化,则先会初始化其父类

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

当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类的初始化

通过数组定义引用,不会出发此类的初始化

引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中)

public class test04 {
    static {
        System.out.println("Main类被加载");
    }
    public static void main(String[] args) throws ClassNotFoundException {
        //1.主动引用
        Son son = new Son();
        //反射也会产生主动引用
        Class.forName("cn.com.reflection.Son");
        //不会产生类的引用的方法
            //子类引用父类的值
        System.out.println(Son.n);
            //数组只是开辟一个内存空间,也不会初始化
        Son[] a = new Son[5];
            //引用常量也不会初始化
        System.out.println(Son.M);
    }
}
class Father {
    static int n = 2;
    static {
        System.out.println("父类被加载");
    }
}
class Son extends Father {
    static {
        System.out.println("子类被加载");
        m = 300;
    }
    static int m = 100;
    static final int M = 1;
}


6.类加载器

类加载器作用是用来把类(class)装载进内存的。JVM规范定义了如下类型的类加载器。

系统类加载器

负责java-classpath或-d java.class.path所指的目录下的类与jar包装入工作库,是最常用的加载器。

扩展类加载器

负责jre/lib/ext或-d java.ext.dirs所指的目录下的类与jar包装入工作库

引导类加载器(根加载器)

用c++编写的,是jvm自带的类加载器,负责java平台核心库,用来装载核心类库。该加载器无法直接获取。

各加载器关系

自定义加载器—>System Classloader—>Extension Classloader—>Bootstap Classloader(从左至右对应从底到顶)

自底向上检查类是否已装载

自顶向底尝试加载类

双亲委派机制

自顶向底尝试加载类时,会检查是否存在了该类的包,如果已存在就不会向下加载子类的包。

双亲委派机制保证了安全性。

public class test05 {
    public static void main(String[] args) throws ClassNotFoundException {
        //获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);
        //获取系统类加载器的父类加载器-->扩展类加载器
        ClassLoader parent = systemClassLoader.getParent();
        System.out.println(parent);
        //获取扩展类加载器的父类加载器-->根加载器(c/c++)
        ClassLoader parent1 = parent.getParent();
        System.out.println(parent1);
        //测试当前类是哪个加载器加载的
        ClassLoader classLoader = Class.forName("cn.com.reflection.test05").getClassLoader();
        System.out.println(classLoader);
        //测试JDK内置的类是谁加载的
        ClassLoader classLoader1 = Class.forName("java.lang.Object").getClassLoader();
        System.out.println(classLoader1);
        //如何获取系统类加载器可以加载的路径
        System.out.println(System.getProperty("java.class.path"));
        /*
        D:\Program Files\Java\jdk1.8.0_241\jre\lib\charsets.jar;
        D:\Program Files\Java\jdk1.8.0_241\jre\lib\deploy.jar;
        D:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\access-bridge-64.jar;
        D:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\cldrdata.jar;
        D:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\dnsns.jar;
        D:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\jaccess.jar;
        D:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\jfxrt.jar;
        D:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\localedata.jar;
        D:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\nashorn.jar;
        D:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunec.jar;
        D:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunjce_provider.jar;
        D:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunmscapi.jar;
        D:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\sunpkcs11.jar;
        D:\Program Files\Java\jdk1.8.0_241\jre\lib\ext\zipfs.jar;
        D:\Program Files\Java\jdk1.8.0_241\jre\lib\javaws.jar;
        D:\Program Files\Java\jdk1.8.0_241\jre\lib\jce.jar;
        D:\Program Files\Java\jdk1.8.0_241\jre\lib\jfr.jar;
        D:\Program Files\Java\jdk1.8.0_241\jre\lib\jfxswt.jar;
        D:\Program Files\Java\jdk1.8.0_241\jre\lib\jsse.jar;
        D:\Program Files\Java\jdk1.8.0_241\jre\lib\management-agent.jar;
        D:\Program Files\Java\jdk1.8.0_241\jre\lib\plugin.jar;
        D:\Program Files\Java\jdk1.8.0_241\jre\lib\resources.jar;
        D:\Program Files\Java\jdk1.8.0_241\jre\lib\rt.jar;
        D:\idea_file\AnnotationAndReflection\out\production\AnnotationAndReflection;
        D:\javafile\IDEA\IntelliJ IDEA 2022.2.4\lib\idea_rt.jar
         */
    }

7.获取类的运行时结构

getXxx()和getDeclaredXxx()

前者获取本类和父类的所有public

后者获取本类全部

getXxx()参数问题

在获取getXxx()时会遇到要求给出参数的class,这是以为多态的原因。比如在获取一个方法时候,不仅仅需要知道方法名字,还需要知道方法参数,才能确定你所需要的确切的方法。

//获取类的信息
public class test06 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class c1 = Class.forName("cn.com.reflection.User");
        //获得类的名字
        System.out.println(c1.getName());   //包名 + 类名
        System.out.println(c1.getSimpleName()); //获得类名
        //获得类的属性
        Field[] fields = c1.getFields(); //获得public属性
        fields = c1.getDeclaredFields(); //获取全部属性
        for(Field f : fields) {
            System.out.println(f);
        }
        //获得类的方法
        Method[] methods = c1.getMethods(); //获得本类和父类的public方法
        methods = c1.getDeclaredMethods();  //获得本类的全部方法
        for(Method m : methods) {
            System.out.println(m);
        }
        //获得类的构造器
        Constructor[] constructors = c1.getConstructors(); //获取public方法
        constructors = c1.getDeclaredConstructors();    //获取全部方法
        for(Constructor c : constructors) {
            System.out.println(c);
        }
    }
}

8.动态创建对象执行方法

创建类的对象(两种)

newInstance()

本质上是调用了无参构造器且构造器访问权限满足

构造器创建对象

通过getDeclaredConstructor(参数.class)获取本类指定类型构造器

用构造器.newInstance(“Xxx”)传入构造参数

调用类的方法并获取返回值

通过类得到方法对象,对象中invoke方法 (invoke返回方法返回值)

class —> method —> invoke(对象,方法参数的值且可以为null)

获取类方法的返回值类型

通过类得到方法对象,对象中getReturnType方法

class —> field —> getReturnType()

获取类方法的参数类型

通过类得到方法对象,对象中getParameterTypes方法

class —> field —> getParameterTypes()

调用类的属性

通过类得到方法对象,对象中set方法

class —> field —> set(对象,方法参数的值)

获取调用类的属性

通过类得到方法对象,对象中get方法

class —> field —> get(对象)

setAccessible(boolean x)

启动和禁用访问安全检查的开关

作用:

1.提高反射效率

2.访问私有

//动态的创建对象
public class test07 {
    public static void main(String[] args) throws Exception {
        //获得Class对象
        Class c1 = Class.forName("cn.com.reflection.User");
        //通过newInstance()构造一个对象
        User user = (User)c1.newInstance(); //本质上调用了无参构造器
        System.out.println(user);
        //通过构造器创建对象
        Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
        User user1 = (User)constructor.newInstance("jacky", 1, 1);
        System.out.println(user1);
        //通过反射调用普通方法
        User user3 = (User)c1.newInstance();
        //通过反射获取一个方法
        Method setName = c1.getDeclaredMethod("setName", String.class);
        //invoke : 激活的意思
        //(对象, "方法的值")
        setName.invoke(user3, "jacky");
        System.out.println(user3.getName());
        //通过反射操作属性
        User user4 = (User)c1.newInstance();
        Field name = c1.getDeclaredField("name");
        //不能直接操作私有属性,需要关闭程序的安全检测,属性或方法的setAccessible(true)
        name.setAccessible(true);
        name.set(user4, "jakcy1");
        System.out.println(user4.getName());
    }
}

抽象类和接口无法被实例化,但是其中存在静态方法或非抽象方法的时候,无需实例化也可调用静态方法。

实际上,任何类的方法都可无需实例化调用。

isModifiers()判断什么关键词修饰判断是否为抽象或者静态等等。

数组反射

假定一个方法,需要知道对象是否为数组并打印。

下面展示包括多维数组的代码:

public void printdata(Object o) {
        Class c = o.getClass();
        if(c.isArray()) {
            int len = Array.getLength(o);
            for(int i=0; i<len; i++) {
                printdata(Array.get(o, i));
            }
        } else {
            System.out.println(o);
        }
    }

9.获取泛型信息

Java采用泛型擦除的机制来引入泛型,Java中的泛型仅仅是给编译器Javac使用的,确保数据的安全性和免去强制类型转换的问题,但是,一旦编译完成,所有和泛型有关的类型全部擦除

public static void main(String[] args) throws NoSuchMethodException {
        Method method = test08.class.getMethod("test01", Map.class, List.class);
        Type[] genericParameterTypes = method.getGenericParameterTypes();
        for(Type genericParameterType : genericParameterTypes) {
            System.out.println("#" + genericParameterType);
            if(genericParameterType instanceof ParameterizedType) {
                Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                for(Type actualTypeArgument : actualTypeArguments) {
                    System.out.println(actualTypeArgument);
                }
            }
        }
        Method method2 = test08.class.getMethod("test02", null);
        Type genericReturnType = method2.getGenericReturnType();
        if(genericReturnType instanceof ParameterizedType) {
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for(Type actualTypeArgument : actualTypeArguments) {
                System.out.println(actualTypeArgument);
            }
        }
    }
    public void test01(Map<String, User> map, List<User> list) {
    }
    public Map<String, User> test02() {
        return null;
    }
`

10.获取注解信息

ORM Object Relationship Map —> 对象关系映射

例如

数据库中表与代码中对应的类的映射

属性与字段对应

对象与记录对应

package cn.com.reflection;
import java.io.File;
import java.lang.annotation.*;
import java.lang.reflect.Field;
//练习反射操作注解
public class test09 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class c1 = Class.forName("cn.com.reflection.test09.Student");
        //通过反射获得注解
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }
        //获得注解value的值
        Table annotation = (Table)c1.getAnnotation(Table.class);
        String value = annotation.value();
        System.out.println(value);
        //获得类指定的注解
        Field name = c1.getDeclaredField("name");
        Field1 annotation1 = name.getAnnotation(Field1.class);
        System.out.println(annotation1.columnName());
        System.out.println(annotation1.type());
        System.out.println(annotation1.length());
    }
}
@Table("db_student")
class Student {
    @Field1(columnName = "db_id", type = "int", length = 10)
    private int id;
    @Field1(columnName = "db_age", type = "int", length = 10)
    private int age;
    @Field1(columnName = "db_name", type = "varchar", length = 3)
    private String name;
    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table {
    String value();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Field1{
    String columnName();
    String type();
    int length();
}
目录
相关文章
|
14天前
|
XML Java 编译器
Java注解的底层源码剖析与技术认识
Java注解(Annotation)是Java 5引入的一种新特性,它提供了一种在代码中添加元数据(Metadata)的方式。注解本身并不是代码的一部分,它们不会直接影响代码的执行,但可以在编译、类加载和运行时被读取和处理。注解为开发者提供了一种以非侵入性的方式为代码提供额外信息的手段,这些信息可以用于生成文档、编译时检查、运行时处理等。
50 7
|
2月前
|
存储 安全 Java
从入门到精通:Java Map全攻略,一篇文章就够了!
【10月更文挑战第17天】本文详细介绍了Java编程中Map的使用,涵盖Map的基本概念、创建、访问与修改、遍历方法、常用实现类(如HashMap、TreeMap、LinkedHashMap)及其特点,以及Map在多线程环境下的并发处理和性能优化技巧,适合初学者和进阶者学习。
64 3
|
2月前
|
XML Java 编译器
Java学习十六—掌握注解:让编程更简单
Java 注解(Annotation)是一种特殊的语法结构,可以在代码中嵌入元数据。它们不直接影响代码的运行,但可以通过工具和框架提供额外的信息,帮助在编译、部署或运行时进行处理。
97 43
Java学习十六—掌握注解:让编程更简单
|
19天前
|
Java 编译器 数据库
Java 中的注解(Annotations):代码中的 “元数据” 魔法
Java注解是代码中的“元数据”标签,不直接参与业务逻辑,但在编译或运行时提供重要信息。本文介绍了注解的基础语法、内置注解的应用场景,以及如何自定义注解和结合AOP技术实现方法执行日志记录,展示了注解在提升代码质量、简化开发流程和增强程序功能方面的强大作用。
55 5
|
25天前
|
监控 Java
Java基础——反射
本文介绍了Java反射机制的基本概念和使用方法,包括`Class`类的使用、动态加载类、获取方法和成员变量信息、方法反射操作、以及通过反射了解集合泛型的本质。同时,文章还探讨了动态代理的概念及其应用,通过实例展示了如何利用动态代理实现面向切面编程(AOP),例如为方法执行添加性能监控。
|
1月前
|
Java 开发者 Spring
[Java]自定义注解
本文介绍了Java中的四个元注解(@Target、@Retention、@Documented、@Inherited)及其使用方法,并详细讲解了自定义注解的定义和使用细节。文章还提到了Spring框架中的@AliasFor注解,通过示例帮助读者更好地理解和应用这些注解。文中强调了注解的生命周期、继承性和文档化特性,适合初学者和进阶开发者参考。
56 14
|
1月前
|
前端开发 Java
[Java]讲解@CallerSensitive注解
本文介绍了 `@CallerSensitive` 注解及其作用,通过 `Reflection.getCallerClass()` 方法返回调用方的 Class 对象。文章还详细解释了如何通过配置 VM Options 使自定义类被启动类加载器加载,以识别该注解。涉及的 VM Options 包括 `-Xbootclasspath`、`-Xbootclasspath/a` 和 `-Xbootclasspath/p`。最后,推荐了几篇关于 ClassLoader 的详细文章,供读者进一步学习。
36 12
|
1月前
|
Java
Java的反射
Java的反射。
26 2
|
2月前
|
存储 安全 Java
从入门到精通:Java Map全攻略,一篇文章就够了!
【10月更文挑战第19天】本文介绍了Java编程中重要的数据结构——Map,通过问答形式讲解了Map的基本概念、创建、访问与修改、遍历方法、常用实现类(如HashMap、TreeMap、LinkedHashMap)及其特点,以及Map在多线程环境下的使用和性能优化技巧,适合初学者和进阶者学习。
79 4
|
2月前
|
存储 Java
[Java]反射
本文详细介绍了Java反射机制的基本概念、使用方法及其注意事项。首先解释了反射的定义和类加载过程,接着通过具体示例展示了如何使用反射获取和操作类的构造方法、方法和变量。文章还讨论了反射在类加载、内部类、父类成员访问等方面的特殊行为,并提供了通过反射跳过泛型检查的示例。最后,简要介绍了字面量和符号引用的概念。全文旨在帮助读者深入理解反射机制及其应用场景。
35 0
[Java]反射