JAVA反射机制

前言

本文简述了Java反射的概念、Java反射的应用场景以及Java反射的部分优缺点,简略介绍了Class类。着重介绍获取Class类对象的几种方法,以及如何使用Java反射,文末还进行了简单的性能分析。

一、反射概述

1. 什么是反射?

Java反射就是在Java程序运行期间,可以知道和获取任何类的全部属性和方法,对于任意对象,都能够调用它的任意方法和属性。这种动态获取类信息,动态调用对象方法的功能就是Java反射机制(Reflection),同时反射机制也是Java被视为一门准动态语言的关键。

2. 应用场景

  • 框架开发
  • 注解
  • 动态代理
  • 序列化/反序列化

3. 反射的优缺点

优点

  • 增强了程序的灵活性和可扩展性,降低耦合度
  • 允许程序创建和控制任何类的对象,无需提前硬编码目标类

缺点

  1. 反射的性能远低于同功能直接代码
  2. 无法在源码中看到程序逻辑,会产生维护问题
  3. 反射代码相较同功能直接代码更加复杂

二、Class类

1. 什么是Class类

Class类是反射机制的关键,Api文档中描述如下
在这里插入图片描述
在类加载过程中会通过defineClass方法在堆内存的方法区中生成一个Class类对象,每个类都有一个唯一的Class类对象,这个对象包含了完整的类的结构信息。
(类加载过程可以参考我之前写的另一篇文章Java类加载器

2. 获取Class类对象

通常获取Class类对象有三种方式
(下面的Person为一个自定义类,暂时不关注路径及类的内容)

  1. 已知具体的类,通过类的class属性获取,此方法最为安全可靠,程序性能最高。
Class c1 = Person.class
  1. 已存在目标类实例,直接调用该实例的getClass()方法获取。
Class c2 = Person.getClass();
  1. 已知目标类全类名,直接调用Class类的forName(“全类名”)方法获取,此方法可能抛出ClassNotFoundException
Class c3 = Class.forName("com.example.Test.Person");
  1. 针对基础数据类型的包装类,可以通过.TYPE获取类型
Integer.TYPE

3.打印测试获取到的Class类对象

测试方法如下

public class Test {
    public static void main(String[] args) throws ClassNotFoundException {
        //类
        Person person = new Person();
        Class c1 = Person.class;
        Class c2 = person.getClass();
        Class c3 = Class.forName("com.example.Test.Person");
        System.out.println(c1);
        System.out.println(c2);
        System.out.println(c3);
        //接口
        System.out.println(TestInterface.class);
        //数组
        System.out.println(int[].class);
        System.out.println(int[][].class);
        //注解
        System.out.println(Override.class);
        //枚举类
        System.out.println(ElementType.class);
        //void
        System.out.println(void.class);
        //Class
        System.out.println(Class.class);
        //包装类
        System.out.println(Integer.TYPE);
        System.out.println(Byte.TYPE);
        System.out.println(Short.TYPE);
        System.out.println(Long.TYPE);
        System.out.println(Float.TYPE);
        System.out.println(Double.TYPE);
        System.out.println(Character.TYPE);
        System.out.println(Boolean.TYPE);
    }
}

结果如下

class com.example.Test.Person
class com.example.Test.Person
class com.example.Test.Person
interface com.example.Test.TestInterface
class [I
class [[I
interface java.lang.Override
class java.lang.annotation.ElementType
void
class java.lang.Class
int
byte
short
long
float
double
char
boolean

三、反射的使用

1.获取构造方法并调用

准备目标类如下

public class Student{
    public Student(){
        System.out.println("无参public构造");
    }
    public Student(int i){
        System.out.println("有参public构造");
    }
    Student(int i,int j){
        System.out.println("默认构造");
    }
    private Student(String str){
        System.out.println("private构造");
    }
    protected Student(boolean bool){
        System.out.println("protected构造");
    }
}

准备测试类如下

public class Test {
    public static void main(String[] args) throws Exception{
    	//获取Class类对象
        Class c1 = Student.class;
        //getConstructors()方法,获取所有被public修饰(共有)的构造方法
        Constructor[] constructors = c1.getConstructors();
        //getDeclaredConstructors()方法,获取全部构造方法
        Constructor[] declaredConstructors = c1.getDeclaredConstructors();
        for(Constructor c : constructors){
            System.out.println(c);
        }
        System.out.println("-------------------");
        for(Constructor c : declaredConstructors){
            System.out.println(c);
        }
        System.out.println("-------------------");
        //getConstructor()方法,用于获取指定的公有构造方法
        //其中参数为Class类对象,即传入所需参数的class对象
        Constructor con1 = c1.getConstructor(null);
        //getDeclaredConstructor()方法,用于获取指定构造方法,参数类型同上
        Constructor con2 = c1.getDeclaredConstructor(String.class);
        //调用newInstance()创建实例
        Student s1 = (Student) con1.newInstance();
        //由于此处con2是私有构造方法,直接调用会报IllegalAccessException
        //需要调用setAccessible并传入true,下文会解释
        con2.setAccessible(true);
        Student s2 = (Student) con2.newInstance("test");
        System.out.println(con1);
        System.out.println(con2);
        System.out.println(s1);
        System.out.println(s2);
    }
}

关于上述代码中的setAccessible()方法,该方法的作用是启动或禁用访问安全检查,当参数值为true则指示反射的对象在使用时抑制Java语言访问检查,参数值为false则指示反射的对象执行Java语言访问检查。除Constructor对象中有setAccessible()方法,Field和Method中也有此方法,作用和使用方法相同,下文不再赘述。
输出结果如下

public com.example.Test.Student(int)
public com.example.Test.Student()
-------------------
protected com.example.Test.Student(boolean)
private com.example.Test.Student(java.lang.String)
com.example.Test.Student(int,int)
public com.example.Test.Student(int)
public com.example.Test.Student()
-------------------
无参public构造
private构造
public com.example.Test.Student()
private com.example.Test.Student(java.lang.String)
com.example.Test.Student@7ef20235
com.example.Test.Student@27d6c5e0

2.获取成员变量并调用

准备一个目标类

public class Student{
    public int id;
    String name;
    private String sex;
    protected int age;

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

测试类如下

public class Test {
    public static void main(String[] args) throws Exception{
        Class c1 = Student.class;
        Field[] fields = c1.getFields();
        Field[] declaredFields = c1.getDeclaredFields();
        System.out.println("获取公有成员变量");
        for(Field f : fields){
            System.out.println(f);
        }
        System.out.println("获取全部成员变量");
        for(Field f : declaredFields){
            System.out.println(f);
        }
        System.out.println("获取指定公有成员变量饼复制测试");
        //获取学生类实例
        Student s = (Student) c1.getConstructor(null).newInstance();
        Field id = c1.getField("id");
        System.out.println(id);
        //为字段赋值 第一个参数传入一个实例对象 第二个参数为想要赋予的值
        id.set(s,123);
        System.out.println(s.getId());
        System.out.println("获取指定成员变量并赋值测试");
        Field name = c1.getDeclaredField("name");
        System.out.println(name);
        name.set(s,"test");
        System.out.println(s.toString());
    }
}

输出结果如下

获取公有成员变量
public int com.example.Test.Student.id
获取全部成员变量
public int com.example.Test.Student.id
java.lang.String com.example.Test.Student.name
private java.lang.String com.example.Test.Student.sex
protected int com.example.Test.Student.age
获取指定公有成员变量饼复制测试
public int com.example.Test.Student.id
123
获取指定成员变量并赋值测试
java.lang.String com.example.Test.Student.name
Student{id=123, name='test', sex='null', age=0}

3. 获取成员方法并调用

准备目标类如下

public class Student{
    public void m1(){
        System.out.println("public修饰,无参,无返回值,m1被调用");
    }
    void m2(String str){
        System.out.println("默认修饰,有参,无返回值,m2被调用");
    }
    private String m3(int i , String str){
        System.out.println("private修饰,有参,有返回值,m3被调用");
        return "m3返回";
    }
    protected String m4(){
        System.out.println("protected修饰,无参,有返回值,m4被调用");
        return "m4返回";
    }
}

测试类如下

public class Test {
    public static void main(String[] args) throws Exception{
        Class c1 = Student.class;
        Method[] methods = c1.getMethods();
        Method[] declaredMethods = c1.getDeclaredMethods();
        System.out.println("获取全部公有方法");
        for(Method m : methods){
            System.out.println(m);
        }
        System.out.println("获取全部方法");
        for(Method m : declaredMethods){
            System.out.println(m);
        }
        System.out.println("获取指定共有方法并调用测试");
        //获取实例
        Student s = (Student)c1.getConstructor(null).newInstance();
        //getMethod方法 第一个参数为方法名
        //第二个参数传入目标方法的参数的Class类对象
        Method m1 = c1.getMethod("m1",null);
        //invoke方法调用 第一个参数传入一个实例 第二个参数传入参数值
        m1.invoke(s,null);
        System.out.println("获取指定方法并调用测试");
        //getDeclaredMethod方法参数规则同上 下面演示了多参数传递的方式
        Method m2 = c1.getDeclaredMethod("m3",new Class[]{int.class,String.class});
        m2.setAccessible(true);
        Object obj = m2.invoke(s,new Object[]{123,"test"});
        System.out.println(obj);
    }
}

结果如下

获取全部公有方法
public void com.example.Test.Student.m1()
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) 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 java.lang.String java.lang.Object.toString()
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()
获取全部方法
public void com.example.Test.Student.m1()
void com.example.Test.Student.m2(java.lang.String)
protected java.lang.String com.example.Test.Student.m4()
private java.lang.String com.example.Test.Student.m3(int,java.lang.String)
获取指定共有方法并调用测试
public修饰,无参,无返回值,m1被调用
获取指定方法并调用测试
private修饰,有参,有返回值,m3被调用
m3返回

4. 获取注解信息

准备目标类和简单相关自定义注解

@classAnno(value1 = "student",value2 = "STUDENT")
public class Student{
    @fieldAnno("thisIsId")
    public int id;

    @methodAnno("method")
    public void method(){
    }
}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface classAnno{
    String value1();
    String value2();
}

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface fieldAnno{
    String value();
}

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface methodAnno{
    String value();
}

准备测试类如下

public class Test {
    public static void main(String[] args) throws Exception{
        Class c1 = Student.class;
        //顺便测试一下获取父类
        System.out.println("获取父类Class类对象");
        System.out.println(c1.getSuperclass());
        
        System.out.println("类注解");
        System.out.println("获取全部公有注解");
        Annotation[] annotations = c1.getAnnotations();
        for(Annotation a : annotations){
            System.out.println(a);
        }
        System.out.println("获取指定公有注解并获取注解变量");
        classAnno c = (classAnno) c1.getAnnotation(classAnno.class);
        System.out.println(c.value1());
        System.out.println(c.value2());

        System.out.println("变量注解");
        System.out.println("通过Field获取注解并获取注解变量");
        Field id = c1.getField("id");
        fieldAnno f = id.getAnnotation(fieldAnno.class);
        System.out.println(f);
        System.out.println(f.value());

        System.out.println("方法注解");
        System.out.println("通过Method获取注解并获取注解变量");
        Method method = c1.getMethod("method");
        System.out.println(method);
        methodAnno methodAnno = method.getAnnotation(methodAnno.class);
        System.out.println(methodAnno);
        System.out.println(methodAnno.value());
    }
}

测试结果如下

获取父类Class类对象
class java.lang.Object
类注解
获取全部公有注解
@com.example.Test.classAnno(value1=student, value2=STUDENT)
获取指定公有注解并获取注解变量
student
STUDENT
变量注解
通过Field获取注解并获取注解变量
@com.example.Test.fieldAnno(value=thisIsId)
thisIsId
方法注解
通过Method获取注解并获取注解变量
public void com.example.Test.Student.method()
@com.example.Test.methodAnno(value=method)
method

四、性能对比

准备目标类如下

public class Student{

    public int id;

    public int getId() {
        return id;
    }

}

准备测试类如下
(进行两组测试,第一组相同方法执行1亿,第二组10亿次)

public class Test {
    public static void main(String[] args) throws Exception{
        method1();
        method2();
        method3();
    }
    //普通调用方法
    public static void method1(){
        Student s = new Student();
        Long t1 = System.currentTimeMillis();

        for (int i = 0; i < 100000000; i++) {
            s.getId();
        }

        Long t2 = System.currentTimeMillis();
        System.out.println("普通调用方法用时:"+(t2-t1)+"ms");
    }
    //反射调用方法 开启检测
    public static void method2() throws Exception {
        Student s = new Student();
        Class c = Student.class;
        Method m = c.getMethod("getId");
        Long t1 = System.currentTimeMillis();

        for (int i = 0; i < 100000000; i++) {
            m.invoke(s);
        }

        Long t2 = System.currentTimeMillis();
        System.out.println("反射调用方法用时:"+(t2-t1)+"ms");
    }
    //反射调用方法 关闭检测
    public static void method3() throws Exception {
        Student s = new Student();
        Class c = Student.class;
        Method m = c.getMethod("getId");
        m.setAccessible(true);
        Long t1 = System.currentTimeMillis();

        for (int i = 0; i < 100000000; i++) {
            m.invoke(s);
        }

        Long t2 = System.currentTimeMillis();
        System.out.println("关闭检测反射调用方法用时:"+(t2-t1)+"ms");
    }
}

测试结果如下

//执行1亿次
普通调用方法用时:3ms
反射调用方法用时:235ms
关闭检测反射调用方法用时:139ms
//执行10亿次
普通调用方法用时:5ms
反射调用方法用时:2204ms
关闭检测反射调用方法用时:1288ms

通过测试结果可以明显看出,反射效率较低,除某些特定需要使用反射的情景外,尽量避免使用以免降低程序效率。


本篇内容到此结束
作者才疏学浅,如文中出现纰漏,还望指正

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

7rulyL1ar

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

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

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

打赏作者

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

抵扣说明:

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

余额充值