Java中的反射技术

news/2024/5/19 6:39:39 标签: 反射, java, 反射调用

反射技术是java的核心技术之一,虽然我们日常开发中,基本上可能用的并不多,但是它同样也是必学的。因为很多框架的设计其实都是有利用反射机制的,这意味着反射是我们向前迈进的一个重要技术。

 

一、反射是什么?

首先我们看一段普通的调用代码

先创建一个平平无奇的Person类,有一个平平无奇的work方法

java">package com.lzh.reflect;


public class Person {
    public void work(String content) {
        System.out.println(content);
    }
}

再创建个Test类进行调用

java">public class Test {
    public static void main(String[] args) {
        Person person = new Person();
        person.work("编码中");
    }
}

结果不用贴了,只是将字符串“编码中”,输出到了控制台。

 

然后现在我们利用反射机制,做相同的调用操作:

java">public class Test {
    public static void main(String[] args) throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException, ClassNotFoundException {
        Class clz = Class.forName("com.lzh.reflect.Person");
        Method method = clz.getMethod("work",String.class);
        Constructor constructor = clz.getConstructor();
        Object object = constructor.newInstance();
        method.invoke(object,"编码中");
    }
}

??

别看这一坨代码比较臃肿复杂,但是它的执行效果和前面的普通调用其实是一样的。只是它是通过反射去调用的,最明显的区别就是,反射在调用同一个类的同一个方法时,并没有指定类。听起来有点绕,但是如果你的Person和Test两个文件在不同的包下,就可以看出来,普通的调用必须import com.lzh.reflect.Person;,导入Person类。但如果是反射,就不需要导入具体的包,而是通过运行时分析参数里的类路径字符串判断出是哪个类。

站在编码者的角度,即便用了反射,我们也知道是用了哪个类,调用了哪个方法,但是对于jvm来说,它们不运行到这一步是不会知道的。在编译时,类的路径名、方法名对它来说只是个字符串参数而已。

通过反射我们甚至可以通过修改配置或传特定的参数,无需重启服务器,就能更改服务器内部的调用逻辑,所以这点也是一个安全隐患,在使用反射时一定要小心使用外部输入的参数。

 

总结:普通调用在编译后,就知道了要加载哪个类,调用这个类的哪个方法。但是反射是在运行时才知道要操作的类是什么,并且在运行时获取类的某个构造方法构建对象,然后调用类的某个方法。

 

 

二、怎么使用反射

1.获取Class

首先我们需要通过反射获取一个类的对象

①Class.forName(类的全路径) 静态方法

第一种方式,就是我们前面例子用的方法了。使用Class.forName(类的全路径) 指定类,这种方式我们需要知道类的全路径名,如果路径有误加载不到类,会抛出ClassNotFoundException异常。

java">Class clz = Class.forName("com.lzh.reflect.Person");
②.class方法

这种方法是需要导入类的,指定了类名,看起来好像有点脱裤子放屁没啥用,因为反射不就是因为不能确定类才用反射吗。但是如果我们重点关注的是反射调用哪个方法或哪个构造函数,而Class其实是已经确定的,所以并不需要在Class上费时间时,是可以用到这种方式的。

java">Class clz = Person.class;
③对象的getClass()方法

这种方式就更简单了,直接通过对象获取类,比方式②还要具体,这次连构造方法都无需关注了。同样,可能在我们只关注method的时候可以用上。

java">Person person = new Person();
Class clz1 = person.getClass();
④基本类型的Type属性

如果是基本类,我们可以通过对应包装类的TYPE属性,获取它基本类的Class,每个基本类型都有这个TYPE属性。

java">Class byteClass = Byte.TYPE;
Class shortClass = Short.TYPE;
Class integerClass = Integer.TYPE;
Class longClass = Long.TYPE;
Class charClass = Character.TYPE;
Class floatClass = Float.TYPE;
Class doubleClass = Double.TYPE;
Class boolClass = Boolean.TYPE;
或简便写成
Class byteClass = byte.class;
Class shortClass = short.class;
Class integerClass = int.class;
Class longClass = long.class;
Class charClass = char.class;
Class floatClass = float.class;
Class doubleClass = double.class;
Class boolClass = boolean.class;

也可以简便的写成int.class、byte.class这样,效果是一样的,通过反编译可以看到int.class最后实际上也是Integer.TYPE。

但是注意,这并不等于Integer.class,基本类型的Class类型和包装类的Class类型是两码事,它们并不是一个东西

⑤类Class

我们也可以在获取了子类Class对象后,通过getSuperclass()获取它的父类Class。

如下,Student的父类是Person:

java">Student student = new Student();
Class slz = student.getClass();
Class superclass = slz.getSuperclass();

 

2.获取对象

获取了Class后,我们自然需要通过某个构造方法获取这个类的对象

①通过Class对象的newInstance()方法

直接通过类获取实例,但是原理是调用无参构造方法,所以如果这个类没有无参构造方法,会抛出InstantiationException异常。

java">Class clz = Person.class;
Person person = (Person) clz.newInstance();
②通过Constructor 对象的 newInstance() 方法

如果我们想调用指定的构造方法,就得用这种方式了。先给Person类随便加一个有参的构造方法:

java">public class Person {
    //有参构造方法
    public Person(String name) {
        System.out.println(name);
    }
    public void work(String content) {
        System.out.println(content);
    }
}

然后通过clz.getConstructor(String.class) 获取指定参数的构造器(如果是获取无参构造就不传参数),这里参数也是类的Class,最后通过构造器的newInstance()方法调用构造方法,同时这里需要传入前面声明的参数:

java">Class clz = Person.class;
Constructor constructor = clz.getConstructor(String.class);
Person person = (Person) constructor.newInstance("123");

注意这里的newInstance()方法是构造器Constructor对象中的方法,和前面的Class中的不是一个方法,所以也会抛出更多种类的异常。

③私有构造方法

如果这个构造方法是私有的呢,给工具人Person类加一个私有的构造方法:

java">    private Person(Integer age) {
        System.out.println(age);
    }

如果还是用前面的getConstructor(Integer.class);就会发现抛出了NoSuchMethodException异常,找不到这个私有的构造方法,这是因为普通的getConstructor只能获取到public级别的构造方法

这时候我们只需要将getConstructor()替换成getDeclaredConstructor()方法即可,其实就是加了个关键字Declared,它可以读取到所有已声明的构造方法,这样即便是私有的也可以找到。另外还需要额外设置一下setAccessible为true,关闭反射对象的访问检查,不然也是无法访问私有构造方法的。

java">Class clz = Person.class;
Constructor constructor = clz.getDeclaredConstructor(Integer.class);
constructor.setAccessible(true);
Person person = (Person) constructor.newInstance(18);

 

3.获取成员变量

首先还是掏出工具人Person类,给它上两个变量,其中一个设为了私有。同时为了看结果,我重新生成了toString方法。

java">public String name;
private Integer age;


@Override
public String toString() {
return "Person{" + "name='" + name + '\'' + ", age=" + age + '}';
}
①通过getField()方法获取成员变量

通过getField()获取指定的成员变量,可以通过getName获取成员变量的名字,getType获取成员变量的类型

java">Class clz = Person.class;
Field field = clz.getField("name");
System.out.println(fields.getName());//获取名字
System.out.println(fields.getType());//获取类型

如果要获取值,则需要传入一个对象才行,然后直接调用get方法:

java">//初始化一个Person对象,并赋给成员变量一个值
Person person = new Person();
person.name = "张三";


//通过反射取出这个成员变量的值
Class clz = person.getClass();
Field field = clz.getField("name");//指定field是name变量
System.out.println(field.get(person));//获取传入对象person的name成员变量值
②通过field.set()为对象设置成员变量值

和get的使用方法一样,set也要传入一个Person对象,不然它怎么知道是给哪个对象设置值呢?这里我们试着用反射构造一个对象,再反射set变量属性后反射get出来:

java">Class clz = Person.class;    //声明Class
Constructor clzConstructors = clz.getConstructor();    //获取默认的无参构造器
Object person = clz.newInstance();//初始化person对象


//获取成员属性,并set值进去
Field field = clz.getField("name");
field.set(person, "马德");
//输出对象的属性
System.out.println(field.get(person));

有人可能觉得你用了同一个Field对象set完了再get,当然get的到啊。实际上就算你在get的步骤前重新初始化一个Field对象去get,结果也是一样的。field并不会绑定在某一个对象上。

③私有成员变量

和构造器一样,只需要加上Declared,用getDeclaredField() ,就可以获取私有级别的成员变量。记住,没有加Declared的方法都是只能访问public级别的,不管是构造器还是成员变量还是方法。

并且也都需要setAccessible(true);关闭访问级别的检查:

java">Field field = clz.getDeclaredField("age");
field.setAccessible(true);
field.set(person, 18);
System.out.println(field.get(person));

 

4.获取和调用方法

最后终于到核心了,利用反射调用方法

①通过getMethod()获取方法,invoke调用方法

这里我偷了个懒,直接把最前面文章开头的例子拿过来了,方法还是普通的work方法

java">    public void work(String content) {
        System.out.println(content);
    }

这次,我们应该能看懂这个调用了

java">Class clz = Class.forName("com.lzh.reflect.Person");
Method method = clz.getMethod("work",String.class);    //获取work(String)方法
Constructor constructor = clz.getConstructor();    //获取构造器
Object object = constructor.newInstance();    //获取对象实例
method.invoke(object,"编码中");    //调用object对象的work方法,并传入参数

首先通过clz.getMethod(“work”,String.class); 获取了work方法,第二个包括以后的参数代表work方法的参数,有几个就要填几个,不然如果work方法有多个重载,反射无法找到具体的方法。

最后调用时是用method.invoke(object,“编码中”); 方法,第一个参数是要执行哪个对象的work方法,和field对象的getset方法类似,第二个包括以后的参数就是work方法所需的参数了,数量要对上,不然会抛出IllegalArgumentException异常。

②调用私有方法

估计大家都猜到怎么调用私有了,getDeclaredMethod() 可以访问到所有方法,包括私有方法。记得如果没加Declared,就只能查到public级别的。所以一般我们用getDeclaredMethod就行,稳妥。

并且要记得method.setAccessible(true)。

然后我们整个最完整的反射,没有import导入Person类,完整的调用一次方法

Person类:

java">public class Person {
   
    public String name;
    private Integer age;    //私有属性年龄
   
    public Person(String name) {    //公共有参构造方法
        this.name = name;
    }
   
    private String play(String game) {        //私有有参方法,且用到了成员变量
        return this.name+"("+this.age+") "+"play ♂ " + game;
    }


}

调用的测试方法:

java">//通过反射获取Class
Class clz = Class.forName("com.lzh.reflect.Person");


//获取有参构造器,那个初始化同时会设置name的构造器
Constructor constructor = clz.getDeclaredConstructor(String.class);


//获取实例,并初始化了name属性
Object person = constructor.newInstance("比利");


//手动获取age成员属性,并设置值
Field field = clz.getDeclaredField("age");
field.setAccessible(true);
field.set(person, 28);


//调用方法
Method method = clz.getDeclaredMethod("play", String.class);
method.setAccessible(true);
Object invoke = method.invoke(person,"摔跤");
System.out.println(invoke);

这次放一次输出结果:

比利(28) play ♂ 摔跤

 

*5.一些其他的方法

再简单说明和反射相关的几个其他方法,非必读,可选择性跳过。

①获取构造器、方法、变量加s

Class对象中,我们已经知道了有getConstructor()和getDeclaredConstructor()获取构造器,getMethod()和getDeclaredMethod()是获取方法,getField()和getDeclaredField()是获取属性。

但是如果在这些方法名的尾部加个s,就代表获取全部的构造器/方法/属性,

返回类型是:Constructor或Method或Field类型的数组

java">Constructor[] clzConstructors = clz.getConstructors();
Constructor[] declaredConstructors = clz.getDeclaredConstructors();


Field[] fields = clz.getFields();


Method[] methods = clz.getMethods();
②getModifiers()

Constructor、Method、Field对象都有方法getModifiers(),返回的是一个整数值,它代表当前的方法或属性前面的修饰符相加得到的值:无任何修饰符是0 , public是1 ,private是2 ,protected是4,static是8 ,final是16。例如一个方法前面的修饰符是public static final ,那么它的值就是相加得到25。

返回类型是:int

这个方法虽然只返回一个数字,但是通过计算是一定能准确的得到修饰符的,因为都是2的倍数,所以每个返回的数字只会有计算出一种结果。

而且我们也没必要计算,反射有提供Modifier.toString() 方法,帮我们把这个数字转换为修饰符:

java">System.out.println("修饰符: "+Modifier.toString(method.getModifiers()));
③getParameterTypes()

Constructor、Method对象的方法getParameterTypes(),返回当前方法的参数列表,是数组格式的Class列表。

返回类型是:Class<?>[]

java">System.out.println(Arrays.toString(work.getParameterTypes()));

 

 

三、总结

至此,只要认真的看完并自己尝试后,我们对于反射就算没入门,也至少会用了。

我也是工作了一年后才开始学习的反射,因为工作中没怎么用过,呃,应该说就没用过。

但是当我们查看一些牛逼框架代码的源码时,就会发现里面很多地方有用到反射,就拿我们耳熟能详的Spring来说,它的核心IOC和AOP功能也是依靠了反射的。所以就算只是为了看源码,为了应付面试官,我们也应当学会反射学了反射大家都是人上人。

 

 

 

参考资料:

大白话说Java反射:入门、使用、原理

https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html

Java反射技术详解

https://blog.csdn.net/huangliniqng/article/details/88554510


http://www.niftyadmin.cn/n/859301.html

相关文章

element ui 框架的优势_element后台系统vue-admin-beautiful

今天给大家推荐一款基于vueelement-ui的绝佳的vue后台框架——vue-admin-beautiful。vue-admin-beautiful是github开源admin中最优秀的集成框架之一&#xff0c;它是一款基于vueelement-ui的绝佳的vue后台框架(基于vue3.0 最新版&#xff0c;同时支持电脑&#xff0c;手机&…

dataframe scala 修改值_pandas | 如何在DataFrame中通过索引高效获取数据?

本文始发于个人公众号&#xff1a;TechFlow&#xff0c;原创不易&#xff0c;求个关注今天是pandas数据处理专题的第四篇文章&#xff0c;我们一起来聊聊DataFrame中的索引。上一篇文章当中我们介绍了DataFrame数据结构当中一些常用的索引的使用方法&#xff0c;比如iloc、loc以…

Java中将数组转成List

1.Arrays.asList坑点说明 在开发中&#xff0c;我们有时候会需要将数组转换为集合List&#xff0c;这时候可能会想到Arrays.asList()&#xff0c;毕竟它是java提供的&#xff0c;肯定专业。。。吗&#xff1f; Integer[] a {1, 2, 3}; List<Integer> list Arrays.asL…

graphpad横坐标修改间隔大小_文字超多的PPT如何优化?这份实战案例修改全过程,从0到1教你搞定!...

之前&#xff0c;我在公众号上发布了一个 PPT 案例征集活动。这几天&#xff0c;在微信上以及邮箱中&#xff0c;陆陆续续的收到几十份投稿&#xff0c;在这里&#xff0c;要感谢大家对我的认可。今天呢&#xff0c;我们就从中选择一份进行修改&#xff0c;跟各分享一下&#x…

IDEA快捷键和代码缩写

用IDEA工作了一段时间了&#xff0c;已经越来越离不开它了&#xff0c;有时候不得不用eclipse的时候&#xff0c;感觉可真是痛苦。 另外我发现IDEA有许多好用的快捷键&#xff0c;如果练熟练了&#xff0c;不仅可以很有效地提高工作效率&#xff0c;而且被人看到了也会觉得你很…

IDEA开启Run Dashboard仪表盘

由于我们用的是多服务多模块的项目&#xff0c;所以开启run dashboard会更好管理&#xff0c;它的页面如下&#xff1a; 看起来是不是很清晰&#xff1f; 但是奇了个怪的&#xff0c;它有时候在IDEA界面里是调不出来的&#xff0c;以下是解决方式&#xff1a; 方法1&#xff…

ae中心点重置工具_小编疯了!免费分享AE自学教程视频 从入门到精通。

AE自学教程视频 从入门到精通&#xff0c;文件约为9个G&#xff0c;全部为视频课程。需要私信小编获取&#xff01;第一章 After Effects工作界面预览、系统首选项和磁盘缓存设定、丽姐APE项目文件和视频特性、理解After Effects的六个特性、导入psd ai tga格式、排序项目列表/…

从0开始学习java泛型

泛型是java中一个很重要的概念&#xff0c;虽然我们平时可能很少用上&#xff0c;但不代表就不需要学习。其实很多牛掰的框架模块&#xff0c;里面都经常使用泛型&#xff0c;随便点开几个源码就能看到了。 1.什么是泛型&#xff1f; 平时我们很少会用到泛型&#xff0c;但是它…