Java 反射
2022/2/4 11:45:18
本文主要是介绍Java 反射,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
参考:
《尚硅谷Java入门视频教程》https://www.bilibili.com/video/BV1Kb411W75N
《【狂神说Java】Java零基础学习视频通俗易懂》https://www.bilibili.com/video/BV12J41137hu
推荐看尚硅谷的视频,更加详细
动态/静态语言
动态语言是在运行时能动态改变结构的语言,例如PHP,所以可以用一句话木马执行很多命令。
静态语言是在运行时不能动态改变结构的语言,如Java、C等。
反射机制能让静态语言Java获得一些动态特性,变为准动态语言。
反射是什么
Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,形象地称之为:反射。
反射,要注意这个“反”字,例如下图:
优缺点
优点:可以实现动态创建对象和编译,体现出很大的灵活性
缺点:对性能有影响。使用反射基本上是一种解释操作,我们告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于直接执行相同的操作。
下面是一个测试性能的例子:
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; // 分析性能问题 public class Test10 { // 普通方式调用 public static void test01() { User user = new User(); long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000000; i++) { user.getName(); } long endTime = System.currentTimeMillis(); System.out.println("普通方式执行10亿次:" + (endTime - startTime) + "ms"); } // 反射方式调用 public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { User user = new User(); Class c1 = user.getClass(); Method getName = c1.getDeclaredMethod("getName", null); long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000000; i++) { getName.invoke(user, null); } long endTime = System.currentTimeMillis(); System.out.println("反射方式执行10亿次:" + (endTime - startTime) + "ms"); } // 反射方式调用,关闭检测 public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { User user = new User(); Class c1 = user.getClass(); Method getName = c1.getDeclaredMethod("getName", null); getName.setAccessible(true); long startTime = System.currentTimeMillis(); for (int i = 0; i < 1000000000; i++) { getName.invoke(user, null); } long endTime = System.currentTimeMillis(); System.out.println("关闭检测执行10亿次:" + (endTime - startTime) + "ms"); } public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException { test01(); test02(); test03(); } }
执行结果:
Class对象
要学会反射,首先要学习一下Class对象,要学习Class对象,首先要简单复习一下类的加载过程。
一个Class对象就是java.lang.Class
类的一个实例。
.java文件(源代码)经过javac.exe编译后生成一个或多个.class文件(字节码文件),使用java.exe对某个字节码文件进行解释运行,相当于将某个字节码文件加载到内存中,这就是类的加载。这个加载到内存中的类,就是java.lang.Class
的一个实例,也称为运行时类。
上面说过,这个java.lang.Class
对象包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。通过这个对象,可以获得其对应类的属性、方法、构造器、实现的接口等。那我们怎么获取这个对象呢?有三种方式可以获取一个类的Class对象:
-
调用运行时类的class属性:
Class clazz1 = User.class;
-
通过运行时类的对象,调用getClass():
User u1 = new User(); Class clazz2 = u1.getClass();
-
调用Class的静态方法:forName(String classPath)
Class clazz3 = Class.forName("com.a.reflection.User");
。forName方法既可以获取我们自己写的类的Class对象,也可以获取系统类的Class对象。
上面三种方式如果获取的是同一个类的Class对象,则它们获取的是同一个对象,也就是 clazz1 == clazz2 == clazz3,因为一个类同一时间只会在内存中生成一个Class对象。加载到内存中的运行时类,会缓存一定的时间,在此时间之内,我们可以通过不同的方式来获取此运行时类。
除了类,还可以有Class对象的类型有:interface(接口)、数组(对于数组来说,只要数组的元素类型与维度一样,就是同一个Class)、enum(枚举)、annotation(注解)、基本数据类型、void(void也可以看作一种类型)。
下面是Class对象的一些注意点:
- Class对象只能由系统建立
- 一个加载的类在JVM中只会有一个Class实例
- 一个Class对象对应的是一个加载到JVM中的.class文件
- 每个类的实例都会记得自己是由哪个Class实例所生成
- 通过Class对象可以完整地得到一个类中的所有被加载的结构
- Class类是Reflection的根源,针对任何想动态加载、运行的类,唯有先获得相应的Class对象。
类的加载
下面简单讲一下类的加载过程,类的加载分为3个过程(加载、链接、初始化):
- 加载:使用java.exe将字节码文件内容加载到内存中,并生成一个代表这个类的
java.lang.Class
对象。 - 链接:为类变量(static)分配内存并设置默认初始值。
- 初始化:使用类构造器<clinit>()方法对类进行初始化,顺序是:静态变量显式赋值/静态代码块 => 匿名代码块 => 构造方法。初始化一个类时,如果发现其父类还没有被初始化,则会先初始化它的父类。
反射的实际应用
创建运行时类的对象
newInstance()
: 创建对应的运行时类的对象
Class<Person> clazz = Person.class; Person person = clazz.newInstance();
只有通过构造器才能构造对象,newInstance()
内部也是调用了对应类的无参构造器。
newInstance()
能正常地创建运行时类的对象的条件:
- 运行时类必须提供空参的构造器。
- 运行时类的空参构造器可被访问,通常设置为
public
。
获取运行时类的完整结构
包括获取运行时类的所有方法、构造器、父类、接口、所在包、注解等,都是用形如getXxx()
的方法来实现的。
Class clazz = Person.class; // getFields(): 获取当前类及其父类的所有public属性 Field[] fields = clazz.getFields(); // getDeclaredFields(): 获取当前运行时类声明的所有属性,包括所有权限(不包含父类中声明的属性)。 Field[] declaredFields = clazz.getDeclaredFields(); // getMethods(): 获取当前运行时类及其父类声明的所有public方法。 Method[] methods = clazz.getMethods(); // getDeclaredMethods(): 获取当前运行时类声明的所有方法,包括所有权限(不包含父类中声明的方法)。 Method[] declaredMethods = clazz.getDeclaredMethods(); // getConstructors(): 获取当前运行时类中声明为public的构造器 Method[] methods = clazz.getConstructors(); // getDeclaredConstructors(): 获取当前运行时类中声明的所有构造器 Method[] declaredMethods = clazz.getDeclaredConstructors(); // getSuperclass(): 获取运行时类的父类 Class superclass = clazz.getSuperclass(); Class[] interfaces = clazz.getInterfaces(); // 要获取父类的接口,可以先获取父类,再获取接口 Class[] interfaces1 = clazz.getSuperclass().getInterfaces(); // 获取当前运行时类所在的包 Package pack = clazz.getPackage(); // 获取运行时类声明的注解 Annotation[] annotations = clazz.getAnnotations();
调用运行时类的指定结构
操作运行时类中的指定属性
Class clazz = Person.class; // 创建运行时类的对象 Person p = (Person) clazz.newInstance(); // 获取指定的public属性 Field id = clazz.getField("id"); // getDeclaredField(String fieldName): 获取运行时类中指定的属性 Field name = clazz.getDeclaredField("name"); // 使当前属性可访问 name.setAccessible(true); /* 设置当前属性的值 set(): 参数1:指明设置哪个对象的属性 参数2:将此属性值设置为多少 */ id.set(p, 1001); // 设置静态属性的值 id.set(null, 1001); /* 获取当前属性的值 get(): 参数1:获取哪个对象的当前属性值 */ int pId = (int) id.get(p); // 获取静态属性的值 int pId2 = (int) id.get(null);
操作运行时类中的指定方法
Class clazz = Person.class; // 创建运行时类的对象 Person p = (Person) clazz.newInstance(); /* 1. 获取指定的某个方法 getDeclaredMethod(): 参数1:指明获取的方法的名称 参数2:指明获取的方法的形参列表 */ Method show = clazz.getDeclaredMethod("show", String.class); // 2. 保证当前方法是可访问的 show.setAccessible(true); /* 3. invoke(): 参数1:方法的调用者 参数2:给方法形参赋值的实参 invoke()的返回值即为对应类中调用的方法的返回值。 */ Object returnValue = show.invoke(p, "CHN"); // 调用静态方法 // private static void showDesc() Method showDesc = clazz.getDeclaredMethod("showDesc"); showDesc.setAccessible(true); // 如果调用的运行时类中的方法没有返回值,则此invoke()返回null Object returnVal = showDesc.invoke(null);
调用运行时类中的指定构造器
Class clazz = Person.class; // private Person(String name) /* 1. 获取指定的构造器 getDeclaredConstructor(): 参数:指明构造器的参数列表 */ Constructor constructor = clazz.getDeclaredConstructor(String.class); // 2. 保证此构造器是可访问的 constructor.setAccessible(true); // 3. 调用此构造器创建运行时类的对象 Person per = (Person) constructor.newInstance("Tom");
这篇关于Java 反射的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23Springboot应用的多环境打包入门
- 2024-11-23Springboot应用的生产发布入门教程
- 2024-11-23Python编程入门指南
- 2024-11-23Java创业入门:从零开始的编程之旅
- 2024-11-23Java创业入门:新手必读的Java编程与创业指南
- 2024-11-23Java对接阿里云智能语音服务入门详解
- 2024-11-23Java对接阿里云智能语音服务入门教程
- 2024-11-23JAVA对接阿里云智能语音服务入门教程
- 2024-11-23Java副业入门:初学者的简单教程
- 2024-11-23JAVA副业入门:初学者的实战指南