目录
一、反射机制介绍
1.1 什么是反射
Java 反射机制是Java语言一个很重要的特性,它使得Java具有了“动态性”。在Java程序运行时,对于任意的一个类,我们能不能知道这个类有哪些属性和方法呢?对于任意的一个对象,我们又能不能调用它任意的方法?答案是肯定的!这种动态获取类的信息以及动态调用对象方法的功能就来自于Java 语言的反射(Reflection)机制。
编程语言分为动态语言和非动态语言,动态语言指的是在程序运行时可以改变程序的结构或者变量的类型,常见的python、JavaScript等。Javav不是动态语言,但是反射机制的存在使得Java具有多态性,或者说Java具有”半多态性“。
1.2 反射的作用
简单来说两个作用,RTTI(运行时类型识别)和DC(动态创建)。
我们知道反射机制允许程序在运行时取得任何一个已知名称的class的内部信息,包括其modifiers(修饰符),fields(属性),methods(方法)等,并可于运行时改变fields内容或调用methods。那么我们便可以更灵活的编写代码,代码可以在运行时装配,无需在组件之间进行源代码链接,降低代码的耦合度;还有动态代理的实现等等;但是需要注意的是反射使用不当会造成很高的资源消耗!
二、创建对象过程分析
实例化一个对象时,JVM虚拟机会先去磁盘中把该类的字节文件(xxx.class文件)通过IO的形式加载到虚拟机的内存中,加载到内存后会根据字节文件形成class对象(这个class对象不是class类的实例,而是class字节文件的对象类型),该class类对象包含该类的所有信息,包括属性、成员变量、构造方法、成员方法等(也就是说字节文件对象中也和普通的类对象一样有成员变量,成员方法和构造方法等,但是和普通类对象不同的是,普通类的成员变量可能是基本数据类型或者是引用类型、其他对象类型等,但是字节文件对象的成员变量的数据类型就是成员变量对象类型或者说是成员变量类型(因为一个成员变量包含访问修饰符,变量名称和值等,这些元素构成了成员变量对象),以此类推类中的构造方法也会存放到字节文件对象的构造方法对象类型的变量中……);然后再将class对象中的成员变量在堆中进行初始化,此时才算是创建了个对象。
需要注意的是无论该类被实例化多少次,JVM的类加载器只会对该类加载一次到虚拟内存,并且在虚拟内存中缓存。每new一次JVM将成员变量在堆中初始化一次。
创建对象时内存结构
Users user = new Users();
实际上,我们在加载任何一个类时都会在方法区中建立“这个类对应的Class对象”,由于“Class对象”包含了这个类的整个结构信息,所以我们可以通过这个“Class对象”来操作这个类。
我们要使用一个类,首先要加载类;加载完类之后,在堆内存中,就产生了一个 Class 类型的对象(一个类只有一个 Class 对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象知道类的结构。这个对象就像一面镜子,透过这个镜子可以看到类的结构,所以,我们形象的称之为:反射。 因此,“Class对象”是反射机制的核心。
三、获取class字节文件对象(类对象)的三种方式
3.1 通过getClass();方法获取类的字节文件对象
package cn.it.bz.Reflect;
public class GetClass1 {
public static void main(String[] args) {
//通过getClass();方法获取类的字节文件对象
User user = new User();
Class aClass = user.getClass();
System.out.println(aClass);//调用Class类的toString方法
String name = aClass.getName(); //获取User类的全名
System.out.println(name);
User user1 = new User();
System.out.println(aClass == user1.getClass());//true,证明类字节文件对象只有一份
}
}
需要注意的是,new对象之后JVM会在将对类的字节文件对象加载到内存的方法区中。说明此时在JVM虚拟内存中已经存在了User类的字节文件对象。
3.2 通过.class 静态属性获取Class对象
package cn.it.bz.Reflect;
//通过.class 静态属性获取Class对象
public class GetClass2 {
public static void main(String[] args) {
Class<User> userClass = User.class;
Class<User> userClass1 = User.class;
System.out.println(userClass == userClass1);//true
}
}
当调用User.class时,如果JVM虚拟内存中存在User类的字节文件对象,则直接返回该字节文件对象。如果JVM虚拟内存不存在该字节文件对象,则会JVM会创建User类的字节文件对象加载到虚拟内存。
3.3 通过forName()获取Class对象
package cn.it.bz.Reflect;
public class GetClass3 {
public static void main(String[] args) throws ClassNotFoundException {
Class aClass = Class.forName("cn.it.bz.Reflect.User");//该方法是Class类下的静态方法,参数是类的全名
System.out.println(aClass);
}
}
当调用forName方法时,JVM先从虚拟内存中找有没有该类对应的字节文件对象,有就直接拿出来,没有就使用类加载器将该类加载到虚拟内存中。
四、通过字节文件对象获取类的信息
4.1 获取类的构造方法
方法名 | 描述 |
---|---|
getDeclaredConstructors() | 返回 Constructor 对象的一个数组,这些对象反映此 Class 对象表示的类声明的所有构造方法(包括所有的访问控制符修饰的构造方法)。 |
getConstructors() | 返回一个包含某些 Constructor 对象的数组,这些对象反映此 Class 对象所表示的类的所有公共(public)构造方法。 |
getConstructor(Class<?>... parameterTypes) | 返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共(public)构造方法。 |
getDeclaredConstructor(Class<?>... parameterTypes) | 返回一个 Constructor 对象,该对象反映此 Class 对象所表示的类或接口的指定构造方法。 |
package cn.it.bz.Reflect;
public class User {
private String name;
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
private User(String name) {
this.name = name;
}
private User(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
package cn.it.bz.Reflect;
import java.lang.reflect.Constructor;
public class GetConstructor {
public static void main(String[] args) throws NoSuchMethodException {
//获取类字节文件对象
Class userClass = User.class;
//获取类的全部构造方法,忽略访问控制符
Constructor[] declaredConstructors = userClass.getDeclaredConstructors();
for (Constructor declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
System.out.println("----------------");
//获取全部的被public修饰的访问控制符
Constructor[] constructors = userClass.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
System.out.println("---------------");
//获取参数是int类型的构造方法忽略访问控制符,int.class实际上就是Integer.class
Constructor declaredConstructor = userClass.getDeclaredConstructor(int.class);
System.out.println(declaredConstructor);
System.out.println("----------------");
//获取无参数的public修饰的构造方法
Constructor constructor = userClass.getConstructor();
System.out.println(constructor);
}
}
4.2 通过类的构造方法实例化对象
package cn.it.bz.Reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class GetConstructor2 {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//获取User类的字节文件对象
Class<User> userClass = User.class;
//获取类的构造方法对象
Constructor<User> constructor = userClass.getConstructor(String.class, int.class);
//通过构造方法实例化类对象
User user = constructor.newInstance("张三", 18);
System.out.println(user);
}
}
4.3 通过类的字节文件对象获取类的成员变量
方法名 | 描述 |
---|---|
getFields() | 返回Field类型的一个数组,其中包含 Field对象的所有公共(public)字段。 |
getDeclaredFields() | 返回Field类型的一个数组,其中包含 Field对象的所有字段。 |
getField(String fieldName) | 返回一个公共成员的Field指定对象。 |
getDeclaredField(String fieldName) | 返回一个 Field指定对象。 |
package cn.it.bz.Reflect;
public class User {
private String name;
public int age;
……
}
package cn.it.bz.Reflect;
import java.lang.reflect.Field;
public class GetField {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
//获取类的字节文件对象
Class<?> userClass = Class.forName("cn.it.bz.Reflect.User");
//获取被public修饰的成员变量
Field[] fields = userClass.getFields();
for (Field field : fields) {
System.out.println(field);
}
System.out.println("-----------------");
//获取类中全部的成员变量
Field[] declaredFields = userClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
System.out.println("-----------------");
//根据成员变量的名称返回被public修饰的成员变量
Field name = userClass.getField("age");
System.out.println(name);
System.out.println("---------------------");
//根据成员变量的名称返回成员变量,忽略修饰符
Field name1 = userClass.getDeclaredField("name");
System.out.println(name1);
}
}
4.4 操作成员变量
package cn.it.bz.Reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class GetField2 {
public static void main(String[] args) throws Exception {
Class<User> userClass = User.class;
//获取public修饰的叫age的成员变量
Field age = userClass.getField("age");
//向User的实例user的age赋值
User user = new User();
age.set(user,123);
//向User的实例user1的age赋值
Constructor<User> constructor = userClass.getConstructor(null);//获取无参数构造方法
User user1 = constructor.newInstance();
age.set(user1,18);
//获取成员变量的值
System.out.println("user:"+user.getAge());
System.out.println("user1:"+user1.getAge());
System.out.println("-------或者---------");
System.out.println(age.get(user));
System.out.println(age.get(user1));
}
}
4.5 通过类的字节文件对象获取类的方法
方法名 | 描述 |
---|---|
getMethods() | 返回一个Method类型的数组,其中包含 所有公共(public)方法。还会返回该类的父类的被public修饰的方法。 |
getDeclaredMethods() | 返回一个Method类型的数组,其中包含 所有方法。 |
getMethod(String name, Class<?>... parameterTypes) | 返回一个公共的Method方法对象。 |
getDeclaredMethod(String name, Class<?>... parameterTypes) | 返回一个方法Method对象 |
package cn.it.bz.Reflect;
import java.lang.reflect.Method;
public class GetMethod {
public static void main(String[] args) throws Exception {
User user = new User();
Class<? extends User> aClass = user.getClass();
//获取全部的方法(包含当前User类的全部方法和其继承父类的公共方法)
Method[] methods = aClass.getMethods();
for (Method method: methods) {
System.out.println(method);
}
System.out.println("----------------------");
//根据方法名和参数获得指定方法
Method setName = aClass.getMethod("setName", String.class);
System.out.println(setName);
System.out.println("----------------------");
//获取私有方法
Method getName = aClass.getDeclaredMethod("suibian");
System.out.println(getName);
}
}
4.6 调用方法
package cn.it.bz.Reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class GetMethod2 {
public static void main(String[] args) throws Exception {
Class<User> userClass = User.class;
//获取方法区(也是字节文件对象)中的方法
Method setName = userClass.getMethod("setName", String.class);
//获取无参构造
Constructor<User> constructor = userClass.getConstructor(null);
//实例化User类
User user = constructor.newInstance();
//执行setName方法,第一个参数表示是哪个实例的setName方法,第二个参数表示执行该方法需要的参数。
setName.invoke(user, "小明");
//获取成员变量的值
Method getName = userClass.getMethod("getName");
Object invoke = getName.invoke(user);//表示执行user实例的getName方法,返回值是Object实际上是String类型
System.out.println(invoke);
}
}
4.7 获取类的其他信息
package cn.it.bz.Reflect;
public class GetClassInfo {
public static void main(String[] args) {
Class<User> userClass = User.class;
//获取类的类名
String name = userClass.getName();
System.out.println("类的名称:"+name);
//获取该类的包名
Package aPackage = userClass.getPackage();
String packageName = aPackage.getName();
System.out.println("该类所在的包名:"+packageName);
//获取该类的父类(Java不支持多继承,只有一个父类)
Class superclass = userClass.getSuperclass();
String superName = superclass.getName();
System.out.println("该类的父类:"+superName);
//获取该类实现的接口(一个类可以实现多个接口)
Class[] interfaces = userClass.getInterfaces();
for (Class anInterface: interfaces) {
System.out.println("该类实现的接口:"+anInterface.getName());
}
}
}
五、小案例
需求:根据给定的方法名顺序来决定方法的执行顺序。
向main方法中String数组中传递参数:
package cn.it.bz.Reflect;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
class Reflect{
public void method1(){
System.out.println("method1执行");
}
public void method2(){
System.out.println("method2执行");
}
public void method3(){
System.out.println("method3执行");
}
}
public class ReflectDemo {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
Reflect reflect = new Reflect();
if (args.length > 0) {
//获取Reflect类的字节文件对象
Class<Reflect> reflectClass = Reflect.class;
// 获取Reflect的所有方法
Method[] methods = reflectClass.getMethods();
for (String arg : args) {
for (int i = 0; i < methods.length; i++) {
if(arg.equalsIgnoreCase(methods[i].getName())){
methods[i].invoke(reflect); //没有参数就不给
}
}
}
}else {
reflect.method1();
reflect.method2();
reflect.method3();
}
}
}
六、反射机制的效率
由于Java反射是要解析字节码,将内存中的对象进行解析,包括了一些动态类型,而JVM无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多!
反射机制的效率测试
package cn.it.bz.Reflect;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws Exception {
//获取当前时间毫秒数
long l = System.currentTimeMillis();
//实例化对象
User user = new User();
//获取类字节文件对象
Class<? extends User> aClass = user.getClass();
//获取setName方法
Method setNameMethod = aClass.getMethod("setName", String.class);
//执行1000次
for (int i = 0; i < 1000000000 ; i++) {
setNameMethod.invoke(user, "name");
}
long l1 = System.currentTimeMillis();
System.out.println("反射耗时::"+(l1-l));
System.out.println("----------------------");
long l2 = System.currentTimeMillis();
User user1 = new User();
for (int i = 0; i <1000000000;i++ ) {
user1.setName("哈哈哈");
}
long l3 = System.currentTimeMillis();
System.out.println("非反射射耗时::"+(l3-l2));
}
}
七、setAccessible方法
setAccessible是启用和禁用访问安全检查的开关。值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查;默认值为false。
由于JDK的安全检查耗时较多.所以通过setAccessible(true)的方式关闭安全检查就可以达到提升反射速度的目的。
7.1 默认情况下获取private修饰的成员变量
package cn.it.bz.Reflect;
import java.lang.reflect.Field;
public class Test2 {
public static void main(String[] args) throws Exception {
User user = new User();
Class<? extends User> aClass = user.getClass();
//获取private修饰的成员变量name
Field name = aClass.getDeclaredField("name");
//向成员变量赋值
name.set(user, "张三");
//返回user对象的name属性
String o =(String) name.get(user);
System.out.println(o);
}
}
7.2 忽略JDK安全检查
package cn.it.bz.Reflect;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Test2 {
public static void main(String[] args) throws Exception {
User user = new User();
Class<? extends User> aClass = user.getClass();
//获取private修饰的成员变量name
Field name = aClass.getDeclaredField("name");
//忽略安全检查
name.setAccessible(true);
//向成员变量赋值
name.set(user, "张三");
//返回user对象的name属性
String o =(String) name.get(user);
System.out.println(o);
System.out.println("----------------------");
Method suibianMethod = aClass.getDeclaredMethod("suibian");
//忽略安全检查
suibianMethod.setAccessible(true);
//执行suibianM方法
suibianMethod.invoke(user);
}
}