parent
ad6c038a44
commit
2968ebbb18
@ -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<Class> superClazzs = new ArrayList<>(); |
||||||
|
getSuperClassName(c, (ArrayList<Class>) 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<K, V>___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<String> function() |
||||||
|
getReturnType(): class java.lang.Class |
||||||
|
getGenericReturnType(): java.lang.Class<java.lang.String> |
||||||
|
``` |
||||||
|
|
||||||
|
- 获取方法参数类型 |
||||||
|
|
||||||
|
```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) |
After Width: | Height: | Size: 56 KiB |
Loading…
Reference in new issue