Java高级之反射
Class类
从java世界理解Class
-
问题一:类和对象的关系?
-
答曰:类是抽象的概念,它是具有相同属性和方法的一组对象集合,它代表着事物的模板;而对象是能够真正“感觉的到、看得见,摸得着的”具体的实体。对对象的抽象便是类,而类的实例化结果便是对象。
-
问题二:有个可能不恰当的问法:对象的抽象是类,那类的抽象用什么表示?
- java API中有个类
java.lang.Class
,该类是用来描述类的类(比较拗口),为了帮助理解,直接上图:
- 对象是具体的实例:比如哈士奇、泰迪、正方形、三角形;
- 类是对象的抽象:比如
Dog
类、Shape
类; - 哈士奇、泰迪同属于狗,具有小狗类型的一些属性,比如吃饭、睡觉等,三角形和正方形同属于形状,有相同的一些属性,比如边长、面积等,那么针对于哈士奇、泰迪,我们选择了Dog来描述,我们可以说:哈士奇、泰迪的类型是
Dog
类,正方形、三角形的类型是Shape
类; - 那么请问:
Dog
类和Shape
类的类型是什么类?答案便是Class
类,它用来描述类的类型。
- java API中有个类
从JVM类加载过程理解Class类。
-
以上图
Dog
类入手,创建一个Dog
类以及一个测试类。//Dog类,是用来描述各个品种的狗的的类, public class Dog { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public void eat() { System.out.println("我的名字是:" + getName() + ",我开始吃饭了!"); } } class DogTest{ public static void main(String[] args) { //huskie、teddy是Dog类的具体实例。 Dog huskie = new Dog(); huskie.setName("哈士奇"); huskie.eat(); Dog teddy = new Dog(); teddy.setName("泰迪"); teddy.eat(); } }
-
过程解读:
Dog
类经过javac.exe命令以后,会生成Dog.class
字节码文件;- 之后使用java.exe对
Dog.class
字节码文件进行解释执行,即加载到内存中,该过程称为类的加载; - 加载
Dog.class
类时,JVM为其创建一个Class类型的实例,并将其关联起来。也就是说:当Dog.class
加载到内存中时,JVM为其创建了一个Class实例,这个Class实例包含了该class类型的所有完整信息。那么只要能得到某个类的Class实例,那么便可以拿到该类的类名、包名、父类、实现的接口、所有方法、字段等等。
注意:
- 类的加载过程中,加载到内存中的类,称之为运行时类,该运行时类,就作为Class的一个实例。也就是说:Class的实例对应着一个运行时的类,它会在内存中缓存一定的时间,在此时间内,我们能通过不同方式获取运行时类。
Class
实例在JVM中是唯一的,因此无论通过何种方式获取到的Class实例都是同一个实例。- 不同的
class
文件会产生不同的Class
实例,加载Dog.class
时,JVM会为Dog.class
创建一个Class
类型的实例, 加载Shape.class
时,JVM会为Shape.class
创建一个Class
类型的实例。
Class实例与类的实例
-
方便个人理解,直接上图。
-
我们对
Dog.class
字节码文件进行解释执行,加载到内存中,并且将这些静态数据转换成方法区的运行时的数据结构,之后会在堆中生成一个代表这个类的java.lang.Class
对象,用来封装类在方法区内的数据结构,可以作为方法区中类数据的访问入口(好像JDK版本不同,Class对象所在的位置也不同)。
反射概念及意义
基本理解
- 在上面内存加载时创建了
Class
实例,Class
实例上保存了这个类的所有完整信息,那么,通过Class
实例获取class
信息的方法便称为反射(Reflection); - 反射机制就是在程序的运行过程中被允许对程序本身进行操作,它是动态语言的关键;
- 在程序运行时,系统始终为所有的对象维护一个被称为运行时的类型标识。例如
Dog.class
有Dog
类的Class
类型的实例,Shape.class
有Shape
类Class
类型的实例。这些Class类型的实例跟踪着每个对象所属的类。这些Class类型的实例保存这些类的完整信息。 - 与
class
类的关系:class是描述类的一个关键字。Class却是保存着运行时信息的类。 Class
与反射配套使用,因为Class类能够帮助我们在程序运行时分析类,获取运行时类中的值。
反射能做到的事
- 拿到
Class
实例后,我们在程序运行时便可以做到:- 判断任意一个对象所属的类以及构造任意一个类的对象;
- 获取任意一个类所具有的成员变量和方法;
- 获取泛型信息(会在泛型章节详细分析);
- 调用任意一个对象的成员变量和方法;
- 处理注解;
- 动态代理。
- 可以拥有
Class
对象的类型有:- primitive type:基本数据类型;
- class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类;
- interface:接口;
- []:数组;
- enum:枚举;
- annotation:注解@interface;
- void。
反射的常用方法
获取Class类的实例
- 获取Class类的实例的四种方法,以
Dog
类为例。
//方式一:直接调用运行时类的属性:.class;
Class clazzOne = Dog.class;
System.out.println("方式一获取到的Class:" + clazzOne);
//方式二:使用运行时类的对象,调用getClass()方法;
Dog dog1 = new Dog();
Class clazzTwo = dog1.getClass();
System.out.println("方式二获取到的Class:" + clazzTwo);
//方式三:直接使用Class的静态方法:forName(String classPath);
Class clazzThree = Class.forName("com.practice.reflect.Dog");
System.out.println("方式三获取到的Class:" + clazzThree);
//方式四:使用类的加载器:ClassLoader
ClassLoader classLoader = DogTest.class.getClassLoader();
Class clazzFour = classLoader.loadClass("com.practice.reflect.Dog");
System.out.println("方式四获取到的Class:" + clazzFour);
//比较获取到的是否是同一个Class
Boolean bool = (clazzOne == clazzTwo) && (clazzTwo == clazzThree) && (clazzThree == clazzFour);
System.out.println(bool);
获取类加载器
- 类加载器有如下几种类型
- 自定义类加载器 ➡️ 系统类加载器 ➡️ 扩展类加载器 ➡️ 引导类加载器
- 系统类加载器(System Classloader):负责java –classpath 或 –D java.class.path所指的目录下的类与jar包装入工作 ,是最常用的加载器;
- 扩展类加载器(Extension Classloader):负责jre/lib/ext目录下的jar包或 –D java.ext.dirs 指定目录下的jar包装入工作库;
- 引导类加载器(Bootstap Classloader):用C++编写的,是JVM自带的类加载器,负责Java平台核心库,用来装载核心类库。该加载器无法直接获取。
public static void main(String[] args) {
//拿到DogTest类的类加载器,自定义的类是系统类加载器进行加载
ClassLoader classLoaderOne = DogTest.class.getClassLoader();
System.out.println("系统类加载器classLoaderOne:" + classLoaderOne);
//获取一个系统类加载器
ClassLoader classLoaderOne1 = ClassLoader.getSystemClassLoader();
System.out.println("系统类加载器classLoaderOne1:" + classLoaderOne1);
System.out.println(classLoaderOne1 == classLoaderOne);
//系统类加载器可以以流的方式创建一个资源 文件目录在当前src下
InputStream is = classLoaderOne1.getResourceAsStream("jdbc.properties");
//调用系统类加载器的getParent():获取扩展类加载器
ClassLoader classLoaderTwo = classLoaderOne.getParent();
System.out.println("扩展类加载器classLoaderTwo:" + classLoaderTwo);
//调用扩展类加载器的getParent():无法获取引导类加载器
//引导类加载器主要负责加载java的核心类库,无法加载自定义类的。
ClassLoader classLoaderThree = classLoaderTwo.getParent();
System.out.println("引导类加载器classLoaderThree:" + classLoaderThree);
}
创建运行时类的对象
-
获取到Class类的实例后,我们可以创建运行时类的对象;比如通过Dog.class获取到对应的Class实例后,创建一个Dog对象名为Labrador拉布拉多犬。
//通过Dog.class拿到Class对象后 Class clazz = Dog.class; //使用Class的newInstance()方法创建对象,它实际上是调用了运行时类的空参的构造器 //使用条件: 1.运行时类必须提供空参的构造器 2.空参的构造器的访问权限得够,通常为public。 Dog Labrador = (Dog)clazz.newInstance(); //也可以通过构造器创建 有这样的构造器:Dog(String name) Constructor constructor = clazz.getConstructor(String.class); Dog d = (Dog)constructor.newInstance("Labrador");
反射各方法梳理
类的属性
-
首先拿到
Class
实例。Class clazz = Dog.class;
-
通过Class拿到类的属性
//获取当前运行时类及其父类中声明为public访问权限的属性 Field[] fields = clazz.getFields(); //获取当前运行时类中声明的所有属性。(不包含父类中声明的属性) Field[] declaredFields = clazz.getDeclaredFields();
-
通过上面拿到的属性进而拿到这个属性的其他信息
getModifiers()
:返回属性的修饰符,是一个int
;getType()
:返回字段类型,也是一个Class
实例;getName()
:返回属性名称;
Field[] fields1 = clazz.getDeclaredFields(); for (Field field : fields1) { //以 private String name; 为例 //返回权限修饰符 private int m = field.getModifiers(); System.out.print(Modifier.toString(m) + "\t"); //返回该字段的类型 String Class s = field.getType(); System.out.print(s.getName() + "\t"); //返回该字段的名字 name String name = field.getName(); System.out.print(name + "\t"); }
-
拿到指定属性的值,并对指定属性的值进行操作
Dog teddy = new Dog(); teddy.setName("泰迪"); Class c = teddy.getClass(); Field field = c.getDeclaredField("name"); //如果不设置 会报错:IllegalAccessException 因为name的属性是private的。 field.setAccessible(true); Object value = field.get(teddy); System.out.println("name属性的值为" + value); //设置name的值 第一个参数是哪一个类的实例,第二个参数是要设置的值。 field.set(teddy, "哈士奇"); System.out.println("修改后的值为" + teddy.getName());
-
方法总结
Field[] getFields()
:获取所有public的field(包括父类);Field[] getDeclaredFields()
:获取当前类的所有field(不包括父类);Field getField(name)
:根据字段名获取某个public的field(包括父类);Field getDeclaredField(name)
:根据字段名获取当前类的某个field(不包括父类)。
类的方法
-
通过
Class
实例可以获取到方法Method
信息//getMethods():获取当前运行时类及其所有父类中声明为public权限的方法 Method[] methods = clazz.getMethods(); //getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法) Method[] methods1 = clazz.getDeclaredMethods(); //获取某个public的Method(包括父类) Method method1 = clazz.getMethod("play"); //获取当前类的某个Method(不包括父类) 无参 Method method = clazz.getDeclaredMethod("sleep");
-
通过上面拿到的方法,可以进一步拿到该方法的其他信息。
getAnnotations()
:获取方法声明的注解;getModifiers()
:返回方法的修饰符,它是一个int
,不同的bit表示不同的含义;getReturnType()
:返回方法返回值类型;getName()
:返回方法名称;getParameterTypes()
:返回方法的参数类型,是一个Class数组;getExceptionTypes()
:抛出的异常。
//getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法) Method[] methods1 = clazz.getDeclaredMethods(); for (Method method : methods1) { //一、获取该方法声明的注解 Annotation[] annotations = method.getAnnotations(); for (Annotation annotation : annotations) { System.out.println("方法的注解为:" + annotation); } //二、获取权限修饰符 System.out.print(Modifier.toString(method.getModifiers()) + "\t"); //三、获取返回值类型 System.out.print(method.getReturnType().getName() + "\t"); //四、获取方法名 System.out.printf(method.getName()); //五、获取参数列表 Class[] parameterTypes = method.getParameterTypes(); //六、获取抛出的异常 Class[] exceptionTypes = method.getExceptionTypes(); }
-
调用方法
Object invoke(Object instance, Object... parameters)
调用某个对象的方法,第一个参数是对象实例,即在哪一个对象上调用该方法;第二个参数是参数列表。
Class clazz = Dog.class; //创建运行时类的对象 Dog dog1 = (Dog)clazz.newInstance(); //以该方法为例:public String play(String toy),获取指定的方法 Method method = clazz.getDeclaredMethod("play",String.class); //确保当前方法可以访问 可以调用private修饰的方法, //不加的话,private方法会报错:IllegalAccessException method.setAccessible(true); //调用方法:调用方法的invoke,Object invoke(Object instance, Object... parameters) // 参数1:对象实例-->dog1 参数2:形参列表; 调用静态方法时,第一个参数传null。 //方法的返回值为调用方法的返回值 Object returnObj = method.invoke(dog1, "篮球"); System.out.println(returnObj);
类的构造器
- 通过Class对象,可以拿到当前运行时类的构造信息
getConstructor(Class...)
:获取当前运行类中某个public
的Constructor
;getDeclaredConstructor(Class...)
:获取当前运行类中某个Constructor
;getConstructors()
:获取当前运行类中所有public
的Constructor
;getDeclaredConstructors()
:获取当前运行类中所有Constructor
。
//Dog类的实例化
//1、使用new关键字
Dog teddy = new Dog();
teddy.setName("teddy");
//2、使用反射的方式
//2.1:使用的是无参的构造方法;2.2:权限修饰符为public
Dog dog1 = Dog.class.newInstance();
dog1.setName("田园犬");
//3、使用反射调用任意的构造方法 例:public Dog(String name);
Class clazz = Dog.class;
//3.1、取当前运行时类中声明为public的构造器
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
System.out.println();
//3.2、获取当前运行类中声明的所有构造器的方法
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor);
}
//3.3、获取当前运行类中的String类型参数的构造方法
Constructor constructor = clazz.getConstructor(String.class);
//3.4、保证此构造器是可访问的
constructor.setAccessible(true);
//3.5、调用构造方法创建对象
Dog dog2 = (Dog)constructor.newInstance("哈士奇");
获取类的其他信息
- 除下以上常用的方法之外,还可以通过Class实例获取该类的其他信息
Class getSuperclass()
获取运行时的父类;Type getGenericSuperclass()
获取带泛型父类;Type[] getActualTypeArguments()
获取带泛型的父类的泛型;
Class[] getInterfaces()
:获取当前类实现的所有接口;Package getPackage()
:获取当前类所在包;Annotation[] getAnnotations()
:获取运行时类声明的注解。
总结
- 反射是框架的灵魂,学好反射才能深入理解框架。
原创不易,欢迎转载,转载时请注明出处,谢谢!
作者:潇~萧下