Java反射机制
概述
- Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
- 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了完整的类结构信息。我们可以通过这个对象看到类的结构。
- 这个Class对象就像一面镜子,通过这个镜子看到类的结构,所以我们形象的称之为:反射
实例化对象的正常方式:
引入需要的“包类”名称——>通过new实例化——>取得实例化对象
实例化对象的反射方式:
实例化对象——>getClass()方法——>得到完整的“包类”名称
补充:动态语言&静态语言
动态语言
是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构。
主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang
静态语言
与动态语言对应的,运行时结构不可变的语言就是静态语言。如Java、C、C++
Java不是动态语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用反射机制、字节码操作获得类似动态语言的特性。Java的动态性让编程的时候更加灵活
反射提供的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
反射相关的主要API
- java.lang.Class:代表一个类
- java.lang.reflect.Method:代表类的方法
- java.lang.reflect.Field:代表类的成员变量
- java.lang.reflect.Constructor代表类的构造器
疑问
通过直接new的方式或反射的方式都可以调用公共的结构,开发中到底用哪个?
建议直接new的方式
什么时候会使用反射的方式
反射具有动态性,所以当我们不确定要实例化哪个类时,可以使用反射
反射机制与面向对象中的封装行是不是矛盾的?如何看待两个技术?
肯定不矛盾啦~
封装性是将属性和方法封装起来,只对外暴露公有的属性和方法,这里的意思是,建议你使用公有的,私有的是供内部自己使用的,封装者认为你不需要使用,公有的已经足够满足需求(比如公有的方法中调用了私有方法并进行了增强,你就没必要去直接调用私有方法)。
反射是让我们能够去操作一个类的私有和公有方法和属性,让我们有这个能力,在需要的时候可以借助反射来实现对类私有方法或私有属性的操作
入门
关于java.lang.Class的理解
具体类的加载过程见JVM笔记
gitee:
https://gitee.com/importguitar/jvm.git
github:
https://github.com/importGuitar/JVM.git
-
类的加载过程
-
程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。
-
接着我们使用java.exe命令对某个字节码文件进行解释运行,相当于将某个字节码文件加载到内存中。此过程就成为类的加载。加载到内存中的类,我们就成为运行时类,此运行时类,就作为Class的一个实例。
-
即:每个运行时类都是Class类型的对象,也就是说,类也是对象,是什么类型的对象呢?是Class类型的对象。
-
如:
Class clazz = Person.class;
-
-
-
换句话说:Class的实例就对应着一个运行时类
-
加载到内存中的运行时类,会缓存一定的时间,在此时间内,我们可以以不同的方式去获取该运行时类,获取到的都是同一个运行时类。
获取Class实例的四种方式
/**
* 获取Class实例的四种方式
*/
@Test
public void testGetClass() throws ClassNotFoundException {
// 方式一:调用运行时类的属性.class
Class<Person> personClass = Person.class;
// 方式二:通过运行时类的对象,调用getClass()方法
Person person = new Person();
Class<? extends Person> personClass1 = person.getClass();
// 方式三:调用Class的静态方法forName(String classPath)
// 该方式比较常用,因为:
// 方式一已经写死了具体类,编译期就确定了要加载哪个类;
// 方式二已经创建了具体对象,没必要再用反射
// 方式三可以将要加载的类以字符串参数的形式传入,来动态加载
// 方式四要调用ClassLoader,不常用
Class<?> personClass2 = Class.forName("cn.shangxiaoying.reflect.Person");
// 方式四:使用类的加载器
ClassLoader classLoader = Person.class.getClassLoader();
Class<?> personClass3 = classLoader.loadClass("cn.shangxiaoying.reflect.Person");
// 获取的是同一个运行时类的Class对象,true
System.out.println(personClass == personClass1 && personClass == personClass2 && personClass == personClass3);
}
Class实例可以是哪些结构?
- class:类
- 外部类
- 成员
- 成员内部类
- 静态内部类
- 局部内部类
- 匿名内部类
- interface:接口
- []:数组
- enum:枚举
- annotation:注解@interface
- private type:基本数据类型
- void:方法的返回类型,void也算一种类型
代码演示:
/**
* Class实例可以是哪些结构
*/
@Test
public void testClassType() {
Class<Object> objectClass = Object.class;
Class<Comparable> comparableClass = Comparable.class;
Class<String[]> stringArrClass = String[].class;
Class<int[][]> intArrClass = int[][].class;
Class<ElementType> elementTypeClass = ElementType.class;
Class<Override> overrideClass = Override.class;
Class<Integer> integerClass = int.class;
Class<Void> voidClass = void.class;
Class<Class> classClass = Class.class;
int[] a = new int[10];
int[] b = new int[100];
Class<? extends int[]> aClass = a.getClass();
Class<? extends int[]> bClass = b.getClass();
// 只要数组的元素类型与维度(一维、二维...)一样,就是同一个Class
// true
System.out.println(aClass == bClass);
}
基操
创建实例
@Test
public void testnewInstance() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class<?> personClass = Class.forName("cn.shangxiaoying.reflect.Person");
/**
* 要保证newInstance()方法能够正常运行,需要满足:
* 运行时类(这里的Person类)必须提供空参构造器,因为它会调用空参构造器来创建实例
* 空参构造器的访问权限要足够(即newInstance()方法能够访问的到),通常设置为public
*/
Object obj = personClass.newInstance();
System.out.println(obj);
}
JavaBean中为何必须提供Public的空参构造器?
- 便于通过反射,创建运行时类的对象。(一些框架的底层都是通过反射来创建对象,用的就是newInstance()方法。)
- 便于子类继承此运行时类时,在子类的构造器中默认调用super()时,保证父类有次构造器。
获取运行时类的属性信息
/**
* 通过反射获取运行时类的属性信息
*/
@Test
public void testGetFields() {
Class<Person> personClass = Person.class;
/**
* 获取当前运行时类及其所有父类中声明为public的属性
*/
Field[] fields = personClass.getFields();
/**
* 获取当前运行时类中声明的所有(不论权限)属性(不包含其父类中的属性)
*/
Field[] declaredFields = personClass.getDeclaredFields();
for (Field field : declaredFields) {
/**
* 获取变量的权限修饰符
*/
int modifier = field.getModifiers();
System.out.println(Modifier.toString(modifier));
/**
* 获取变量的数据类型
*/
Class<?> type = field.getType();
System.out.println(type.getName());
/**
* 获取变量名
*/
String name = field.getName();
System.out.println(name);
}
}
获取运行时类的方法信息
/**
* 获取运行时类的方法信息
*/
@Test
public void testGetMethods() {
Class<Person> personClass = Person.class;
/**
* 获取当前运行时类及其所有父类声明为public权限的方法
*/
Method[] methods = personClass.getMethods();
/**
* 获取当前运行时类声明的所有(不论权限)方法(不包含父类)
*/
Method[] declaredMethods = personClass.getDeclaredMethods();
for (Method method : declaredMethods) {
/**
* 获取方法声明的注解
*/
Annotation[] annotations = method.getAnnotations();
/**
* 获取方法的权限修饰符
*/
int modifiers = method.getModifiers();
System.out.println(Modifier.toString(modifiers));
/**
* 获取方法的返回值类型
*/
Class<?> returnType = method.getReturnType();
/**
* 获取方法名
*/
String name = method.getName();
/**
* 获取方法的形参列表
*/
Class<?>[] parameterTypes = method.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.println(parameterType.getName());
}
/**
* 获取方法抛出的异常
*/
Class<?>[] exceptionTypes = method.getExceptionTypes();
for (Class<?> exceptionType : exceptionTypes) {
System.out.println(exceptionType.getName());
}
}
}
获取构造器信息
/**
* 获取构造器信息
*/
@Test
public void testGetConstructors() {
Class<Person> personClass = Person.class;
/**
* 获取当前运行时类声明为public的构造器
*/
Constructor<?>[] constructors = personClass.getConstructors();
/**
* 获取当前运行时类的所有构造器
*/
Constructor<?>[] declaredConstructors = personClass.getDeclaredConstructors();
/**
*
* 获取构造器的权限修饰符、参数等方式与获取方法的修饰符等类似
*/
}
获取父类信息
/**
* 获取当前运行时类的父类信息
*/
@Test
public void getSuperClass() {
Class<Person> personClass = Person.class;
/**
* 获取当前运行时类的父类,不带泛型
*/
Class<? super Person> superclass = personClass.getSuperclass();
/**
* 获取当前运行时类的父类,带泛型
*/
Type genericSuperclass = personClass.getGenericSuperclass();
/**
* 获取父类的泛型
*/
ParameterizedType type = (ParameterizedType) genericSuperclass;
Type[] actualTypeArguments = type.getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument.getTypeName());
}
}
获取接口信息
/**
* 获取当前运行时类实现的接口信息
*/
@Test
public void testGetInterfaces() {
Class<Person> personClass = Person.class;
/**
* 获取当前运行时类实现的接口
*/
Class<?>[] interfaces = personClass.getInterfaces();
/**
* 获取当前运行时类父类实现的接口
*/
Class<?>[] interfaces1 = personClass.getSuperclass().getInterfaces();
}
获取所在包
/**
* 获取当前运行时类所在的包
*/
@Test
public void testGetPackge() {
Class<Person> personClass = Person.class;
/**
* 获取当前运行时类所在的包
*/
Package pac = personClass.getPackage();
}
获取注解信息
/**
* 获取当前运行时类声明的注解
*/
@Test
public void testGetAnnotation() {
Class<Person> personClass = Person.class;
/**
* 获取当前运行时类声明的注解
*/
Annotation[] annotations = personClass.getAnnotations();
}
操作指定属性
/**
* 操作运行时类中的指定属性
*/
@Test
public void testOperateField() throws Exception {
Class<Person> personClass = Person.class;
// 创建实例(要设置的属性不是静态属性时需要创建实例)
Person person = personClass.newInstance();
// 获取属性
/**
* getField()方法只能获取public权限的属性
*/
// Field age = personClass.getField("age");
/**
* getDeclaredField()方法可以获取任意访问权限的属性,一般都使用这个方法
*/
Field age = personClass.getDeclaredField("age");
/**
* 对于非public权限的字段不能直接修改,需要setAccessible(true)之后才能修改他的值
*/
age.setAccessible(true);
/**
* 设置属性
* set(要设置哪个对象的属性, 要设置的值)
* 对于静态属性:
* set(null或者Class对象, 要设置的值)
*/
age.set(person, 22);
/**
* 获取属性
* get(要获取哪个对象的值)
*/
Integer personAge = (Integer) age.get(person);
System.out.println(personAge);
}
操作指定方法
/**
* 操作指定方法
*/
@Test
public void testOperateMethod() throws Exception {
Class<Person> personClass = Person.class;
/**
* 创建实例(非静态方法需要创建实例)
*/
Person person = personClass.newInstance();
/**
* 获取指定方法
* getDeclaredMethod(方法名, 可变形参类型列表)
*/
Method show = personClass.getDeclaredMethod("show", String.class);
/**
* 对于非publ权限的方法,需要设置可访问
*/
show.setAccessible(true);
/**
* 调用方法
* invoke(方法的调用者, 可变实参列表)
* invoke()方法的返回值即为调用的方法的返回值,如果调用的类没有返回值,则invoke()方法返回null
* 相当于:
* String showContent = person.show("吉他弹唱");
*/
Object showContent = show.invoke(person, "吉他弹唱");
System.out.println(showContent);
/**
* 调用静态方法
*/
Method sayHi = personClass.getDeclaredMethod("sayHi");
sayHi.setAccessible(true);
// 调用者可以传一个Class对象,也可以传null
sayHi.invoke(personClass);
}
操作指定构造器
不常用,一般使用newInstance()调用运行时类的无参构造器
/**
* 操作指定构造器
*/
@Test
public void testOperateConstructor() throws Exception {
Class<Person> personClass = Person.class;
/**
* 获取指定构造器
* getDeclaredConstructor(可变参数类型列表)
*/
Constructor<Person> declaredConstructor = personClass.getDeclaredConstructor(String.class);
/**
* 保证可访问
*/
declaredConstructor.setAccessible(true);
/**
* 调用指定构造器
* newInstance(可变实参列表)
*/
Person person = declaredConstructor.newInstance("豆包");
System.out.println(person);
}
Q.E.D.