目录
一.什么是反射?
二.反射的核心方法和功能:
1.获取类的元信息:
2. 动态实例化对象:
3. 访问字段(包括私有字段):
4. 调用方法(包括私有方法):
5.动态代理:
一.什么是反射?
Java 反射(Reflection)是 Java 提供的一种功能,可以在运行时检查类、接口、字段和方法的信息,同时还可以动态调用对象的方法或构造器,甚至修改字段的值。反射使程序具有很强的动态性,但也可能带来一定的性能和安全问题。
反射的核心是
java.lang.reflect
包,其中包含了如下主要类:
Class
:提供了类的元信息。Field
:表示类的字段。Method
:表示类的方法。Constructor
:表示类的构造方法。
class
(包括interface
)的本质是数据类型(Type
)。无继承关系的数据类型无法赋值:Number n = new Double(123.456); // OK String s = new Double(123.456); // compile error!
而
class
是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class
类型时,将其加载进内存。每加载一种class
,JVM就为其创建一个Class
类型的实例,并关联起来。它长这样:public final class Class {private Class() {} }
所以,JVM持有的每个
Class
实例都指向一个数据类型(class
或interface
),并且一个Class
实例包含了该class
的所有完整信息。由于JVM为每个加载的
class
创建了对应的Class
实例,并在实例中保存了该class
的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等。因此,如果获取了某个Class
实例,我们就可以通过这个Class
实例获取到该实例对应的class
的所有信息。而通过Class
实例获取class
信息的方法称为反射(Reflection)。
二.反射的核心方法和功能:
反射的核心方法和功能如下:
- 获取类的元信息
- 动态实例化对象
- 访问字段(包括私有字段)
- 调用方法(包括私有方法)
1.获取类的元信息:
通过反射获取类的包名、类名和方法等信息。
import java.lang.reflect.Method;public class ReflectionExample {public static void main(String[] args) {try {// 获取 Class 对象:// 1. 使用 Class.forName()Class<?> clazz = Class.forName("java.util.ArrayList");// 2. 使用 类名.classClass<?> clazz2 = ArrayList.class;// 3. 使用对象.getClass()ArrayList<String> list = new ArrayList<>();Class<?> clazz3 = list.getClass();System.out.println(clazz1 == clazz2); // trueSystem.out.println(clazz2 == clazz3); // true// 打印类名和包名System.out.println("类名: " + clazz.getName());System.out.println("简单类名: " + clazz.getSimpleName());System.out.println("包名: " + clazz.getPackage().getName());// 获取所有方法Method[] methods = clazz.getMethods();System.out.println("类中的方法:");for (Method method : methods) {System.out.println(method.getName());}} catch (ClassNotFoundException e) {e.printStackTrace();}}
}
输出示例: 类名: java.util.ArrayList 简单类名: ArrayList 包名: java.util 类中的方法: add remove size clear ...
Class.forName
:通过类的完全限定名动态加载类。getMethods
:返回类及其父类的所有公共方法。
注意一下
Class
实例比较和instanceof
的差别:Integer n = new Integer(123);boolean b1 = n instanceof Integer; // true,因为n是Integer类型 boolean b2 = n instanceof Number; // true,因为n是Number类型的子类boolean b3 = n.getClass() == Integer.class; // true,因为n.getClass()返回Integer.class Class c1 = n.getClass(); Class c2 = Number.class; boolean b4 = c1 == c2; // false,因为Integer.class != Number.class
用
instanceof
不但匹配指定类型,还匹配指定类型的子类。而用==
判断class
实例可以精确地判断数据类型,但不能作子类型比较。而通常情况下,我们应该用
instanceof
判断数据类型,因为在面向抽象编程的时候,我们不关心具体的子类型。只有在需要精确判断一个类型是不是某个class
的时候,我们才使用==
判断class
实例。
注意:数组(例如
String[]
)也是一种类,而且不同于String.class
,它的类名是[Ljava.lang.String;
。此外,JVM为每一种基本类型如int
也创建了Class
实例,通过int.class
访问。因为反射的目的是为了获得某个实例的信息。因此,当我们拿到某个
Object
实例时,我们就可以通过反射获取该Object
的class
信息:void printObjectInfo(Object obj) {Class clazz = obj.getClass(); }
2. 动态实例化对象:
通过反射动态创建类的实例。
假如我们获取到了一个
Class
实例,我们就可以通过该Class
实例来创建对应类型的实例:// 获取String的Class实例: Class cls = String.class; // 创建一个String实例: String s = (String) cls.newInstance();
上述代码相当于
new String()
。通过Class.newInstance()
可以创建类实例,它的局限是:只能调用public
的无参数构造方法。带参数的构造方法,或者非public
的构造方法都无法通过Class.newInstance()
被调用。
为了调用任意的构造方法,Java的反射API提供了Constructor
对象,它包含一个构造方法的所有信息,可以创建一个实例。Constructor
对象和Method非常类似,不同之处仅在于它是一个构造方法,并且调用结果总是返回实例:
import java.lang.reflect.Constructor;public class DynamicInstanceExample {public static void main(String[] args) {try {// 获取 Class 对象Class<?> clazz = Class.forName("java.lang.String");// 获取构造方法(使用 getConstructor 获取特定参数的构造器)Constructor<?> constructor = clazz.getConstructor(String.class);// 创建对象实例(通过 newInstance 动态创建对象)Object instance = constructor.newInstance("Hello Reflection");System.out.println("实例内容: " + instance); // 实例内容: Hello Reflection// 获取构造方法Integer(int):Constructor cons1 = Integer.class.getConstructor(int.class);// 调用构造方法:Integer n1 = (Integer) cons1.newInstance(123);System.out.println(n1); // 123// 获取构造方法Integer(String)Constructor cons2 = Integer.class.getConstructor(String.class);Integer n2 = (Integer) cons2.newInstance("456");System.out.println(n2); // 456} catch (Exception e) {e.printStackTrace();}}
}
3. 访问字段(包括私有字段):
对任意的一个Object
实例,只要我们获取了它的Class
,就可以获取它的一切信息。而在Java中,Field
类是Java反射机制的一部分,用于表示类的成员变量。通过Field
对象,我们可以获取类的字段(成员变量)的信息,并且能够在运行时动态地访问或修改这些字段的值。
要获取类的字段(成员变量),需要通过
Class
对象获取对应的Field
对象。可以通过以下两种方式:
获取类的所有字段:使用
getDeclaredFields()
方法可以获取类中所有的字段,包括私有字段(private
)和公共字段(public
)。获取某个特定字段:使用
getDeclaredField(String name)
方法获取指定名称的字段。
- Field getField(name):根据字段名获取某个public的field(包括父类)
- Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
- Field[] getFields():获取所有public的field(包括父类)
- Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
import java.lang.reflect.Field;class Person {private String name = "John";
}public class AccessPrivateFieldExample {public static void main(String[] args) {try {Person person = new Person();Class<?> clazz = person.getClass();// 获取类的私有字段Field field = clazz.getDeclaredField("name");field.setAccessible(true); // 取消 Java 的访问权限检查// 获取和修改字段值System.out.println("原始值: " + field.get(person));field.set(person, "Alice");System.out.println("修改后的值: " + field.get(person));} catch (Exception e) {e.printStackTrace();}}
}
下面是使用field对不同权限属性的访问:
// reflection
public class Main {public static void main(String[] args) throws Exception {Class<?> stdClass = Student.class;// 获取public字段"score":System.out.println(stdClass.getField("score"));// 获取继承的public字段"name":System.out.println(stdClass.getField("name"));// 获取private字段"grade":System.out.println(stdClass.getDeclaredField("grade"));}
}class Student extends Person {public int score;private int grade;
}class Person {public String name;
}
还有其他的常见方法:
getName()
:获取字段的名称。getType()
:获取字段的类型(返回Class
对象)。getModifiers()
:获取字段的修饰符(如public
、private
等)。set(Object obj, Object value)
:设置字段值。get(Object obj)
:获取字段值。
Field
类使得Java反射具有非常强的动态性,允许我们在程序运行时进行更细粒度的操作。不过,过多使用反射可能会影响性能,并增加代码的复杂性,因此建议在需要动态行为或框架开发时使用。
4. 调用方法(包括私有方法):
我们已经能通过Class
实例获取所有Field
对象,同样我们也可以通过Class
实例获取所有Method
信息。
Class
类提供了以下几个方法来获取Method
:
Method getMethod(name, Class...)
:获取某个public
的Method
(包括父类)Method getDeclaredMethod(name, Class...)
:获取当前类的某个Method
(不包括父类)Method[] getMethods()
:获取所有public
的Method
(包括父类)Method[] getDeclaredMethods()
:获取当前类的所有Method
(不包括父类)
// reflection
public class Main {public static void main(String[] args) throws Exception {Class stdClass = Student.class;// 获取public方法getScore,参数为String:System.out.println(stdClass.getMethod("getScore", String.class));// 获取继承的public方法getName,无参数:System.out.println(stdClass.getMethod("getName"));// 获取private方法getGrade,参数为int:System.out.println(stdClass.getDeclaredMethod("getGrade", int.class));}
}class Student extends Person {public int getScore(String type) {return 99;}private int getGrade(int year) {return 1;}
}class Person {public String getName() {return "Person";}
}
一个
Method
对象包含一个方法的所有信息:
getName()
:返回方法名称,例如:"getScore"
;getReturnType()
:返回方法返回值类型,也是一个Class实例,例如:String.class
;getParameterTypes()
:返回方法的参数类型,是一个Class数组,例如:{String.class, int.class}
;getModifiers()
:返回方法的修饰符,它是一个int
,不同的bit表示不同的含义。
我们正常的(直接的)方法调用指的是通过对象直接调用类中的方法。例如:
String s = "Hello world";
String result = s.substring(6); // 直接调用substring方法
System.out.println(result); // 输出 "world"
而反射调用则是通过Java反射机制来动态地调用对象的方法。通过Method.invoke()
,可以在运行时决定调用哪个方法,并传递参数。例如:
import java.lang.reflect.Method;public class Main {public static void main(String[] args) throws Exception {String s = "Hello world";Method m = String.class.getMethod("substring", int.class); // 获取substring方法String result = (String) m.invoke(s, 6); // 动态调用substring方法System.out.println(result); // 输出 "world"}
}
invoke
的第一个参数是对象实例,即在哪个实例上调用该方法,后面的可变参数要与方法参数一致,否则将报错。
反射调用特点:
- 使用
invoke
时,方法的调用是动态的,在运行时解析方法。你需要提供Method
对象来引用类中的方法。- 可以调用私有方法或无法直接访问的非公共方法(通过
setAccessible(true)
来访问)。- 反射调用的参数和返回值是通过
Object
类型来传递和返回的,因此需要进行类型转换。- 反射方法的调用通常比直接调用慢,因为它需要通过反射机制来查找和执行方法。
- 能动态地加载和调用方法,使得代码更具灵活性(例如插件系统、动态执行等)。
invoke
方法比正常的直接方法调用灵活,但代价是性能上有所牺牲,并且不能享受编译时的类型检查。
特性 | 正常方法调用 | 反射调用 invoke |
---|---|---|
性能 | 较高,直接编译时检查方法 | 较低,涉及反射和运行时解析 |
编译时检查 | 直接通过类的方法签名进行检查 | 不在编译时检查,运行时才解析 |
灵活性 | 方法在编译时已固定,不能动态选择方法 | 可以动态选择方法,适用于动态行为 |
方法调用 | 直接调用方法,参数明确 | 需要通过反射获取Method 对象,并传递参数 |
访问控制 | 只能访问公共方法 | 可以通过setAccessible(true) 访问私有方法 |
eg:
import java.lang.reflect.Method;class Calculator {private int add(int a, int b) {return a + b;}
}public class InvokePrivateMethodExample {public static void main(String[] args) {try {Calculator calculator = new Calculator();Class<?> clazz = calculator.getClass();// 获取私有方法Method method = clazz.getDeclaredMethod("add", int.class, int.class);method.setAccessible(true);// 调用私有方法Object result = method.invoke(calculator, 10, 20);System.out.println("结果: " + result);} catch (Exception e) {e.printStackTrace();}}
}
5.动态代理:
Java的动态代理(Dynamic Proxy)是指在程序运行时,动态生成代理类并创建代理对象的技术。通过动态代理,可以在不修改目标类的情况下,增加额外的功能或逻辑(如日志记录、性能监控、权限控制等)。Java的动态代理有两种常见的实现方式:JDK动态代理和CGLIB动态代理。在这里,下面我们将重点讲解JDK动态代理,并涉及CGLIB的相关内容。
(1)动态代理的思想:
通过反射机制,在运行时创建一个代理类,该类实现目标接口,并且代理类内部将调用实际目标对象的方法。在调用过程中,代理类可以执行额外的操作(如日志、事务等)再调用目标方法。
(2)动态代理的好处:
- 无需修改目标类:代理类不需要继承或实现目标类,只需要实现目标类的接口。
- 灵活性高:代理行为可以在运行时动态生成和调整。
- 增强功能:可以在不改变原有代码的情况下为目标方法提供额外的功能。
那么如何不编写实现类,直接在运行期创建某个interface
的实例呢?
动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface
的实例。
我们仍然先定义了接口Hello
,但是我们并不去编写实现类,而是直接通过JDK提供的一个Proxy.newProxyInstance()
创建了一个Hello
接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码。JDK提供的动态创建接口对象的方式,就叫动态代理。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;public class Main {public static void main(String[] args) {// InvocationHandler 是一个接口,它必须实现 invoke() 方法。这个方法在代理实例的方法被调用时执行。InvocationHandler handler = new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(method);if (method.getName().equals("morning")) {System.out.println("Good morning, " + args[0]);}return null;}};// 创建了一个Hello接口对象。这种没有实现类但是在运行期动态创建了一个接口对象的方式,我们称为动态代码Hello hello = (Hello) Proxy.newProxyInstance(Hello.class.getClassLoader(), // 传入用于加载代理类的ClassLoader,通常可以使用接口的getClassLoader()来获取。new Class[] { Hello.class }, // 传入需要实现的接口数组handler // 传入处理调用方法的InvocationHandler,包含具体的业务逻辑); // 调用代理对象hello的morning方法hello.morning("Bob");}
}interface Hello {void morning(String name);
}
invoke()
方法接收三个参数:
proxy
: 被代理的对象(即代理类实例)。method
: 被调用的方法对象,包含了方法的名称、参数类型等信息。args
: 被调用方法的参数。