一、反射基础
反射指的是可以于运行时加载、探知、使用编译期间完全未知的类 。 程序在运行状态中,可以动态加载一个只有名称的类,对于 任意一 个已加载的类, 都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性
1.1 获取Class的多种方式
Class
类是Reflection(反射)的根源。针对任何您想动态加载、运行的类,唯有先获得相应的Class 对象
public final class Class<T>
extends Object
implements Serializable, GenericDeclaration, Type, AnnotatedElement
Class 类
的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也表示为 Class 对象。
类型 | 描述 |
---|---|
接口.class 返回接口 | 案例: interface java.util.Collection ; 关键字 interface |
注解.class 返回接口 | 案例:Override.class ; 关键字 interface |
类.class 返回类 | 案例:class java.util.Collections ; 关键字 class |
枚举.class 返回类 | 案例:Enum.class ; 关键字class |
Class
没有公共构造方法。Class
对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass
方法自动构造的。
方式 | 描述 |
---|---|
static Class< ? >forName(String className) | 使用Class的一些静态方法,还有其他的重载方法 |
类.class | 如果是类,则直接.class即可。这种方式最简单,也不会出现ClassNotFoundException等异常 |
对象.getClass() | 如果是对象,则使用这种方式 |
注: 使用反射,应该保证该类有一个默认的构造方法,否则会报错
1.2 API
1.2.1 构造器、属性、方法的基本API
下面多个方法标记所有; 如果去掉Declared
,则获取的只能是public
修饰的,例如getMethods()
,就只能获取公共方法
方法 | 描述 |
---|---|
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) | 返回一个 Constructor 对象,该对象反映此 Class 对象所表示的类或接口的指定构造方法 |
Constructor<?>[] getDeclaredConstructors() | 返回 Constructor 对象的一个数组,这些对象反映此 Class 对象表示的类声明的所有构造方法 |
Field getDeclaredField(String name) | 返回一个 Field 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明字段 |
Field[] getDeclaredFields() | 返回 Field 对象的一个数组,这些对象反映此 Class 对象所表示的类或接口所声明的所有字段 |
Method getDeclaredMethod(String name, Class<?>... parameterTypes) | 返回一个 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法 |
Method[] getDeclaredMethods() | 返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法 |
注:构造器和方法都一个可变参数,这是为区别重载,所以在获取对应的构造器和方法的时候,必须要传入对应形参的Class; 而字段就不用。如果没有形参,则使用关键字null
代替;或者不写也可以。
Constructor<Student> c = clazz.getDeclaredConstructor(int.class,int.class,String.class);
Student s1 = c.newInstance(17,22,"一号人物")(传入参数)
Method method = clazz.getDeclaredMethod("setUname", String.class);//(方法名,参数.class)
method.invoke(s2, "二号人物");//s2表示调用方法的对象,反射使用方法是需要和具体的对象绑定
Student s3 = clazz.newInstance(); // 获得对象
Field f = clazz.getDeclaredField("uname"); // 属性,不能直接使用javabean中的private的属性的
// 需要通过添加下面方法(setAccessible(true))才可以使用
f.setAccessible(true); // 这个属性不需要做安全检查,可以直接访问
f.set(s3, "三号人物"); //通过反射直接写属性
补充java.lang.reflect.Member接口
选项 | 描述 |
---|---|
java.lang.reflect.Member 接口 | 所有已知实现类:Constructor , Field , Method |
static int DECLARED 静态属性 | 标识类或接口的所有已声明成员的集合。 |
static int PUBLIC 静态属性 | 标识类或接口的所有公共成员(包括继承成员)的集合 |
1.2.2 细节理解
反射获取的是类相关信息,如果要具体到某一个对象上,都需要指定具体的对象
案例分析
class ReflectPoint {
private int x;
public int y;
public ReflectPoint(int x, int y) {
this.x = x;
this.y = y;
}
public ReflectPoint() {
}
}
public class ReflectField {
public static void main(String[] args) throws Exception {
ReflectPoint pt = new ReflectPoint(3, 5);
Field fieldY = pt.getClass().getField("y");
//请问 fieldY 的值时多少?
System.out.println(fieldY);//public int reflectStudy.ReflectPoint.y
System.out.println(fieldY.get(pt));
Field fieldx = pt.getClass().getDeclaredField("x");
fieldx.setAccessible(true);
System.out.println(fieldx.get(pt));
}
}
解释原因
pt.getClass().getField("Y")
是类上的。它不知道是调用的那一个具体的对象。而通过fieldY.get(pt) 来确认到底是调用的哪一个。如果是私有属性,通过调用
getDeclaredField("X")
来获取私有方法,通过设置访问权限fieldX.setAccessible(true)
来设置访问权限。
1.2.3 通过反射修改对象的属性值。
private static void changeStringValue(Object obj) throws Exception {
Field[] fields = obj.getClass().getFields();
for(Field field : fields){
//if(field.getType().equals(String.class)){
if(field.getType() == String.class){ // // 同一份字节码; 表单更加准确
String oldValue = (String)field.get(obj);
String newValue = oldValue.replace('b', 'a');
field.set(obj, newValue);
}
}
}
注意:只有Field
可以有set方法,Method
、Constructor
只有获取的方法。
1.2.4 成员方法的反射
方法与对象是没有关系的。可以在对象1上调用,也可以在对象2上调用,所以这是属于类的
- 获取:第一个参数是方法名,第二个是参数列表,
- 调用,第一个是哪个对象去调用这个方法,第二参数是传入哪些实参。
- 如果Method的第一个参数是null,表明这个方法是静态方法,不需要对象。
(一)源码分析
/**
* 反射方法
*/
public class ReflectMethod {
public static void testMethd(String[] array) {
System.out.print("invoke me!");
}
public static void main(String[] args) throws Exception {
Method method = ReflectMethod.class.getMethod("testMethd",String[].class);
//静态方法,对象传null;
method.invoke(null,new String[]{"111","222","333"});
}
}
(二)打印结果
Exception in thread "main" java.lang.IllegalArgumentException: wrong number of arguments
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at reflectStudy.ReflectMethod.main(ReflectMethod.java:20)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
参数个数不匹配
(三) 原因
在1.4,没有可变参数之前,用数组来接收参数列表。为了向下兼容。会安装JDK1.4 语法进行处理,即,将字符串数组才分成单独的参数;所以会wrong number of arguments
;
(四) 针对这种问题的处理方案
public class ReflectMethod {
public static void testMethd(String[] array) {
System.out.println("invoke me!");
}
public static void main(String[] args) throws Exception {
Method method = ReflectMethod.class.getMethod("testMethd",String[].class);
//静态方法,对象传null;
//兼容1.4版本,把这个数组拆包,变成三个参数,所以报参数错误
//method.invoke(null,new String[]{"111","222","333"});
//解决方案一: 把数组封装到Object数组中,拆包后是一个字符串型的数组。
method.invoke(null, new Object[]{new String[]{"11","22"}});
//解决方案二: 把字符串数组标示成一个Object对象,编译器就不会拆包了。
method.invoke(null, (Object)new String[]{"11","22"});
}
}
1.2.5 数组相关的反射
如果数组的
类型
和纬度
相同,则得到字节码是同一份
int[] a1 = new int[3];
int[] a2 = new int[4];
int[][] a3 = new int[3][];
int[][] a4 = new int[4][];
int[][] a5 = new int[3][2];
int[][] a6 = new int[3][5];
//int[][] a7 = new int[][4];//编译出错
//String[] s0 = new String[];编译出错
String[] s1 = new String[]{};
String[] s2 = new String[]{"a","b"};
//String[] s2 = new String[2]{"a","b"};编译出错
System.out.println(a1.getClass() == a2.getClass());//true
//System.out.println(a1.getClass() == a3.getClass());//编译出错
System.out.println(a3.getClass() == a4.getClass());//true
System.out.println(a3.getClass() == a5.getClass());//true
System.out.println(s1.getClass() == s2.getClass());//true
Arrays.asList
int[] a1 = new int[] {1,2,3};
String[] s1 = new String[]{"a","b","c"};
System.out.println(Arrays.asList(a1));//打印: [[I@1b6d3586]
System.out.println(Arrays.asList(s1));//打印: [a, b, c]
兼容1.4的API;
int [] a1 = new int[]{1,2,3}
; 在1.5
以后,asList接收的是可变参数。另外int
是基本类型。
1.3 安全检查
启用和禁用访问
安全检查的开关
,值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查
注意其实现了多个子类:Constructor、Field、Method
1.4 反射操作泛型
Java采用泛型擦除的机制来引入泛型。Java中的泛型仅仅是给编译器javac使用的,确保数据的安全性和免去强制类型转换的麻烦。但是,一旦编译完成,所有的和泛型有关的类型全部擦除。但是会保留元数据。
1.4.1 API
Java就新增了ParameterizedType
,GenericArrayType
,TypeVariable
和WildcardType
几种类型来代表不能被归一到Class类中的类型但是又和原始类型齐名的类型。
接口 | 描述 |
---|---|
ParameterizedType: | 表示一种参数化的类型,比如Collection<String> |
GenericArrayType: | 表示一种元素类型是参数化类型或者类型变量的数组类型 |
TypeVariable: | 是各种类型变量的公共父接口 |
WildcardType: | 代表一种通配符类型表达式,比如?, ? extends Number ,? super Integer ; wildcard(通配符的意思) |
1.4.2 应用
测试一下接口,只用
ParameterizedType
做案例,其他可以自行实验
package reflectStudy;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
class User {
}
public class GenericDemo {
public void test01(Map<String, User> map, List<User> list) {
System.out.println("new GenericDemo().test01()");
}
public Map<Integer, User> test02() {
System.out.println("new GenericDemo().test02()");
return null;
}
public static void main(String[] args) throws Exception {
//获取指定方法的类型变量
Method method1 = GenericDemo.class.getMethod("test01", Map.class, List.class);
Type[] types = method1.getParameterTypes();
for(Type type:types) {
System.out.println(type.getTypeName());// 打印 java.util.Map <br> java.util.List
System.out.println(type);//interface java.util.Map;interface java.util.List
}
System.out.println("===========================");
//获取指定方法的返回值类型变量
Method method2 = GenericDemo.class.getMethod("test02");
Type type2 = method2.getGenericReturnType();
System.out.println(type2.getTypeName());//java.util.Map<java.lang.Integer, reflectStudy.User>
System.out.println(type2);//java.util.Map<java.lang.Integer, reflectStudy.User>
System.out.println("===========================");
// 获取泛型信息
Type[] types3 = method1.getGenericParameterTypes();
for(Type type:types3) {
System.out.println(type);
// Type[] getActualTypeArguments() 返回表示此类型实际类型参数的 Type 对象的数组。
if(type instanceof ParameterizedType) {
Type[] genericTypes = ((ParameterizedType) type).getActualTypeArguments();
for (Type genericType : genericTypes) {
System.out.println("参数-泛型类型:"+genericType.getTypeName());
// System.out.println("参数-泛型类型:"+genericType);//会判断是 class or interface
}
}
}
System.out.println("===========================");
//返回值的泛型信息
Type type4 = method2.getGenericReturnType();
System.out.println(type4);
if(type4 instanceof ParameterizedType) {
Type[] genericTypes = ((ParameterizedType) type4).getActualTypeArguments();
for (Type genericType : genericTypes) {
System.out.println("参数-泛型类型:"+genericType.getTypeName());
// System.out.println("参数-泛型类型:"+genericType);//会判断是 class or interface
}
}
}
}
1.5 反射操作注解
1.5.1 API
方法 | 描述 |
---|---|
<T extends Annotation>T getAnnotation(Class<T> annotationClass) | 如果存在该元素的指定类型的注释,则返回这些注释,否则返回 null |
Annotation[] getAnnotations() | 返回此元素上存在的所有注释。 |
Annotation[] getDeclaredAnnotations() | 返回直接存在于此元素上的所有注释。 |
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) | 如果指定类型的注释存在于此元素上,则返回 true,否则返回 false。 |
1.5.2 应用
可以参考注解那一篇博客
1.6 数组相关反射
java.lang.reflect.Array
这个类有多个静态方法。可以获取数组中的元素等。Array 类提供了动态创建和访问 Java 数组的方法;Array 允许在执行 get 或 set 操作期间进行扩展转换,但如果发生收缩转换,则抛出IllegalArgumentException。
1.7小节
注意: 如果使用这个方法来获取一个实例对象,就需要在这个被反射的类中提供一个无参构造方法,否则会报错。
反射降低运行效率, 也因此使用缓存;反射在框架中大量被使用