Java反射详解
原文地址:反射详解
为什么单独又写一篇呢。。。。因为到后面框架时,我发现自己有点基础不牢,而且排版好像也有点问题(不知道为什么从本地copy到博客的md编辑器后有些地方给我变成代码块了)。。。所以单独写一篇详细点的。
Class类
阅读时请注意大小写
在Java中用来表示运行时类型信息的对应类就是Class类,Class类也是一个实实在在的类,存在于JDK的
java.lang
包中。java程序编译生成
.class
文件,
.class
里面的内容就是你编译的
.java
文件的内容,不过是被编译为字节形式,给JVM查看的。而用来描述
.class
文件的类,就是
Class
类
Class类被创建后的对象就是Class对象。实际上在Java中每个类都有一个Class对象,每当我们编写并且编译一个新创建的类就会产生一个对应Class对象并且这个Class对象会被保存在同名.class文件里,编译后的字节码文件保存的就是Class对象,那为什么需要这样一个Class对象呢?是这样的,当我们new一个新对象或者引用静态成员变量时,Java虚拟机(JVM)中的类加载器子系统会将对应Class对象加载到JVM中,然后JVM再根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值。需要特别注意的是,手动编写的每个class类,无论创建多少个实例对象,在JVM中都只有一个Class对象,即在内存中每个类有且只有一个相对应的Class对象。而且Class对象只能由JVM来进行创建和销毁。
另外需要知道的一点是
在JVM创建的Class类型的对象中
成员变量被封装为Field对象
构造方法被封装为Constructor对象
成员方法被封装为Method对象
其实在这里我初学时就有些疑惑,既然Class类是按照我自己定义的类一起创建的,而且跟我自己定义的类是一模一样的。当我需要调用里面的方法时,我可以使用自己定义的类来调用啊。只不过要把方法定义为静态方法罢了。
但是,我后面想到一个问题,这是面对的普通的方法,那么在最初的构造方法,我要怎么来调用呢。也就是说,我怎么通过类来调用构造方法。
上面是我们构造一个A类对象时需要进行的一步。众所周知,对象是能够调用方法的,那么我如何通过对象a来调用A类的构造方法?
调用不到,对吧。因为a本来就是由构造方法构造出来的,a根本就调用不到构造方法。而且,再进一步想,A这个类也调用不到自己的构造方法。
那么,如果,我要在某些情况下,使用到A的构造方法,或者说,将A的构造方法作为一个函数的参数传递,那该怎么办?
这就需要用到下面将写的反射技术,Class类也是反射的基础
类的加载
第一次使用类的信息时,
.class
字节码文件会被加载到内存当中,存储在方法区中。
JVM会为加载到方法区的
.class
文件创建一个Class类型对象,该对象保存在堆内存中,等同于堆内存中的Class类型的对象,指向了方法区中的
.class
文件
一个类只会被加载一次,所以Class类型的对象只有一个,且任意类型都有对应的Class类型的对象
反射
那么,什么叫反射呢?我这里用我的理解来讲一下,反射就是通过Class类来获取
.class
文件的内容,因为
.class
的内容记载了类的所有属性(成员变量,构造方法,成员方法等)。
当然,具体的定义。。。可以去查看官方文档
获取Class对象
- 通过
类的成员方法java.lang.Object
public Class<?> getClass()
Person p = new Person(); Class c1 = p.getClass(); System.out.println(c1);
- 通过class属性
Class<Person> c2 = Person.class; System.out.println(c2);
- 通过
类的静态方法java.lang.Class
: 获取方法参数指定类名对应的Class类型的对象(使用得最多,因为不需要知道具体的类的名字)public static Class<?> forName(String className)
Class<?> c3 = Class.forName("domain.Person"); System.out.println(c3);
总结一下
获取class对象方式 | 作用 | 应用场景 |
---|---|---|
Class.forName(“全类名”) | 通过指定的字符串路径获取 | 多用于配置文件,将类名定义在配置文件中。读取文件,加载类 |
类名.class | 通过类名的属性class获取 | 多用于参数的传递 |
对象.getClass() | 通过对象的getClass()方法获取 | 多用于对象的获取字节码的方式 |
其他的一些方法
String getSimpleName(); 获得简单类名,只是类名,没有包
String getName(); 获取完整类名,包含包名+类名
- T newInstance() ;创建此 Class 对象所表示的类的一个新实例。要求:类必须有public的无参数构造方法
获取构造方法对象
在通过反射获取构造方法之前,请一定保证存在了相应的Class对象。
-
: 获取public修饰的所有的构造方法,每个构造方法被封装成了Constructor类型的对象,被存储数组中。Constructor类是专门用来描述构造方法的一个类public Constructor[] getConstructors()
Constructor<?>[] cons = c.getConstructors(); //遍历 for (Constructor<?> con : cons) { System.out.println(con); }
-
: 获取指定参数类型的public修饰的构造方法。如果不存在对应的构造方法,则会抛出public Constructor<T> getConstructor(Class... parameterTypes)
java.lang.NoSuchMethodException
异常。
参数说明:
Class... parameterTypes
: 必须传递数据类型对应的Class对象,而且是可变参数,可以传递数组,参数列表,不传递(获取空参)。
例如:获取 构造方法
对应的Construtor对象,填写的参数列表为public Person(String name, int age) { ...}
,String.class
int.class
//获取public修饰的空参构造方法对象 Constructor<?> con = c.getConstructor(); System.out.println(con); //获取public修饰的第一个参数是String类型,第二个参数是int类型的构造方法对象 Constructor<?> con2 = c.getConstructor(String.class, int.class); System.out.println(con2);
私有方法也能通过其他方法反射出来,但最好不要这么做这样的暴力反射,因为破坏了封装性。如果有这样的需求,查阅API文档:
getDeclaredConstructor
等相关方法
使用构造方法对象构造对象
-
类的成员方法:java.lang.reflect.Constructor
:执行构造方法对象,创建一个具体的对象public Object newInstance(Object... params)
//获取Class类型的对象 Class<?> c = Class.forName("domain.Person"); //通过Class类型的对象获取空参构造方法对象 Constructor<?> con = c.getConstructor(); //执行构造方法对象,创建一个具体的对象 //执行的是空参构造方法对象,不需要传递参数,返回的类型是Object,强转为Person类 Person p = (Person)con.newInstance();
-
类的成员方法:java.lang.Class
执行空参构造方法,创建一个对象。public T newInstance()
//获取Class类型的对象 Class<?> c = Class.forName("domain.Person"); //java.lang.Class类 成员方法: newInstance Person p = (Person)c.newInstance();
获取成员方法对象
在获取成员方法之前,需要获取Class类型的对象
-
类的成员方法:java.lang.Class
:获取所有public修饰的成员方法,包含继承下来的方法,每个成员方法被封装成了一个Method对象,存储Method数组中。public Method[] getMethods()
类是用来描述成员方法的一个类。java.lang.reflect.Method
//获取Class类型的对象 Class<?> c = Class.forName("domain.Person"); //获取所有public修饰的成员方法,包含继承下来的 Method[] ms = c.getMethods(); for (Method m : ms) { System.out.println(m); }
-
类的成员方法:java.lang.Class
获取public修饰的指定方法名称,指定参数类型对应的成员方法对象。参数列表中public Method getMethod(String name, Class... parameterTypes)
为方法名称,String name
为必须传递数据类型对应的Class对象,而且是可变参数,可以传递数组,参数列表,不传递则获取空参。Class... parameterTypes
//获取Class类型的对象 Class<?> c = Class.forName("domain.Person"); //获取public修饰的名称为toString的没有参数的方法 Method m1 = c.getMethod("toString"); System.out.println(m1); //获取public修饰的名称为setName的参数为String类型的方法 Method m2 = c.getMethod("setName", String.class); System.out.println(m2);
使用成员方法对象执行方法
使用
java.lang.reflect.Method
类的成员方法
public Object invoke(Object obj,Object... args)
来对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。参数
Object obj
为包含成员方法的对象,
Object... arg
执行方法时,该方法需要的具体的参数。返回值在没有返回值的方法返回一个null,有返回值的方法则封装为具体结果。
//1.获取Class类型的对象
Class<?> c = Class.forName("domain.Person");
//成员方法的调用,需要包含成员方法的对象
//创建一个对象
Object obj = c.newInstance();
//2.获取setName方法对象
Method setNameMethod = c.getMethod("setName", String.class);
//3.执行setName方法对象
Object result = setNameMethod.invoke(obj, "茶叶蛋");
System.out.println(result);//null
System.out.println(obj);// Person{name='茶叶蛋',age=0}
//2.获取getName方法对象
Method getNameMethod = c.getMethod("getName");
//3.执行getName方法对象
result = getNameMethod.invoke(obj);
System.out.println(result);
类加载器
将class文件(硬盘)加载到内存生成Class对象。
详情参见
https://chayedan.site/index.php/archives/23/