一、反射机制介绍
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 获取类的构造方法
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); } }