(十四)Java 关于反射机制(详解)

本文介绍了Java反射机制的概念和使用方法,包括通过Modifier类分析修饰符,利用Class类获取类信息,以及如何创建对象、访问成员和调用方法。反射提供了一种在运行时检查和操作类、对象的能力,具有动态性和通用性的优点,但也存在性能和安全的潜在问题。

反射机制

前言

​ 本文主要介绍 Java 反射机制的基本概念和使用方法。反射在许多场景中都非常有用,例如处理注解、动态创建对象、实现插件系统等。在学习Java反射(Reflection)机制前,我们需要掌握,Java 类加载机制,即Java反射机制是通过反射 API 来操纵 Java 程序中的类、对象、方法等,因此需要了解 Java 类加载机制,包括类加载器、类的加载过程、类的初始化等。

​ 反射机制是一种 RTTI (Run-Time Type Identification),即,运行时类型识别。提出 RTTI 的目的有很多,如系统异常处理等底层操作就依赖 RTTI 。这里我们不讨论 RTTI 深层次的原理,将注意力更多的放到反射机制对应用层面的编程所产生的巨大作用。

什么是反射

​ 反射(Reflection)是 Java 提供的一种机制,允许在运行时检查和操作类、对象、方法、属性等。通过反射,我们可以在运行时获取类的详细信息,例如类名、继承关系、构造函数、方法、属性等。同时,我们也可以动态地创建对象、调用方法、访问属性等。

小知识:Modifier

​ Modifier 是 Java 编程语言中的一个关键字,它用于表示类、方法、变量和接口的访问修饰符或者修饰符。Java JDK 是 Java 平台的核心组件,它提供了 Java 编译器、Java 虚拟机、Java 类库等开发工具和运行环境。

在 Java 中,每个类、方法、变量和接口都可以有一个或多个修饰符,这些修饰符可以控制它们的可见性、可访问性、行为和状态等。Modifier类提供了以下静态方法,可以用于获取和操作Java中的修饰符:

  • public static boolean isAbstract(int modifiers):判断给定的修饰符是否为抽象(abstract)。
  • public static boolean isFinal(int modifiers):判断给定的修饰符是否为final。
  • public static boolean isInterface(int modifiers):判断给定的修饰符是否为接口(interface)。
  • public static boolean isNative(int modifiers):判断给定的修饰符是否为本地方法(native)。
  • public static boolean isPrivate(int modifiers):判断给定的修饰符是否为私有的(private)。
  • public static boolean isProtected(int modifiers):判断给定的修饰符是否为受保护的(protected)。
  • public static boolean isPublic(int modifiers):判断给定的修饰符是否为公共的(public)。
  • public static boolean isStatic(int modifiers):判断给定的修饰符是否为静态的(static)。
  • public static boolean isStrict(int modifiers):判断给定的修饰符是否为严格的(strict)。
  • public static boolean isSynchronized(int modifiers):判断给定的修饰符是否为同步的(synchronized)。
  • public static boolean isTransient(int modifiers):判断给定的修饰符是否为瞬态的(transient)。
  • public static boolean isVolatile(int modifiers):判断给定的修饰符是否为易失性的(volatile)。

这里我们试着通过 Modifier 查看下成员或者方法的修饰符的值:

public class TestReject {

	public static void main(String[] args) {
		System.out.println("ABSTRACT : " + Modifier.ABSTRACT);	
		// 1024		0000 0100   0000 0000
		System.out.println("FINAL : " + Modifier.FINAL);
		// 16			0000 0000   0001 0000
		System.out.println("PRIVATE : " + Modifier.PRIVATE);	
		// 2			0000 0000   0000 0010
		System.out.println("PROTECTED : " + Modifier.PROTECTED);	
		// 4			0000 0000   0000 0100
		System.out.println("PUBLIC : " + Modifier.PUBLIC);
		// 1			0000 0000   0000 0001
		System.out.println("STATIC : " + Modifier.STATIC);	
		// 8			0000 0000   0000 1000
		// 一个成员或者方法的修饰符的最终值,是由多个修饰符的值进行“按位相或”得到的!
    }
}

输出为:

ABSTRACT :1024
FINAL :16
STATIC :8
PROTECTED :4
PRIVATE :2
PUBLIC :1

元数据类—— Class 类

首先,在我们没接触到反射机制时,对于类我们通常的使用方法是:

	Complex c = new Complex();

这个 Complex 类事先得先必须存在,而且源代码的角度已经“定死”了。这些代码在我们曾经编写 Complex 类的过程中已经确定下来了,不可能在执行的过程中再发生改变。

如果现在存在这么一种情况:某个类,这个类是在未来才能定义的,但是因为某种编程需要,我们希望现在就产生这个类的一个对象,这就是 RTTI 存在的作用。

package com.hb.reflect.test;

public class ReflectFirstTest {
    public static Object creatAnObject(String className) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        Class<?> klass = Class.forName(className);
        Object object = klass.newInstance();

        return object;
    }
}

程序第 5 行:Class<?> klass = Class.forName(className);

Class 类是一个泛型类,是“描述类的内部信息的类”,又称“元数据类”。

其实,所有类都有一个class 静态成员;这个成员是JVM生成的,我们没有更改它的权限。ReflectFirstTest 类的静态方法createAnObject(String className),能够根据传递过来的,字符串类型的类名
称(必须包含包路径在内的全路径类名称),生成这个类的一个实例。

程序第 5 行: Object object = klass.newInstance();

这个方法可以产生一个对象。本质上,newInstance() 方法调用的是类的无参构造方法。

获取 class 对象

在 Java 中,每个类都有一个与之关联的 Class 对象,用于存储类的元数据。要使用反射,首先需要获取目标类的 Class 对象。有三种方法可以获取 Class 对象:

  1. 通过类的全限定名(包括包名)调用 Class.forName() 方法:

    Class<?> clazz = Class.forName("java.lang.String");
    
  2. 通过类字面量(class literal)获取:

    Class<?> clazz = String.class;
    
  3. 通过对象实例的 getClass() 方法获取:

    String str = "Hello, World!";
    Class<?> clazz = str.getClass();
    

创建对象

通过 Class 对象,我们可以创建目标类的实例。有两种方法可以创建对象:

  1. 使用默认构造函数创建对象:

    Object obj = clazz.newInstance();
    
  2. 使用指定构造函数创建对象:

    Constructor<?> constructor = clazz.getConstructor(String.class);
    Object obj = constructor.newInstance("Hello, World!");
    

反射机制实现的操作

这里需要将我们之前编写的 Complex 类拿过来,具体代码请跳转至:

关于复数类complexhttps://blog.youkuaiyun.com/SwaggerHB/article/details/129735124?spm=1001.2014.3001.5502

1、获取类的所有构造方法

通过反射机制引用类的成员、方法之前,必须要先能获取它们的信息,而且越详细越好。Java 反射机制在 Class 类中提供了大量侦测类成员、类方法以及构造方法的方法,而且还可以进一步得到包括成员类型、权限修饰符、方法参数(包括参数个数、类型)信息、方法返回值类型等等在内的众多信息。

/*
 * 所谓反射机制,又称为:动态识别机制。
 * 即,在程序执行过程中能够对指定类的内部数据进行了解、使用。
 */
public class TestReflect {
    public static void main(String[] args) {
        Class<?> klass = Complex.class;
        Constructor<?> [] constructors = klass.getConstructors();
        //获取 klass 这个类的所有构造方法;
        for (Constructor<?> constructor : constructors) {
            String constructorName = constructor.getName();
            System.out.println(constructorName);

            Parameter [] parameters = constructor.getParameters();
            int index = 1;
            for (Parameter parameter : parameters) {
                Class<?> parameterType = parameter.getType();
                System.out.println("\t第" + index++ + "个参数类型:" + parameterType);
            }
        }
    }
}

2、获取类的方法

public class TestReflect {
    public static void main(String[] args) {
        Class<?> klass = Complex.class;
        Method[] allMethods = klass.getMethods();         //获取本类及其父类的所有 public 方法;
        Method[] methods = klass.getDeclaredMethods();    //获取本类的所有方法;
        
        for (Method method : methods) {
            System.out.println("方法:" + method.getName() + " ");
            System.out.println("返回值类型:" + method.getReturnType() + " ");
            int parameterCount = method.getParameterCount();
            System.out.println("参数个数:" + parameterCount + " ");

            if(parameterCount > 0) {
                Parameter[] parameters = method.getParameters();
                for (int index = 0; index < parameters.length; index++) {
                    System.out.println("\t第" + (index + 1) + "个参数类型 : " + parameters[index].getType());
                }
            }
        }
    }
}

3、获取类的所有成员

public class TestReflect {
    public static void main(String[] args) {
        Class<?> klass = Complex.class;
        // Field[] fields = klass.getFields();          只能找到 public 属性
        Field[] fields = klass.getDeclaredFields();

        for (Field field : fields) {
            String fieldName = field.getName();
            Class<?> fieldType = field.getType();
            int modify = field.getModifiers();
            System.out.println("成员 : " + fieldName + ", 类型 : " + fieldType  + ", 权限修饰符 : " + modify);
        }
    }
}

4、通过反射机制对成员进行引用

无论是获取成员的值,或者,对成员进行赋值,都需要通过对象才可以进行。因此,在用反射机制对成员进行引用时,都必须提供一个本类的对象才可以。

public class TestReflect {
    public static void main(String[] args) {
        String className = "com.hb.complex.core.Complex";
        try {
            Class<?> clazz = Class.forName(className);
            Object object = clazz.newInstance();
            Field field = clazz.getDeclaredField("real");
            // 由于 real 成员是 private 修饰的,必须更改访问规则;
            // "field.setAccessible(true);" 的作用就是让我们可以在用反射时访问私有变量;
            field.setAccessible(true);
            // 必须提供这个类的对象,即,object,才能对成员赋值;
            field.set(object, 3.14);
            //通过获取成员的值,检测上面的赋值操作是否成功?
            double value  = (double)field.get(object);
            System.out.println(value);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}

5、通过反射机制对方法进行调用

和对成员的操作一样,对方法通过反射机制进行调用,也需要传递类对象;比对成员操作更复杂一些的是,方法的调用需要参数,所以需要先准备好参数。

public class TestReflect {
    public static void main(String[] args) {
        String className = "com.hb.complex.core.Complex";
        try {
            Class<?> clazz = Class.forName(className);
            Complex complex1 = (Complex) clazz.newInstance();
            Method setReal = clazz.getDeclaredMethod("setReal", double.class);

            setReal.invoke(complex1, 12.3);
            System.out.println("通过反射机制执行setReal()方法后的对象的值 : " + complex1);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }
}

输出结果为:

通过反射机制执行setReal()方法后的对象的值 : 12.3

6、通过反射机制用构造方法产生对象

​ 前面我们通过 Class<?> 类所提供的 newIlnstance() 方法,获得了相关类的一个对象,这种产生对象的方法,本质上是调用了该类的无参构造方法,如果想通过对带参构造方法的调用,生成该类的对象,就如同上面对方法的调用一样:在查找构造方法时,必须说明构造方法的参数类型;在调用时也要提供具体的参数;

public class TestReflect {
    public static void main(String[] args) {
        String className = "com.hb.complex.core.Complex";
        try {
            Class<?> clazz = Class.forName(className);

            //因为构造方法也支持重载;
            //因此,在定位构造方法时,必须提供构造方法的参数类型信息;
            Class<?>[] paraTypes = new Class<?>[] {double.class, double.class};
            Constructor<?> constructor = clazz.getConstructor(paraTypes);
            //确定构造方法的参数值
            Object[] paras = new Object[] {1.0, 2.0};
            //通过构造方法生成一个对象;
            Object object = constructor.newInstance(paras);
            System.out.println("新产生的对象是:" + object);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
}

输出结果为:

新产生的对象是:1.0+2.0i

反射机制的优缺点

优点

  • 动态性:反射允许我们在运行时获取类的信息和操作对象,提高了程序的灵活性和可扩展性。

  • 通用性:通过反射,我们可以编写一些通用的工具类和函数,不需要针对具体的类和对象进行硬编码。

缺点

  • 性能开销:反射操作通常比直接操作对象要慢,因为涉及到许多动态查找和类型检查等操作。

  • 安全问题:反射允许我们访问和修改类的非公共成员,可能会破坏封装性和安全性。

对反射机制的思考

反射机制是一种非常有用的机制,但是在使用时需要注意其性能问题和可能带来的安全问题,合理地使用反射机制可以为程序的灵活性和可扩展性提供很大的帮助。

综合上述所有所实现功能,其最重要的一点是:

能够对尚未定义的类进行全方位的操作。

如果结合之前讲述的 XML 和 Properties配置文件,就可以让我们的工具更灵活地处理用户在未来才能定义的类,并对其进行实例化、执行其所提供的所有方法、访问它的所有成员。当然,具体的应用场景通常都是复杂的。毕竟,复杂工具总是被应用于复杂任务中,以实现之前所不能实现的功能。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

HB0o0

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

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

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

打赏作者

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

抵扣说明:

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

余额充值