【Java】反射

发布时间:2023年12月20日

反射(Reflection)

1、什么是反射

反射是一种非常强大的机制。反射跟注解配合,就天衣无缝了。

动态语言和静态语言

**动态语言:**是一类在运行时可以改变其结构的语言.例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。动态语言主要有JavaScript、python、c#等。

**静态语言:**与动态语言相对应,运行时不能改变的语言称为静态语言。静态语言主要有C、C++、Java。

Java不是动态语言,但是它可以被称为”准动态语言“,Java具有一定的动态性,通过反射机制可以获得类似于动态语言的特性。反射让编程变得更加灵活。

Reflection(反射)是 Java 被视为动态语言的关键,反射机制允许程序在执行期借助于 Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

2、反射的基础——Class对象

加载完类之后,HotSpot虚拟机中,在堆内存的方法区中就产生了一个 InstanceKlass对象用来存储类的所有信息,该对象必定不能被开发者通过任何方式获取。与此同时,堆中会产生一个Class类型的对象(一个类只有一个 Class 对象,这个对象就包含了InstanceKlass对象中的元数据),我们可以通过这个对象看到类的结构。

一个类加载完在堆中生成对应的Class对象,类的实例对象是通过Class对象创建的,通过类的实例对象可以获取到Class对象(getClass()方法),从而得到类的元数据,这被形象地称为”反射“。

在这里插入图片描述

所以可以说,反射就是通过获取Class对象,并操作Class对象达到各种目的。

可以通过多种方式获得Class对象。

//第一种方法,通过Class类的静态方法通过全限定名获取某个类的Class对象
Class c=Class.forName("java.lang.String");
//第二种方法,通过实例化对象的getClass方法获取它的Class对象
String s1=new String("haha");
Class c=s1.getClass();
//第三种方法,已知一个类的全类名,可以通过全类名获得它的Class对象
Class c=Person.class;
//第四种方法,通过类加载器,由类的全限定名加载某个类并获得Class对象

Class类有很多方法:

在这里插入图片描述

3、反射获得类信息

反射第一个用途就是获得类的各种信息了。

通过Class对象,可以获得运行时类的完整结构:Field(字段),Method(方法),Constructor(构造器),SuperClass(父类),Annotation(注解)。

package main.test;

class User{
    private String id;

    public void printMessage(){
        System.out.println(this.id);
    }
}

public class Test {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class c = Class.forName("main.test.User");
        //获得类名
        System.out.println(c.getName());
        //获得类的字段    打印信息:[]   只能找到public的属性
        System.out.println(Arrays.toString(c.getFields()));
        //获得类的字段    打印信息:[private java.lang.String main.test.User.id]   所有属性都能得到
        System.out.println(Arrays.toString(c.getDeclaredFields()));
        //获得特定属性名   打印信息:private java.lang.String main.test.User.id
        System.out.println(c.getDeclaredField("id"));
        //获得类的方法    打印信息:[public void main.test.User.printMessage()]
        System.out.println(Arrays.toString(c.getDeclaredMethods()));

        //此外,还可以获得类中的构造器、注解,都是一样的套路,非常简单
    }
}

4、反射操作方法和字段

第二个用途,Class对象可以获得类的一个实例化对象,也可以通过Class对象调用方法和设置字段值。

package main.test;

public class Test {
    public static void main(String[] args) throws Exception {
        Class c = Class.forName("main.test.User");
        
        //=======================================================
        //通过newInstance()获得实例化对象,该方法本质上是调用了User的无参构造方法,如果方法没有无参构造,会报错,所以这个方法已经过时(被@Deprecated)了
        User user = (User)c.newInstance();
        //打印信息:User{id='null'}
        System.out.println(user);
        //可以通过先获得构造器,再用指定构造器构造一个对象,这样就不会出错了
        User o1 = (User)c.getDeclaredConstructor(String.class).newInstance("传参");
        User o2 = (User)c.getDeclaredConstructor().newInstance();
        //打印信息:User{id='传参'}
        System.out.println(o1);
        //打印信息:User{id='null'}
        System.out.println(o2);
        
        //=======================================================
        //通过Class对象获得方法,再进行激活
        User user1 = (User)c.getDeclaredConstructor(String.class).newInstance("张三");
        //第一次打印user1对象中的信息:张三
        System.out.println(user1.getId());
        //为何这里第二个参数要传入String.class?因为同一个方法名可以重载不同类型的参数,所以必须把参数类型标清楚
        Method setId = c.getDeclaredMethod("setId", String.class);
        //激活方法,传参要传入User对象,表明激活的是哪一个对象中的方法
        setId.invoke(user1,"李四");
        //第二次打印user1对象中的信息:李四
        System.out.println(user1.getId());
        
        //=======================================================
       	//通过Class对象获得字段,再设置字段的值
        User user2 = (User)c.getDeclaredConstructor(String.class).newInstance("张三");
        //第一次打印user1对象中的信息:张三
        System.out.println(user2.getId());
        Field id = c.getDeclaredField("id");
        //直接调用id.set(user2,"李四")会报错,因为id这个字段是私有的,所以需要先把id的权限放开
        id.setAccessible(true);
        id.set(user2,"李四");
        //第二次打印user1对象中的信息:李四
        System.out.println(user2.getId());
        
        //这个看起来有点儿多此一举,但是之后就知道它的用处了~~~	
    }
}

class User{
    private String id;

    public User() {
    }

    public User(String id) {
        this.id = id;
    }

    public void printMessage(){
        System.out.println(this.id);
    }
    
    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "User{" +
                "id='" + id + '\'' +
                '}';
    }
}

通过反射获得对象的速度非常非常慢。

public static void main(String[] args) throws Exception {
    int num=100000000;
    Class c = Class.forName("main.test.User");
    long begin1 = System.currentTimeMillis();
    for (int i = 0; i <num ; i++) {
        User o1 = (User)c.getDeclaredConstructor(String.class).newInstance("传参");
    }
    //花费时间:4179
    System.out.println(System.currentTimeMillis() - begin1);

    long begin2 = System.currentTimeMillis();
    for (int i = 0; i < num; i++) {
        User o1 = new User("传参");
    }
    //花费时间:0
    System.out.println(System.currentTimeMillis() - begin2);

    //通过反射实例化对象速度慢了超级多倍哦,想清楚再使用
}

Class对象中的Field、Method、Constructor中都有setAccessible这个方法,可以突破private的限制。setAccessible设置为true可以关闭Java对public、private的检测,是可以提高反射的效率的。如果发现反射的效率实在是不尽人意,可以尝试关闭语法检测,大概可以减少3-5倍的运行时间。

5、反射操作泛型

反射操作泛型,这个听起来有点儿奇怪。Java的泛型在编译器中实现,类型检查和类型推断由编译器执行,然后生成普通的非泛型的字节码,虚拟机完全不感知泛型的存在。编译器使用泛型类型信息保证类型安全,然后在生成字节码之前将其清除 。因此,字节码文件中不存在泛型信息。换而言之,Class对象中按理来说是不存在泛型的信息的。

为了通过反射操作这些类型, Java新增了ParameterizedType、GenericArrayType、TypeVariable 和 WildcardType 几种类型来代表不能被归一到 Class 类中的类型但是又和原始类型齐名的类型。

  • ParameterizedType 表示一种参数化类型比如 Collection

  • GenericArrayType 表示一种元素类型是参数化类型或者类型变量的数组类型

  • TypeVariable 是各种类型变量的公共父接口

  • WildcardType 代表一种通配符类型表达式

6、反射操作注解

实现一个小功能:实体-表关系映射。

通过Java的实体和实体中的字段,可以判断出该实体对应MySQL中的哪张表,以及表中有哪些字段。

package main.test;

import java.lang.annotation.*;
import java.lang.reflect.Field;
import java.util.Arrays;


public class Test {
    public static void main(String[] args) throws Exception {
        Class c = Class.forName("main.test.Student");

        //通过反射获得类的注解
        Annotation[] cAnnotations = c.getAnnotations();
        //打印信息:[@main.test.TableMapping("db_student")]
        System.out.println(Arrays.toString(cAnnotations));

        //获得注解的value的值
        TableMapping annotation = (TableMapping) cAnnotations[0];
        //打印信息:db_student
        System.out.println(annotation.value());

        //同理,获得类中字段的注解的参数值
        Field[] fields = c.getDeclaredFields();
        FieldMapping[] fieldAnnotations = new FieldMapping[fields.length];
        for (int i = 0; i < fields.length; i++) {
            //已知类型,不需要强转
            fieldAnnotations[i] = fields[i].getAnnotation(FieldMapping.class);
        }
        //打印信息:  db_id,int,256
        //          db_age,int,3
        //          db_name,varchar,256
        for (FieldMapping fieldAnnotation : fieldAnnotations) {
            System.out.print(fieldAnnotation.columnName() + ",");
            System.out.print(fieldAnnotation.type() + ",");
            System.out.println(fieldAnnotation.length());
        }


    }
}


/**
 * @author xcy
 * 自定义一个类名的注解
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableMapping {
    String value();
}

/**
 * @author xcy
 * 自定义一个属性的注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldMapping {
    //列名
    String columnName();

    //类型
    String type();

    //长度
    int length();
}

@TableMapping("db_student")
class Student {
    @FieldMapping(columnName = "db_id", type = "int", length = 256)
    private int id;
    @FieldMapping(columnName = "db_age", type = "int", length = 3)
    private int age;
    @FieldMapping(columnName = "db_name", type = "varchar", length = 256)
    private String name;

    public Student(int id, int age, String name) {

        this.id = id;
        this.age = age;
        this.name = name;
    }

    public Student() {
    }

    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;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

不同的注解就像是不同颜色的标签,注解的参数就像是标签中可以记录的内容。使用一个注解作用于类/字段/方法,就是为类/字段/方法添加了额外的补充信息,这些信息可以通过反射获取到。补充信息被获取到,就可以对这些信息进行处理、操作,从而让被注解的类/字段/方法有了额外的功能,而且这些功能不容易被感知。

Spring、Springboot、mybatis-plus等框架正是因为引用了大量注解,才让开发者能够用简单的几行代码实现复杂的逻辑操作。

文章来源:https://blog.csdn.net/weixin_52808620/article/details/135103261
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。