JAVA反射(二)

本文详细探讨了JAVA反射机制,通过实例讲解如何在运行时创建类的实例、获取构造器、属性和方法。强调了`getConstructors()`与`getDeclaredConstructors()`、`getFields()`与`getDeclaredFields()`、`getMethods()`与`getDeclaredMethods()`的区别。还介绍了如何动态设置和获取属性值,以及调用方法,包括私有方法的调用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在之前有写了关于JAVA反射的理解以及几种用法,今天主要想记一下常用一些功能以及一些注意的地方,为了使功能尽量全面,我们以一个包含比较丰富的实体类为例:
首先是新建一个自定义的注解、以及接口:
在这里插入图片描述
在这里插入图片描述
接下来新建一个Creature类:
在这里插入图片描述
接下来新建一个Person类,继承自Creature类,实现上面的自定义接口和一个自带的Comparable接口,以及引用自定义注解:
在这里插入图片描述
其中Person类属性有姓名name以及年龄age,分别用不同的访问修饰符:
在这里插入图片描述
有参无参构造器使用不同的访问修饰符:
在这里插入图片描述
setName方法传入多个变量并有输出语句:
在这里插入图片描述
声明一个无返回值和参数的eat()方法:
在这里插入图片描述
声明一个带自定义注解并且返回值为String的sleep()方法,并且抛出运行时异常:
在这里插入图片描述
再声明两个内部类Computer以及Mobile:
在这里插入图片描述
以及重写tostring方法等等,就不一一展示了。
首先来看在运行时创建一个类的实例,首先是创建Class实例,上一次已经说过:

Class<Person> clazz=Person.class;

使用一个带Person泛型的Class类接收,再调用该对象的.newInstance()方法创建一个Person实例:

		Person person=clazz.newInstance();
        System.out.println(person);

运行结果:
在这里插入图片描述
同样我们也可以使用forName()加载全类名创建一个Class实例:

  	    Class<Person> clazz2= (Class<Person>) Class.forName("com.lya.Reflection.Person");
        Person person=clazz2.newInstance();
        System.out.println(person);

从结果我们不难看出,newInstance()方法默认调用无参构造器。

接下来看在运行时获取类的构造器,注意在上面我们声明Person构造器时,两个有参与无参的构造器访问修饰符不同。
首先同样是获取Class实例:

Class clazz=Person.class;

接下来使用reflect包中的Constructor类,声明一个该类型的数组,调用class实例的getConstructors()方法对所有构造器进行获取:

 Constructor[] cons=clazz.getConstructors();
        for (int i = 0; i <cons.length ; i++) {
            System.out.println(cons[i]);
        }

运行结果:
在这里插入图片描述
只打印出了修饰符为public的无参构造器,说明另一构造器没有获取到,此时我们调用另一方法getDeclaredConstructors()即可:

 Constructor[] cons1=clazz.getDeclaredConstructors();
        for (int i = 0; i <cons1.length ; i++) {
            System.out.println(cons1[i]);
        }

运行结果:
在这里插入图片描述
若此时我们将有参构造器改为private修饰,再次使用两种方法分别运行:
getConstructors():
在这里插入图片描述
getDeclaredConstructors():
在这里插入图片描述
由此说明,getConstructors()方法可以获取所有public修饰的构造器,而getDeclaredConstructors()方法可以获取所有已经声明的构造器,包括私有的。

类似地,我们还可以获取运行时类的属性、方法等:
获取属性:

 Class clazz=Person.class;
        Field[] fields=clazz.getFields();
        for (int i = 0; i <fields.length ; i++) {
            System.out.println(fields[i].getName());
        }
        System.out.println("------------------");
        Field[] fields1=clazz.getDeclaredFields();
        for (int i = 0; i <fields1.length; i++) {
            System.out.println(fields1[i].getName());
        }

运行结果:
在这里插入图片描述
这里值得注意的是:
Person类分别有两个属性name和age,而其继承的父类Creatrue还有两个属性color和legs,当我们调用getFields()方法,获取到的是name以及color两个属性,因为这两个属性都是用public修饰,而调用getDeclaredFields()方法时,获取到的是name以及age两个属性,也就是Person类本身的属性,这也就是说明getDeclaredFields()可以获取的是不包括父类的所有已声明的属性

获取方法:

 @Test
    public void test1(){
        Class clazz=Person.class;

        Method[] methods=clazz.getMethods();
        for (int i = 0; i <methods.length ; i++) {
            System.out.println(methods[i].getName());
        }

        System.out.println("-------------------------------");
        
        Method[] methods1=clazz.getDeclaredMethods();
        for (int i = 0; i <methods1.length; i++) {
            System.out.println(methods1[i].getName());
        }
    }

通过调用getName()方法可以直接获取到方法名,运行结果:
在这里插入图片描述
通过仔细比较,发现getMethods()方法是获取了所有public修饰的方法,并且其父类以及更上一级的父类public方法全都获取到了,而getDeclaredMethods()方法则是获取Person本类中所有声明的方法,不包括父类的。

同样,我们还可以获取运行时类的其他元素,像是父类、注解、接口、内部类等等,这些东西我们在Person类中都有写好,所以可以看一看,使用方法与上面类似:

 @Test
    public void test3(){
        Class clazz=Person.class;
        //1.获取带泛型父类的类型
        Type type = clazz.getGenericSuperclass();
        System.out.println(type);
        //2.参数化类型
        ParameterizedType pt=(ParameterizedType)type;
        //3.获取真实参数类型
        Type[] types=pt.getActualTypeArguments();

        Class ptClazz=(Class)types[0];
        System.out.println(ptClazz.getName());
    }

这里说一下获取带泛型父类的类型以及该泛型类型,首先先看获取泛型父类的类型,我们调用getGenericSuperclass()方法即可,而该方法的返回值类型为一个名为Type的接口:
在这里插入图片描述
该接口包括所有数据类型,运行结果:
在这里插入图片描述
因为Creatrue类泛型为自定义T,而默认会调用toString方法,所以打印出该结果,在获取了带泛型的父类类型后,我们再看如何获取该父类泛型的类型,在上面的打印结果中,我们不难看出,获取泛型类型实际上就是获取泛型中的java.lang.String,即参数,这里我们需要将类型Type进行参数化:

 ParameterizedType pt=(ParameterizedType)type;

ParameterizedType 也是一个接口,其中包含了三个方法,而我们需要调用的是getActualTypeArguments()方法,该方法返回一个Type类型的数组,我们再新建一个Type数组进行接收:

Type[] types=pt.getActualTypeArguments();

由于该数组中只有一个类型,也就不必要遍历,我们直接输出它:
在这里插入图片描述
发现其结果是class,即输出的是修饰String的Class实例,而我们需要的只是该类型,这里可以将其强转成Class类型,再调用getName()方法进行打印,或者直接调用type接口中的getTypeName()方法,都可以获取成功:
在这里插入图片描述
其他的获取都非常类似,也就不一一演示,最后我们看一看在运行时获取并且调用运行时类的属性,比如我们要获取Person类中的name属性,首先先指定我们需要操作的属性:

   Class clazz=Class.forName("com.lya.Reflection.Person");

        Person p=(Person)clazz.newInstance();

        Field name = clazz.getField("name");

接下来我们对其进行设置:

name.set(p,"张三");

接下来我们要获取该值:

String str=(String) name.get(p);

结果:
在这里插入图片描述
接下来我们看看操作另一属性age,按照上面的方法:

  		Field age = clazz.getDeclaredField("age");
        age.set(p,18);
        System.out.println(age.get(p));

但运行后报错:
在这里插入图片描述
错误告诉我们不能访问,因为该属性由private修饰,此时我们需要对该属性进行一个权限设置即可:

age.setAccessible(true);//忽略访问权限

再次运行:
在这里插入图片描述
类似的,我们也可以获取并且调用运行时类的方法,比如我们要调用eat方法:

		Class clazz=Class.forName("com.lya.Reflection.Person");

        Person person=(Person)clazz.newInstance();

        Method m1=clazz.getMethod("eat");

        m1.invoke(person);

这里需要注意getMethod()方法以及invoke()方法:
在这里插入图片描述
getMethod()方法需要传入一个我们指定的方法名,以及每该方法每个参数的class实例,没有时则不传
在这里插入图片描述
invoke方法传入一个指定的对象,以及我们需要传入的实际参数,依照我们调用的方法实际情况进行传参,没有参数则不传,有参数时,比如我们调用setName()方法,而该方法返回值为Object,我们就新建一个Object对象进行接收,与上面类似,sleep方法由private修饰,若我们想调用,忽略其访问权限即可:

 		Method m2=clazz.getMethod("setName", String.class, double.class, int.class);
        Object obj=m2.invoke(person,"张三",99,18);
        System.out.println(obj);
        System.out.println("-----------------------");
        Method m3=clazz.getDeclaredMethod("sleep");
        m3.setAccessible(true);
        Object obj2=m3.invoke(person);
        System.out.println(obj2);

运行结果:
在这里插入图片描述
打印后我们发现,接收setName方法的object对象打印出来为空,而接收sleep的方法打印出来为我们在方法中写好的返回值,这也就是说明,invoke方法的返回值,实际上就是我们需要操作的方法的返回值,有则存在,没有则是空值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值