1. 基本概念
本节内容参考 Oracle 官方文档
通过反射,Java 代码可以发现有关一家在类的字段,方法和构造函数的信息,并可以在安全限制内对这些字段,方法和构造函数进行操作
- 对于类:可以知道这个类的所有方法和字段
- 对于对象,可以调用任意方法和获取任意字段
下面会逐个说明上述的操作是如何使用的
1.1 Class
这里面的 Class 是指的 [java.lang.Class](https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html)
,可以说其是反射的起点, Class 浅析 这篇文章简单分析了下 Class 类。
通过 Class 对象可以获取此类的方法(java.lang.reflect.Method
)、字段(java.lang.reflect.Field
)、构造方法(java.lang.reflect.Constructor
),可以看到这三个类都是属于 reflect 包下的,皆是反射的成员,而它们又都实现了 java.lang.reflect.Member
接口。所以接下来会分析下 Member 接口的定义与行为。
1.2 Member
Member 是定义获取 Method、Field 以及 Construtor 标识信息的接口。标识信息都有什么则通过它定义的方法就可知了,下面列出了它的方法和说明
方法 | 说明 |
---|---|
Class<?> _getDeclaringClass()_ | 获取定义此成员的 Class |
String getName() | 获取成员名称 |
int getModifiers() | 获取成员的修饰符 此方法大多和 java.lang.reflect.Modifier 配合使用,通过 Modifier 的静态方法与字段进行逻辑判断 |
boolean isSynthetic() | 获取成员是否是由编译器生成的 |
1.2.1 Fields
类所定义的字段,获取方式如下有两类
- 获取 public 声明的字段,包括父类:
getFields()
、getField(String name)
- 获取非 public 声明的字段,不包括父类:
getDeclaredFields()
、getDeclaredField(String name)
1.2.1.1 获取字段类型
通过 getType()
和 getGenericType()
方法可以获取到字段的类型和泛型类型
如果对 Type 和 GenericType 有不了解的,可以参考下Type 浅析 这篇文章,这里就不多说了
1.2.1.2 获取和修改字段值
用法比较简单,注意一点就是在获取和修改私有字段的时候要首先调用 Field 的 setAccessible(true)
方法,还有就是给 static 字段赋值时,首个参数传空即可,示例:
enum Tweedle { DEE, DUM }
public class Book {
private long chapters = 0;
public String[] characters = { "Alice", "White Rabbit" };
public Tweedle twin = Tweedle.DEE;
public static void main(String... args) {
Book book = new Book();
String fmt = "%6S: %-12s = %s%n";
try {
Class<?> c = book.getClass();
Field chap = c.getDeclaredField("chapters");
chap.setAccessible(true);
out.format(fmt, "before", "chapters", book.chapters);
chap.setLong(book, 12);
out.format(fmt, "after", "chapters", chap.getLong(book));
Field chars = c.getDeclaredField("characters");
out.format(fmt, "before", "characters",
Arrays.asList(book.characters));
String[] newChars = { "Queen", "King" };
chars.set(book, newChars);
out.format(fmt, "after", "characters",
Arrays.asList(book.characters));
Field t = c.getDeclaredField("twin");
out.format(fmt, "before", "twin", book.twin);
t.set(book, Tweedle.DUM);
out.format(fmt, "after", "twin", t.get(book));
// production code should handle these exceptions more gracefully
} catch (NoSuchFieldException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
}
}
// 输出结果
/**
* BEFORE: chapters = 0
* AFTER: chapters = 12
* BEFORE: characters = [Alice, White Rabbit]
* AFTER: characters = [Queen, King]
* BEFORE: twin = DEE
* AFTER: twin = DUM
*/
1.2.2 Methods
类所定义的方法,获取方式如下有三类
- 获取 public 声明的方法,包括父类:
getMethods()
、getMethod(String name,Class<?>... parameterTypes)
- 获取非 public 声明的方法,不包括父类:
getDeclaredMethods()
、getDeclaredtMethod(String name,Class<?>... parameterTypes)
还有一种比较特殊
getEnclosingMethod()
,是返回定义局部类的方法,例如下面的 ```java class Human {public Object body() {
class Body {
}
return new Body();
} }
public class Main extends Human {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Human human = new Human();
System.out.println( human.body().getClass().getEnclosingMethod()); // 返回:public java.lang.Object classtest.Human.body()
}
}
<a name="9MtIs"></a>
#### <br />
<a name="88APT"></a>
#### 1.2.2.1 获取方法类型
下面的图是获取类型的所有方法
![](https://cdn.nlark.com/yuque/0/2020/png/699419/1606392577090-bcbf373b-53db-4b31-b4e8-fec0a134e12f.png#align=left&display=inline&height=208&margin=%5Bobject%20Object%5D&originHeight=538&originWidth=1470&size=0&status=done&style=none&width=567)
代码示例:
```java
public class Main {
private static final String fmt = "%s: %s%n";
<E extends RuntimeException> List<String> genericThrow(String... some) throws E { return null; }
public static void main(String[] args) throws NoSuchMethodException {
Method m = Main.class.getDeclaredMethod("genericThrow", String[].class);
System.out.format("%s%n", m.toGenericString());
// 返回值类型
System.out.format(fmt, "ReturnType", m.getReturnType());
System.out.format(fmt, "GenericReturnType", m.getGenericReturnType());
// 参数类型
Class<?>[] pType = m.getParameterTypes();
Type[] gpType = m.getGenericParameterTypes();
for (int i = 0; i < pType.length; i++) {
System.out.format(fmt, "ParameterType", pType[i]);
System.out.format(fmt, "GenericParameterType", gpType[i]);
}
// 抛出异常类型
Class<?>[] xType = m.getExceptionTypes();
Type[] gxType = m.getGenericExceptionTypes();
for (int i = 0; i < xType.length; i++) {
System.out.format(fmt, "ExceptionType", xType[i]);
System.out.format(fmt, "GenericExceptionType", gxType[i]);
}
}
}
// 输出结果
/**
* <E> java.util.List<java.lang.String> classtest.Main.genericThrow(java.lang.String...) throws E
* ReturnType: interface java.util.List
* GenericReturnType: java.util.List<java.lang.String>
* ParameterType: class [Ljava.lang.String;
* GenericParameterType: class [Ljava.lang.String;
* ExceptionType: class java.lang.RuntimeException
* GenericExceptionType: E
*/
1.2.2.2 调用方法
使用 invoke 调用,首参数是实例对象,如果是静态方法则为传 null,跟字段同样,如果是非 public 方法的话,需要事先调用setAccessible(true)
,下面是例子,一个静态私有可变参数的方法
public class Main {
static class MethodTest {
private static String splice(String interval,String ...args){
StringBuilder returnValue = new StringBuilder();
for (int i = 0; i < args.length; i++) {
if (i!=0){
returnValue.append(interval);
}
returnValue.append(args[i]);
}
return returnValue.toString();
}
}
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method spliceMethod = MethodTest.class.getDeclaredMethod("splice",String.class,String[].class);
spliceMethod.setAccessible(true);
String result = (String) spliceMethod.invoke(null,",",new String[]{"1","2","3"});
System.out.println(result);
}
}
1.2.3 Constructors
类所定义的构造方法,获取方式如下有三类
- 获取 public 声明的构造方法,包括父类:
getConstructors()
、getConstructor(Class<?>... parameterTypes)
- 获取非 public 声明的方法,不包括父类:
getDeclaredConstructors()
、getDeclaredtConstructor(Class<?>... parameterTypes)
还有一种比较特殊
getEnclosingConstructor()
,是返回定义局部类的方法,例如下面的 ```java class ConstructorTest{ public Object object; ConstructorTest(T message,long…list) {
class ConstructorLocalClass {
}
object = new ConstructorLocalClass();
} }
public class Main {
public static void main(String[] args) {
ConstructorTest
<a name="9To64"></a>
#### <br />
<a name="wEm3V"></a>
#### 1.2.3.1 创建类实例
有两种反射方法可以创建类的实例,[`java.lang.reflect.Constructor.newInstance()`](https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Constructor.html#newInstance-java.lang.Object...-) 和 [`Class.newInstance()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#newInstance--) 。建议首选 [`Constructor.newInstance()`](https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Constructor.html#newInstance-java.lang.Object...-) 这种方式,原因如下几点:
- [`Class.newInstance()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#newInstance--) 只能调用无参的构造函数,[`Constructor.newInstance()`](https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Constructor.html#newInstance-java.lang.Object...-) 则可以调用任意参数的构造函数
- [`Class.newInstance()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#newInstance--) 抛出的异常是不可预知的,[`Constructor.newInstance()`](https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Constructor.html#newInstance-java.lang.Object...-) 则只会抛出 [`InvocationTargetException`](https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/InvocationTargetException.html) 异常
- [`Class.newInstance()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#newInstance--) 只能调用可见的构造函数,[`Constructor.newInstance()`](https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Constructor.html#newInstance-java.lang.Object...-) 则可以调用私有构造函数
下面是例子:
```java
class ConstructorTest<T> {
public T message;
private ConstructorTest(T message, long... list) {
this.message = message;
System.out.println(Arrays.toString(list));
}
}
public class Main {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<?> constructor = ConstructorTest.class.getDeclaredConstructor(Object.class,long[].class);
constructor.setAccessible(true);
ConstructorTest<?> t = (ConstructorTest<?>) constructor.newInstance("message",new long[]{1,2});
System.out.println(t.message);
}
}
1.3 Arrays and Enumerated Types
根据 Java 虚拟机的规定,数组和枚举都属于 classes。大多 Class 的方法适用于它们。反射为数组和枚举提供了一些特殊的 API
1.3.1 Arrays
通过 Class.isArray()
判断类是否是 Array 类型
只能通过 java.lang.reflect.Array.newInstance()
的方式反射实例化对象,下面的例子会展示一个一维数组和二维数组的创建:
public class Main {
public static void main(String[] args) {
// 创建一维数组
Object object = Array.newInstance(String.class, 3);
Array.set(object, 2, "2");
System.out.println("一维数组:");
System.out.println(Arrays.toString((Object[]) object));
System.out.println("二维数组:");
// 创建二维数组
Object matrix = Array.newInstance(int.class, 2, 2);
Object row0 = Array.get(matrix, 0);
Object row1 = Array.get(matrix, 1);
Array.setInt(row0, 0, 1);
Array.setInt(row0, 1, 2);
Array.setInt(row1, 0, 3);
Array.setInt(row1, 1, 4);
for (int i = 0; i < 2; i++)
for (int j = 0; j < 2; j++)
System.out.format("matrix[%d][%d] = %d%n", i, j, ((int[][]) matrix)[i][j]);
}
}
// 输出
/**
* 一维数组:
* [null, null, 2]
* 二维数组:
* matrix[0][0] = 1
* matrix[0][1] = 2
* matrix[1][0] = 3
* matrix[1][1] = 4
*/
1.3.2 Enumerated Types
枚举类的用法跟一般类的类很像,其中 Class.isEnum()
方法时判断类是否是枚举类,Class.getEnumConstants()
则会返回枚举中定义的枚举。对于字段,则有 java.lang.reflect.Field.isEnumConstant()
返回是否是枚举字段
2. 反射的实现原理
2.1 类成员信息的获取
反射无论是基于类还是基于对象,都绕不开 Class 类,而 Class 类本质上是 .class 文件在 JVM 中的映射实例。
.class 的文件分析在这里不会多叙,可以看《Java 虚拟机规范》,后面我也会专门写一篇文章分析 class,下面贴了一张书的截图,可以看出文件里包含了类中所定义的所有成员与属性
2.2 Field 的获取和赋值源码分析
获取方法包含 getField
和 getDeclaredField
两类,所以获取的就看分析这两个方法即可,而其中 getField
又包含了 getDeclaredField
的代码,所以下面只看 getField
2.3 Method 的获取和 invoke 源码分析
3. 反射的性能损耗与优化
标签