diff --git a/blogs/反射.md b/blogs/反射.md new file mode 100644 index 0000000..8b0ac50 --- /dev/null +++ b/blogs/反射.md @@ -0,0 +1,467 @@ +--- +反射 +--- + +#### 目录 + +1. 思维导图 +2. 定义 +3. 简单示例 +4. Class + - 如何获取 Class + - 通过 Class 获取类修饰符和类型 +5. Member + - Field + - Method + - Constructor +6. 数组和枚举 +7. 反射优缺点 +8. 参考 + +#### 思维导图 + +![](https://i.loli.net/2019/01/01/5c2ad008a6bdc.png) + +#### 定义 + +1. 对于任意一个类,都能知道这个类的所有属性和方法 +2. 对于任意一个对象,都能知道调用它的任意一个属性和方法 + +这种动态获取信息以及动态调用对象的方法的功能成为 Java 语言的反射机制。 + +反射机制主要设计两个类:Class 和 Member。 + +#### 简单示例 + +```java +public class UserInfo { + private String name; + private int age; + + private UserInfo(String name, int age) { + this.name = name; + this.age = age; +// try { +// throw new Exception("SingleTon can't ref"); +// } catch (Exception e) { +// e.printStackTrace(); +// } + } + + @Override + public String toString() { + return "UserInfo{" + + "name='" + name + '\'' + + ", age=" + age + + '}'; + } +} +``` + +既然构造方法都私有化了,怎么获取类实例呢? + +```java +public class RefDemo { + public static void main(String[] args) throws Exception { + Class c = UserInfo.class; + Constructor constructor = c.getDeclaredConstructor(String.class, int.class); + constructor.setAccessible(true); + UserInfo userInfo = (UserInfo) constructor.newInstance("Omooo", 18); + System.out.println(userInfo.toString()); + } +} + +输出:UserInfo{name='Omooo', age=18} +``` + +#### Class + +Class 是反射操作的基础,每个 class 类,无论创建多少个实例对象,在 JVM 中都对应同一个 Class 对象。 + +##### 如何获取 Class + +Java 反射包 java.lang.reflect 中的所有类都没有 public 构造方法,想要获取这些类的实例,只能通过 Class 类获取,所以说如何想使用反射,必须先获取的 Class 对象。 + +获取 Class 对象有以下几种方法: + +1. Object.getClass() + + 通过对象实例获取对应的 Class 对象。 + + ```java + Class c = "Omooo".getClass(); + ``` + +2. The.Class + + ```java + Class c = String.class; + ``` + +3. Class.forName() + + ```java + Class c = Class.forName("java.lang.String"); + //对于数组比较特殊 String[]: + Class cStringArray = Class.forName("[[Ljava.lang.String;") + ``` + +4. The.TYPE + + ```java + Class c = Double.TYPE; + ``` + +5. Class.getSuperclass() + +##### 通过 Class 获取类修饰符和类型 + +```java + public static void main(String[] args) throws Exception { + Class c = HashMap.class; + //获取类名 UserInfo + String className = c.getName(); + System.out.println("类名: " + className); + //获取类限定符 public + String modifier = Modifier.toString(c.getModifiers()); + System.out.println("类限定符: " + modifier); + //获取类泛型参数 + TypeVariable[] typeParameters = c.getTypeParameters(); + if (typeParameters.length > 0) { + StringBuilder sb = new StringBuilder("泛型参数:"); + for (TypeVariable typeVariable : typeParameters) { + sb.append(typeVariable.getName()); + sb.append("___"); + } + System.out.println(sb.toString()); + } else { + System.out.println("类泛型参数为空!"); + } + //获取类所实现的接口 + Type[] interfaces = c.getGenericInterfaces(); + if (interfaces.length > 0) { + StringBuilder sb = new StringBuilder("接口信息:"); + for (Type type : interfaces) { + sb.append(type.toString()); + sb.append("___"); + } + System.out.println(sb.toString()); + } else { + System.out.println("类接口信息为空!"); + } + //获取继承的父类信息 + List superClazzs = new ArrayList<>(); + getSuperClassName(c, (ArrayList) superClazzs); + if (superClazzs.size() != 0) { + StringBuilder sb = new StringBuilder("父类信息:"); + for (Class clazz : superClazzs) { + sb.append(clazz.getName()); + sb.append("___"); + } + System.out.println(sb.toString()); + } + //获取类注解信息 + Annotation[] annotations = c.getAnnotations(); + if (annotations.length != 0) { + StringBuilder sb = new StringBuilder("类注解信息:"); + for (Annotation annotation : annotations) { + sb.append(annotation.toString()); + sb.append("___"); + } + System.out.println(sb.toString()); + } else { + System.out.println("类注解信息为空!"); + } + } + +输出: +类名: java.util.HashMap +类限定符: public +泛型参数:K___V___ +接口信息:java.util.Map___interface java.lang.Cloneable___interface java.io.Serializable___ +父类信息:java.util.AbstractMap___java.lang.Object___ +类注解信息为空! +``` + +#### Member + +Member 有三个实现类: + +1. Field 对应类变量 +2. Method 对应类方法 +3. Constructor 对应类构造方法 + +##### Field + +通过 Field 你可以访问给定对象的类变量,包括获取变量的类型、修饰符、注解、变量名、变量值等等,即使变量是 private 的。 + +- 获取 Field + + Class 提供了四种方法获得给定类的 Field。 + + - getDeclaredField(String name) 获取指定的变量,包括 private 的 + - getField(String name) 获取指定的变量,只能是 public 的 + - getDweclaredFields() 获取所有的变量,包括 private 的 + - getFields() 获取所有的变量,只能是 public 的 + +- 获取变量类型、修饰符、注解 + + ```java + public static void main(String[] args) { + Class clazz = UserInfo.class; + Field[] fields = clazz.getDeclaredFields(); + if (fields.length > 0) { + for (Field field : fields) { + StringBuilder sb = new StringBuilder(); + //变量名 + String name = field.getName(); + sb.append("变量名:"); + sb.append(name); + //变量类型 + String type = field.getType().getName(); + sb.append("变量类型:"); + sb.append(type); + //变量修饰符 + String modifer = Modifier.toString(field.getModifiers()); + sb.append("变量修饰符:"); + sb.append(modifer); + //变量上的注解 + Annotation[] annotations = field.getDeclaredAnnotations(); + if (annotations.length > 0) { + for (Annotation annotation : annotations) { + String annName = annotation.toString(); + sb.append("注解:"); + sb.append(annName); + } + } + System.out.println(sb); + } + } + } + ``` + +- 获取、设置变量值 + + ```java + public static void main(String[] args) throws Exception { + UserInfo userInfo = new UserInfo("Omooo", 18); + System.out.println("Before: " + userInfo.toString()); + Class clazz = userInfo.getClass(); + Field nameField = clazz.getDeclaredField("name"); + nameField.setAccessible(true); + Field ageField = clazz.getField("age"); + nameField.set(userInfo, "Tom"); + ageField.setInt(userInfo, 19); + System.out.println("After: " + userInfo.toString()); + } + ``` + + 其中,对于 private 的属性、方法或者构造函数,比如要有: + + ```java + xxx.setAccessible(true); + ``` + + 这个方法是 AccessibleObject 中的一个方法,Field、Method、Constructor 都是其子类,该方法的作用就是可以取消 Java 语言访问权限检查。 + +##### Method + +- 获取 Method + + Class 依然提供了四种方法获取 Method: + + ``` + getDeclaredMethod(String name, Class... parameterTypes) + getMethod(Sting name, Class... parameterTypes) + getDeclaredMethods() + getMethods() + ``` + + 获取带参数方法时,如果参数类型错误会报 NoSuchMethodException,对于参数是泛型的情况下,泛型必须当成 Object 处理。 + +- 获取方法返回类型 + + ```java + getReturnType() 获取目标方法返回类型对应的 Class 对象 + getGenericReturnType() 获取目标方法返回类型对应的 Type 对象 + ``` + + 对于返回值是普通类型如 Object、int、String 等,两者返回值一样。但是有两种特殊情况: + + ```java + //1.返回值是泛型 + public T function() + getReturnType() : class java.lang.Object + getGenericReturnType() : T + //2.返回值为参数化类型 + public Class function() + getReturnType(): class java.lang.Class + getGenericReturnType(): java.lang.Class + ``` + +- 获取方法参数类型 + + ```java + getParameterTypes() 获取目标方法各参数类型对应的 Class 对象 + getGenericParameterTypes() 获取目标方法各参数类型对应的 Type 对象 + ``` + + 返回值为数组。 + +- 获取方法声明抛出的异常的类型 + + ```java + getExceptionTypes() 获取目标方法抛出的异常类型对应的 Class 对象 + getGenericExceptionTypes() 获取目标方法抛出的异常类型对应的 Type 对象 + ``` + + 返回值为数组。 + +- 获取方法参数名称 + + .class 文件中默认不存储方法参数名称,如果想要获取方法参数名称,需要在编译的时候加上 -parameters 参数。构造方法的参数获取同样。 + + ```java + public static void main(String[] args) throws Exception { + UserInfo userInfo = new UserInfo("Omooo", 18); + Class clazz = userInfo.getClass(); + Method nameMethod = clazz.getMethod("setName", String.class); + Parameter[] parameters = nameMethod.getParameters(); + if (parameters.length > 0) { + for (Parameter p : parameters) { + StringBuilder sb = new StringBuilder(); + sb.append("参数类型: "); + sb.append(p.getType()); + sb.append("参数名称: "); + sb.append(p.getName()); + System.out.println(sb.toString()); + } + } + } + 输出:参数类型: class java.lang.String参数名称: arg0 + ``` + + 如果编译未加上 -parameters 参数,返回的参数名则形如 ”argX“,X 代表参数的位置。 + +- 获取方法修饰符 + + ```java + method.getModifiers() + ``` + +- 通过反射调用方法 + + 反射通过 Method 的 invoke() 方法来调用目标方法。第一个参数为需要调用的目标类对象,如果方法是 static 的,则该参数为 null,后面的参数都为目标方法的参数值,顺序与目标方法声明中的参数顺序一致。 + + 需要注意的是,被调用的方法本身所抛出的异常在反射中都会以 InvocationTargetException 抛出。换句话说,反射调用过程中如果异常 InvocationTargetException 抛出,说明反射调用本身是成功,因为这个异常是目标方法本身所抛出的异常。 + +##### Constructor + +通过反射访问构造方法并通过构造方法构建新的对象。 + +- 获取构造方法 + + 和 Method 一样,Class 也提供了四种方法获取: + + ```java + getDeclaredConstructor(Class... parameterTypes) + getConstructor(Class... parameterTypes) + getDeclaredConstructors() + getConstructors() + ``` + + 构造方法的名称、限定符、参数、声明的异常等获取方法和 Method 一致。 + +- 创建对象 + + 创建对象有两种方法: + + 1. java.lang.reflect.Constructor.newInstance() + 2. Class.newInstance() + + 一般来讲,优先使用第一种方法,第二种方法已经被废弃了。它们之间的区别有: + + - 方法一可以调用任意参数的构造方法,而方法二只能调用无参的构造方法 + - 方法一会将原方法抛出的异常都包装成 InvocationTaregtException 抛出,而方法二会将原方法中的异常不做处理原样抛出 + - 方法一不需要方法权限,方法二只能调用 public 的构造方法 + + 需要注意的是:反射不支持自动装箱,传入参数时要小心。自动装箱是在编译期间的,而反射是在运行期间。 + +#### 数组和枚举 + +数组和枚举也是对象,但是在反射中,对数组和枚举的创建、访问和普通对象有点不同,Java 反射为数组和枚举提供了一些特定的 API 接口。 + +##### 数组 + +- 数组类型 + + 数组本质上也是一个对象,所以它也有自己的类型。例如对于 int[] array,数组类型为 class [I。数组类型中的 [ 个数表示数组的维度,其后面的字母代表数组元素类型,即 int,一般为类型的首字母大小。( long 类型例外,为 J,因为 L 被引用对象类型占用了) + +- 创建和初始化数组 + + Java 反射为我们提供了 java.lang.reflect.Array 类用来创建和初始化数组。 + + ```java + Array.newInstance(Class componentType, int... dimensions) + Array.set(Object array, int index, int value) + Array.get(Object array, int index) + ``` + + 例如,用反射创建 int[] array = new int[]{1, 2}: + + ```java + public static void main(String[] args) throws Exception { + Object array = Array.newInstance(int.class, 2); + Array.set(array, 0, 1); + Array.set(array, 1, 2); + System.out.println(Array.get(array, 1)); + } + ``` + +- 多维数组 + + Java 反射没有提供能够直接访问多维数组元素的 API,但你可以把多维数组当成数组的数组处理。 + + ```java + public static void main(String[] args) throws Exception { + Object matrix = Array.newInstance(int.class, 2, 2); + Object row1 = Array.get(matrix, 0); + Object row2 = Array.get(matrix, 1); + Array.set(row1, 0, 1); + Array.set(row1, 1, 2); + Array.set(row2, 0, 3); + Array.set(row2, 1, 4); + } + ``` + +##### 枚举 + +枚举隐式继承自 java.lang.Enum,Enum 继承自 Object,所以枚举本质上也是一个类,也可以有成员变量、构造方法、方法等。对于普通类所能使用的反射方法,枚举都能使用,另外,Java 反射额外提供了几个方法。 + +```java +Class.isEnum() +Class.getEnumConstants() +java.lang.reflect.Field.isEnumConstant() +``` + +#### 反射优缺点 + +优点:动态改变类行为 + +缺点: + +1. 性能开销 + + 反射涉及类型动态解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率低下,尽量避免在高性能要求下使用反射。 + +2. 安全限制 + + 使用反射要求程序必须在一个没有安全限制的环境中运行。 + +3. 内部曝光 + + 由于反射允许代码执行一些在正常情况下不被允许的操作,所以使用反射可能会导致意料之外的副作用。 + +#### 参考 + +[Java 反射完全解析](https://www.jianshu.com/p/607ff4e79a13) \ No newline at end of file diff --git a/images/反射.png b/images/反射.png new file mode 100644 index 0000000..7af7ec4 Binary files /dev/null and b/images/反射.png differ