Java反射-Reflection

本文介绍了Java反射的概念,包括内省和获取类信息的能力。重点讲解了如何获取和使用Method对象,涉及基本类型、接口和数组的处理。文章还讨论了动态调用方法、Field对象及其常用API,并提到了动态加载类和通过反射创建对象的基本方法。
摘要由CSDN通过智能技术生成

*文章内容基于《Java Reflection In Action》前三章部分内容整理
博文链接:伊地知虾饺的博客-Java反射-Reflection

什么是反射?

​ 反射(reflection)是程序在运行时检查或改变其自身行为和结构的一种能力。程序了解自身(对于Java来说指的是有关类的信息)的能力被称为“内省(introspection)”。书中写到“程序的内省就好像你照镜子时看见自己一样”。人在照镜子时能看见自己的外貌和仪态,类似的,Java程序通过内省能获取有关类的结构和行为有关的信息。

有关Method

获取类中的方法

Method getMethod(String name, Class[] parameterTypes); //1

Method[] getMethods(); //2

Method getDeclaredMethod(String name, Class[] parameterTypes); //3
    
Method[] getDeclaredMethods(); //4

​ 上述代码块中, String name 是方法的名字, Class[] parameterTypes 是要获取的方法接受的参数的类。第二个参数可以是数组,也可是可变个数的参数

getDeclaredMethodgetDeclaredMethods 返回在这个类中声明的方法,不包括继承自父类的方法。然而,这两个方法不会关注方法的可见性——无论方法是public,protected,package还是private,都会被返回。

getMethodgetMethods 返回类中的所有方法——无论是在类中声明的还是继承自父类的。但是这两个方法只会返回被声明为public的方法。

​ 当找不到方法时,会抛 NoSuchMethodException

有关基本类型、接口和数组

​ 在上一节的方法中,第二个参数必须包含想要获得的方法的参数的类。然而,基本类型、接口和数组并不能够成为具体的类对象(虽然Java中有Integer这样的类,但这里指的是int这样的基本类型。即使JVM会自动拆箱/装箱,但在作为参数时二者仍是不同的)。

​ 虽然基本类型不能作为一个类对象,Java还是为其提供了class属性(如: int.class )。可以使用下面的语句得到Vector类中的get方法:

Method m = Vector.class.getMethod("get", int.class);

​ 可以使用 isPrimitive() 来判断一个类对象(class object)是否是基本类型。另外,即使void并不是Java中的类,它也有class属性。需要注意的是,isPrimitive()void.class 返回 true

​ 对于接口,Java也为其引入了类对象。例如,Vector中addAll方法接收一个Collection 接口的具体实现类,要获取该方法,可以使用:

Method m = Vector.class.getMethod( "addAll", Collection.class);

类似的,可以使用 isInterface() 判断一个类对象是否代表一个接口。

​ 在Java中,数组是对象,但数组的对象在运行时由JVM创建。数组的类名常量与其他的类相同。例如,Vector中的copyInto 方法接收一个Object数组,可以使用下面的语句获取这个方法:

Method m = Vector.class.getMethod( "copyInto", Object[].class);

类似的,可以使用isArray()来判断一个类对象是否代表数组。另外,可以用getComponentType()来获取数组包含的类的对象。对于二维数组来说,int[][].class.getComponentType()返回int[].class。需要注意的是,int[][]的component type是int[],而element type是 int(不知道怎么翻译这两个词最恰当,故保留原文中的说法)。

有关Method对象

常用API:

MethodDescription
Class getDeclaringClass()返回声明了该Method对象代表的方法的类的类对象
Class[] getExceptionTypes()以类对象数组的形式返回该Method对象代表的方法抛出的异常
int getModifiers()返回该Method对象代表的方法的修饰符,以int形式(?)
String getName()返回该Method对象代表的方法的方法名
Class[] getParameterTypes()以类对象数组的形式返回该Method对象代表的方法的参数
Class getReturnType()返回该Method对象代表的方法的返回值类型
Object invoke(Object obj, Object[] args)在指定的Object上调用该Method对象代表的方法

动态调用方法(Dynamic Invocation)

动态调用方法指的是程序调用编译时并未确定的方法,具体到API,指的是上面表格中的invoke方法:

Object invoke(Object obj, Object[] args)

方法的第一个参数obj表示,在这个对象上调用Method对象代表的方法;第二个参数args表示向这个方法传递的参数,可以是数组,也可以是可变个数的参数

当调用静态方法时,第一个参数会被忽略,因此也可以直接写null;对于无参方法,第二个参数可以是空数组,也可以是null

在动态调用方法时使用基本类型

在调用invoke时,第二个参数是一个Object数组。当传入基本类型参数时,需要使用Integer这样的包装类装箱。当方法的返回值是int时,将返回的Object强转为Integer并拆箱。在运行时,JVM会自动拆箱/装箱,也就是说,在实际写代码时,并不需要在传参时手动装箱,或是将返回值手动拆箱。

public class Main {
    public static void main(String[] args) throws Exception{
        //创建TestObject的类对象
        Class<TestObject> testObjectClass = TestObject.class;
        //得到类对象中的method方法
        Method method = testObjectClass.getDeclaredMethod("method", int.class, int.class);
        TestObject obj = new TestObject();
        /*在obj上调用method方法,并将返回得Object强转为Integer
        可以看到并不需要手动拆箱/装箱
        传入的第二个参数不是数组,而是可变个数的参数
        */
        int value = (Integer) (method.invoke(obj, 3, 3));
        System.out.println(value);
    }
}

class TestObject {
    public int method(int arg1, int arg2) {
        return arg1 + arg2;
    }
}

常见异常

对于Java来说,方法由方法签名和声明它的类同时决定。也就是说,两个方法的方法签名完全相同,但是在不同的类中声明,就不是同一个方法。当动态调用方法时,如果传入的Object代表的类和声明这个方法的类不一样,就会抛出IllegalArgumentException

另外,如果调用方法的类对方法没有权限(比如方法被声明为private),会抛出IllegalAccessException

IllegalArgumentException在以下两种情况会被抛出:

  • 传入的Object代表的类不支持该方法
  • 传入的数组或可变个数参数类型或个数与方法不匹配

如果调用的方法内部抛出了异常,该异常会被包装为InvocationTargetException然后抛出。

有关Field(字段)

在运行中找到Field(类的字段 )

与获取类中的方法相似,在运行时获取类的Field也有四个常用方法。getDeclaredFieldgetDeclaredFields获取仅在类中声明的常量,无论可见性;而getFieldgetFields获取类和所有父类中的public常量

MethodDescription
Field getField(String name) 返回类对象代表的类中指定public字段的Field对象
Field[] getFields()以Field对象数组的形式返回类对象代表的类中的所有public字段的Field对象
Field getDeclaredField(String name )返回仅在该类对象代表的类中声明的字段的Field对象
Field[] getDeclaredFields()以Field对象数组的形式返回所有在该类对象代表的类中声明的字段的Field对象

有关Field对象

Field对象代表类中特定的一个字段。每个Field对象都含有这个字段的元信息,包括字段的名称、声明这个字段的类以及修饰符。Field对象还提供若干获取和设置方法。下表罗列了常用的与Field对象有关的API。其中,getBooleansetBoolean方法下面的省略号表示还有许多类似的方法,这些方法都像getBooleansetBoolean方法一样返回一个基本类型的值或是设置一个基本类型的值。

MethodDescription
Class getType()返回代表了 该Field对象代表的字段 被声明时的类 的类对象
Class getDeclaringClass()返回声明了该Field对象代表的字段的类的类对象
String getName()返回字段被声明时的名称
int getModifiers()以int的形式返回该Field对象代表的字段被声明时的修饰符
Object get( Object obj )返回该Field对象代表的字段中特定对象的值
boolean getBoolean( Object obj ) ……返回该Field对象代表的字段中布尔类型字段的值
void set( Object obj, Object value )设置Field对象代表的字段中特定对象的值
void setBoolean( Object obj, boolean value )……设置该Field对象代表的字段中布尔类型字段的值

常见异常

  • 如果该字段未被定义,抛出IllegalArgumentException
  • 如果调用set方法时,传递的参数类型与Field对象中的不匹配,抛出IllegalArgumentException
  • 当调用get方法获取基本类型的返回值,而实际获取到的值不能被转换为基本类型时,同样抛出IllegalArgumentException
  • 如果字段对调用getset方法的类是不可见的,抛出IllegalAccessException对于Field和Method来说,不可见对象可以通过调用field.setAccessible(true)来越过修饰符。

动态加载类以及通过反射创建对象

动态加载类

使用静态方法Class.forName(String name), 例:

Class cls = Class.forName(dbClassName);

该方法在确保类被载入后返回一个代表该类的类对象。该过程由类加载器(class loader)实现,如果在调用forName方法之前类加载器已经加载过该类,它就会直接返回一个代表这个类的类对象;如果此前没加载过,类加载器就会在所有可能的路径下查找对应的.class文件,如果没找到,就会抛出ClassNotFoundException

(这部分是全书的第三章。作者在原书中写到,这部分更详细的内容将在第六章讨论)

通过反射创建对象

通过反射创建对象的基础

调用X.class.newInstance()(该方法在Java9之后被弃用)。使用此方法在效果上与调用X的无参构造方法是一样的。二者的不同之处在于,newInstance可以通过forName创建,即在编译时并不需要知道确切的类名,从而达到动态创建对象的效果。

使用Constructor对象

使用Constructor对象,可以获取并调用含有参数的构造方法,常见API如下表:

Method
Constructor getConstructor(Class[] parameterTypes)
Constructor getDeclaredConstructor(Class[] parameterTypes)
Constructor[] getConstructors()
Constructor[] getDeclaredConstructors()

具体用法及特性与前文写到的Method和Field的get方法都是类似的,此处不做赘述。同样的,参数中的类对象数组同样可以替换为可变个数的参数。

ConstructorMethod很像,但要调用Constructor对象代表的构造方法,应该调用newInstance()而不是invoke。Constructor对象常用的API如下表:

Method
Class getDeclaringClass()
Class[] getExceptionTypes()
int getModifiers()
String getName()
Class[] getParameterTypes()
Object newInstance( Object[] initargs )

同样的,newInstance方法中的参数也可以是可变个数的参数而不是数组。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值