1 什么是反射
反射(Reflection)是 Java 的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性。
通过反射,我们可以在运行时获得程序中每一个类型的成员和成员的信息。程序中一般的对象的类型都是在编译期就确定下来的,而 Java 反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。
反射的核心是 JVM 在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。
Java 反射主要提供以下功能:
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
- 在运行时调用任意一个对象的方法
重点:是运行时而不是编译时。
Class 和 java.lang.reflect 一起对反射提供了支持,java.lang.reflect类库主要包含了以下三个类:
- Field
- Method
- Constructor
2 反射的优缺点
反射具有一些优点:
- 可扩展性 :应用程序可以利用全限定名创建可扩展对象的实例,来使用来自外部的用户自定义类。
- 类浏览器和可视化开发环境:一个类浏览器需要可以枚举类的成员。可视化开发环境(如 IDE)可以从利用反射中可用的类型信息中受益,以帮助程序员编写正确的代码。
- 调试器和测试工具:调试器需要能够检查一个类里的私有成员。测试工具可以利用反射来自动地调用类里定义的可被发现的 API 定义,以确保一组测试中有较高的代码覆盖率。
反射的一些缺点:
- 性能开销 :反射涉及了动态类型的解析,所以 JVM 无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被执行的代码或对性能要求很高的程序中使用反射。
- 安全性:反射在一定程度上破坏了安全性。
- 内部暴露:由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用,这可能导致代码功能失调并破坏可移植性。反射代码破坏了抽象性。
反射最重要的用途就是开发各种通用框架。很多框架(比如Spring)都是配置化的(比如通过XML文件配置Bean),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射,运行时动态加载需要加载的对象。
3 反射的基本应用
首先来看一个简单的例子:
package org.example;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
// 正常调用与反射调用
public class ReflectionTest1 {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 正常调用
ReflectionTest1 r = new ReflectionTest1();
r.setName("张三");
String name1 = r.getName();
System.out.println(name1);
// 通过反射调用
// 获取指定类的Class对象,参数为全限定类名
Class<?> aClass = Class.forName("org.example.ReflectionTest1");
// 根据Class对象获取指定名称和参数类型的方法
Method setName = aClass.getMethod("setName", String.class);
// 获取无参构造器
Constructor<?> constructor = aClass.getConstructor();
// 根据构造器创建实例对象
Object newInstance = constructor.newInstance();
// 指定方法调用invoke方法,参数为实例对象和指定方法所需的参数
setName.invoke(newInstance, "李四");
// 根据class对象获取指定名称和参数类型的方法
Method getName = aClass.getMethod("getName");
// 指定方法调用invoke方法
String name2 = (String) getName.invoke(newInstance);
System.out.println(name2);
}
}
1 获取class对象
在反射中,获取某个类的Class对象有三种方法:
- Class.forName静态方法
- 当知道类的全限定类名时,可以通过Class.forName(全限定类名)来获取该类的Class对象。
- .class方法
- 只适用于编译前就知道的class。
- 类对象的getClass方法
- 已存在的一个对象,可以通过调用getClass方法获取该对象所在类的Class对象。 ```java package org.example;
// 获取Class对象的三个方法 public class ReflectionTest2 { public static void main(String[] args) throws ClassNotFoundException { // Class.forName 静态方法 Class<?> aClass = Class.forName(“org.example.ReflectionTest2”);
// .class方法
Class bclass = String.class;
// 类对象的getClass方法
String str = new String("hello");
Class<? extends String> cclass = str.getClass();
}
}
<a name="dsdSP"></a>
### 2 通过反射创建对象
通过反射创建对象的方法有两种:
- 通过Class对象的`newInstance()`方法。
- 该方法只能使用默认的无参构造方法。
- 如果Class对象没有无参构造方法,则会报InstantiationException异常。
- 通过Constructor对象的`newInstance()`方法。
- 可以传递不同的参数来选择不同的构造方法。
```java
package org.example;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
// 创建Class对象的两个方法
public class ReflectionTest3 {
private int id;
public ReflectionTest3() {
}
public ReflectionTest3(int id) {
this.id = id;
}
public void show() {
System.out.println("hello! " + id);
}
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
// Class.forName 静态方法
Class aClass = Class.forName("org.example.ReflectionTest3");
// 通过Class对象的newInstance()方法,必须调用无参构造方法
ReflectionTest3 o = (ReflectionTest3) aClass.newInstance();
o.show();
// 通过Constructor对象的newInstance()方法,有参、无参构造方法均可调用
// 通过Class对象获取无参的Constructor对象
Constructor<?> constructor = aClass.getConstructor();
ReflectionTest3 o1 = (ReflectionTest3) constructor.newInstance();
o1.show();
// 通过Class对象获取有参的Constructor对象
Constructor constructor1 = aClass.getConstructor(int.class);
ReflectionTest3 o2 = (ReflectionTest3) constructor1.newInstance(1);
o2.show();
}
}
3 通过反射获取类属性、方法、构造器
属性(也就是成员变量):
- getFields:获取public修饰的属性。
- getDeclaredFields:获取所有属性,但不包括父类的属性。
- getFields,getDeclaredField类似,不再重复。
方法:
- getMethod:获取一个指定的方法,第一个参数是方法名称,后面是方法参数对应的Class对象。
- getMethods:获取所有public修饰的方法,包括其继承的方法。
- getDeclaredMethods:获取所有方法,但不包括继承的方法。
构造器:
- 通过Class对象的newInstance()方法,必须调用无参构造方法。
- 通过Constructor对象的newInstance()方法,有参、无参构造方法均可调用 ```java package org.example;
import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method;
// 通过反射获取类的属性、方法、构造器 public class ReflectionTest4 { public String name; protected String name2; String name3; private int id;
public ReflectionTest4() {
}
public ReflectionTest4(String name, int id) {
this.name = name;
this.id = id;
}
public void show() {
System.out.println("show! " + id + ", " + name);
}
private void show2() {
System.out.println("show2! " + id + ", " + name);
}
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
ReflectionTest4 r = new ReflectionTest4("zhangsan", 1);
Class<? extends ReflectionTest4> aClass = r.getClass();
// 获取public修饰的属性
Field[] fields = aClass.getFields();
for (Field field : fields) {
System.out.println(field);
}
System.out.println("============");
// 获取所有属性,但不包括父类的属性。
Field[] declaredFields = aClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
System.out.println("============");
// 获取所有public修饰的方法,包括其继承的方法
Method[] methods = aClass.getMethods();
for (Method method : methods) {
System.out.println(method);
}
System.out.println("============");
// 获取所有方法,但不包括继承的方法
Method[] declaredMethods = aClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod);
}
}
} ```