java 反射
1. 什么是反射操作
通过Class实例获取class信息的方法称为反射(Reflection),反射的目的是为了获得某个实例的信息,JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。
2. Class类
我们要怎么才能够获取一个class的Class实例呢,这里有三个方法
2.1. 直接通过一个class的静态变量class获取:
Class c = String.class;
--------------------------------
class java.lang.String
2.2. 如果我们有一个实例变量,可以通过该实例变量提供的getClass()方法获取:
String s = "Hello, World!";
Class c = s.getClass();
--------------------------------
class java.lang.String
2.3. 如果知道一个class的完整类名,可以通过静态方法Class.forName()获取:
在这里 java.lang.String 就是一个class的完整类名,这里检测异常是因为可能存在非法字符,或指定的类名不存在。
try {
// 在这里 java.lang.String 就是一个class的完整类名
Class c = Class.forName("java.lang.String");
System.out.println(c);
} catch (NullPointerException e) {
System.out.println("Error");
}
因为Class实例在JVM中是唯一的,所以上述方法获取的Class实例是同一个实例。
类可以直接通过类名获取,也可以通过类实例化后的对象获取,例如下方的例子:
import java.lang.reflect.Field;
import java.lang.reflect.Method;
class Student {
public int score;
private int grade;
}
public class ReflectionLearn {
public static void main(String[] args) throws Exception {
Class stdClass1 = Student.class;
Student student = new Student();
Class stdClass2 = student.getClass();
System.out.println(stdClass1);
System.out.println(stdClass2);
}
}
---------------------------------------------------------------------
class com.zhbi.source.Student
class com.zhbi.source.Student
3. 访问字段(Field)
对任意的一个Object实例,只要我们获取了它的Class,就可以获取它的一切信息。Class类提供了以下几个方法来获取字段
- Field getField(name):根据字段名获取某个public的field(包括父类)
- Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
- Field[] getFields():获取所有public的field(包括父类)
- Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
使用测试:
import java.lang.reflect.Field;
import java.lang.reflect.Method;
class Person {
public String name;
}
class Student extends Person {
public int score;
private int grade;
}
public class ReflectionLearn {
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"));
}
}
--------------------------------------------------------------
public int com.zhbi.source.Student.score
public java.lang.String com.zhbi.source.Person.name
private int com.zhbi.source.Student.grade
3.1. 字段包含的信息
一个Field对象包含了一个字段的所有信息,Field含有以下方法:
- getName():返回字段名称,例如,"name";name 其实就是变量(属性)名称
- getType():返回字段类型,也是一个Class实例,例如,String.class;
- getModifiers():返回字段的修饰符,它是一个int,不同的bit表示不同的含义。
import java.lang.reflect.Field;
import java.lang.reflect.Method;
class Student {
public int score;
private int grade;
}
public class ReflectionLearn {
public static void main(String[] args) throws Exception {
Class stdClass = Student.class;
// score 其实就是变量(属性)名称,这个名称必须是与被调用变量同名的,
// 并且加上引号,用于赋值
Field field = stdClass.getField("score");
System.out.println(field.getName());
System.out.println(field.getType());
}
}
--------------------------------------------------------------
score
int
3.2. 获取字段值
利用反射拿到字段的一个Field实例只是第一步,我们还可以拿到一个实例对应的该字段的值。
如果这个实例中存在私有的字段,那么会得到一个IllegalAccessException,这是因为需要访问的这个字段被定义为一个private字段,正常情况下,一个类无法访问另外一个类的private字段。
要修复错误,可以将字段的修饰类型private改为public,或者,在调用 Field.get(p) 之前,先写一句 Field.setAccessible(true); 调用Field.setAccessible(true)的意思是,别管这个字段是不是public,一律允许访问。
类中使用 private 对外界不需要访问的实例属性进行封装,使得外界无法进行随意的访问,或非法的访问从而做到更好的模块封装。但是现在我们可以通过Java的反射来获取 private 属性,这似乎违背了java的封装性。其实不然,反射是一种非常规的用法,使用反射,首先代码非常繁琐,其次,它更多地是给工具或者底层框架来使用,目的是在不知道目标实例任何信息的情况下,获取特定字段的值。此外,setAccessible(true)可能会失败。如果JVM运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。例如,某个SecurityManager可能不允许对java和javax开头的package的类调用setAccessible(true),这样可以保证JVM核心库的安全。
import java.lang.reflect.Field;
import java.lang.reflect.Method;
class Student {
public int score = 100;
public int grade = 90;
}
public class ReflectionLearn {
public static void main(String[] args) throws Exception {
// Student std = new Student();
// Class stdClass = std.getClass();
Class stdClass = Student.class;
Field scoreField = stdClass.getField("score");
Field gradeField = stdClass.getField("grade");
System.out.println(scoreField.get(new Student()));
System.out.println(gradeField.get(new Student()));
}
}
----------------------------------------------------------
100
90
3.3. 设置字段值
通过Field实例既然可以获取到指定实例的字段值,自然也可以设置字段的值。设置字段值是通过Field.set(Object, Object)实现的,其中第一个Object参数是指定的实例,第二个Object参数是待修改的值。
import java.lang.reflect.Field;
import java.lang.reflect.Method;
class Student {
public int score = 100;
public int grade = 90;
}
public class ReflectionLearn {
public static void main(String[] args) throws Exception {
// Student std = new Student();
// Class stdClass = std.getClass();
Class stdClass = Student.class;
Field scoreField = stdClass.getField("score");
Field gradeField = stdClass.getField("grade");
Student student = new Student();
scoreField.set(student, 80);
gradeField.set(student, 60);
System.out.println(scoreField.get(student));
System.out.println(gradeField.get(student));
}
}
------------------------------------------------------
80
60
4. 调用方法(Method)
通过Class实例可以获取所有Field对象,同样的可以通过Class实例获取所有Method信息。
Class类提供了以下几个方法来获取Method:
- Method getMethod(name, Class...):获取某个public的Method(包括父类)
- Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类)
- Method[] getMethods():获取所有public的Method(包括父类)
- Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)
4.1. 调用实例方法
Method都含有一个invoke方法,因为调用的方法是实例方法,所以传入的第一个参数,为这个方法所在类的实例。
第一种,被调用的方法不带有参数
第二种,被调用的方法带有参数
import java.lang.reflect.Field;
import java.lang.reflect.Method;
class Student {
public int score = 100;
public int grade = 90;
public void printScore() {
System.out.println("Score is :" + score);
}
public void printGrade(String s) {
System.out.println(s + "grade is :" + grade);
}
}
public class ReflectionLearn {
public static void main(String[] args) throws Exception {
Student std = new Student();
// Class stdClass = std.getClass();
Class stdClass = Student.class;
// printScore 其实就是被调用方法的名称,这个名称必须是与被调用方法同名的,
// 并且加上引号,用于赋值
Method smethod = stdClass.getMethod("printScore");
smethod.invoke(std);
// 这里 String.class 带参数方法的参数类型,这里这个参数的类型是 String
// printGrade 其实就是被调用方法的名称,这个名称必须是与被调用方法同名的,
// 并且加上引号,用于赋值
Method gmethod = stdClass.getMethod("printGrade", String.class);
// 在这里填入参数 "My "
gmethod.invoke(std, "My ");
}
}
------------------------------------------------------
Score is :100
My grade is :90
对Method实例调用invoke就相当于调用该方法,invoke的第一个参数是对象实例,即在哪个实例上调用该方法,后面的可变参数要与方法参数一致,否则将报错。
4.2. 调用静态方法
如果获取到的Method表示一个静态方法,调用静态方法时,由于无需指定实例对象,所以invoke方法传入的第一个参数永远为null。
import java.lang.reflect.Field;
import java.lang.reflect.Method;
class StaticFunction {
public static void printGood(String s) {
System.out.println(s + "very good!");
}
}
public class ReflectionLearn {
public static void main(String[] args) throws Exception {
// Class stdClass = std.getClass();
Class stdfClass = StaticFunction.class;
Method dmethod = stdfClass.getMethod("printGood", String.class);
// 由于调用的是静态方法,所以不需要创建类的实例即可调用,
// 所以传递的实例即为null,方法的参数在这里填入参数为 "Im "
dmethod.invoke(null, "Im ");
}
}
--------------------------------------------------
Im very good!
4.3. 调用非public方法
和Field有些相似,对于非public的私有方法,我们虽然可以通过Class.getDeclaredMethod()获取该方法实例,但直接对其调用将得到一个IllegalAccessException。为了调用非public方法,我们通过Method.setAccessible(true)允许其调用。
import java.lang.reflect.Field;
import java.lang.reflect.Method;
class Student {
public int score = 100;
public int grade = 90;
public void printScore() {
System.out.println("Score is :" + score);
}
private void printGrade(String s) {
System.out.println(s + "grade is :" + grade);
}
}
public class ReflectionLearn {
public static void main(String[] args) throws Exception {
Student std = new Student();
// Class stdClass = std.getClass();
Class stdClass = Student.class;
// 这里 String.class 带参数方法的参数类型,这里这个参数的类型是 String
// printGrade 其实就是被调用方法的名称,这个名称必须是与被调用方法同名的,并且加上引号
Method gmethod = stdClass.getMethod("printGrade", String.class);
// 使得私有的方法允许被调用
gmethod.setAccessible(true);
// 在这里填入参数 "My "
gmethod.invoke(std, "My ");
}
}
5. 调用构造方法
我们通常使用new操作符创建新的实例,如果通过反射来创建新的实例,可以调用Class提供的newInstance()方法,但是,调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。
为了调用任意的(有无参数的)构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。Constructor对象和Method非常类似,不同之处仅在于它是一个构造方法,并且,调用结果总是返回实例。
通过Class实例获取Constructor的方法如下:
- getConstructor(Class...):获取某个public的Constructor;
- getDeclaredConstructor(Class...):获取某个Constructor;
- getConstructors():获取所有public的Constructor;
- getDeclaredConstructors():获取所有Constructor。
注意:Constructor总是当前类定义的构造方法,和父类无关,因此不存在多态的问题。
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
class Student {
public int score = 100;
public int grade = 90;
public Student(String s) {
System.out.println(s);
}
public void printScore() {
System.out.println("Score is :" + score);
}
public void printGrade(String s) {
System.out.println(s + "grade is :" + grade);
}
}
public class ReflectionLearn {
public static void main(String[] args) throws Exception {
// 这里需要注意被调用类的构造函数必须是声明为public的,否则将会发生
// NoSuchMethodException 错误这是因为构造函数没有声明时默认是
// private私有的,所以无法访问
// String.class 指定的是构造函数的参数类型,这里的参数类型是String
Constructor cstr = Student.class.getConstructor(String.class);
// 这里是对类进行实例化,这里传入参数为字符串 "Hello, World!"
Student std = (Student)cstr.newInstance("Hello, World!");
}
}
--------------------------------------------------------------
Hello, World!
这里需要注意,即使是使用Constructor,被调用类的构造函数同样必须是声明为public的,否则将会发生 NoSuchMethodException 错误,这是因为构造函数没有声明时默认是private私有的,所以无法访问。也可以通过setAccessible(true)设置允许访问。但是同样setAccessible(true)可能会失败的。
6. 获取父类的Class
有了Class实例,我们还可以获取它的父类的Class,例如下面这样:
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
class Person {
public int score = 100;
public int grade = 90;
public Person() {
System.out.println("Person");
}
}
class Student extends Person {
public int score = 100;
public int grade = 90;
public Student(String s) {
System.out.println(s);
}
public void printScore() {
System.out.println("Score is :" + score);
}
public void printGrade(String s) {
System.out.println(s + "grade is :" + grade);
}
}
public class ReflectionLearn {
public static void main(String[] args) throws Exception {
Class sclass = Student.class;
Class pclass = sclass.getSuperclass();
System.out.println(sclass);
System.out.println(pclass);
}
}
---------------------------------------------------------
class com.zhbi.source.Student
class com.zhbi.source.Person
7. 获取接口(interfcae)
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
interface Person {
public void speak();
}
class Student implements Person {
public int score = 100;
public int grade = 90;
public Student(String s) {
System.out.println(s);
}
public void printScore() {
System.out.println("Score is :" + score);
}
public void printGrade(String s) {
System.out.println(s + "grade is :" + grade);
}
@Override
public void speak() {
System.out.println("Speak");
}
}
public class ReflectionLearn {
public static void main(String[] args) throws Exception {
Class sclass = Student.class;
// 这里需要定义为一个数组,这是因为一个类可以实现多个接口
Class[] iclass = sclass.getInterfaces();
System.out.println(sclass);
for (Class i : iclass) {
System.out.println(i);
}
}
}
--------------------------------------------------------------
class com.zhbi.source.Student
interface com.zhbi.source.Person
要特别注意:getInterfaces()只返回当前类直接实现的接口类型,并不包括其父类实现的接口类型
8. 继承关系
当我们判断一个实例是否是某个类型时,正常情况下,使用instanceof操作符
Object n = Integer.valueOf(123);
boolean isDouble = n instanceof Double; // 结果为 false
boolean isInteger = n instanceof Integer; // 结果为 true
boolean isNumber = n instanceof Number; // 结果为 true
boolean isSerializable = n instanceof java.io.Serializable; // 结果为 true
如果是两个Class实例,要判断一个向上转型是否成立,可以调用isAssignableFrom()
// Integer i = ?
Integer.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Integer
// Number n = ?
Number.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Number
// Object o = ?
Object.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Object
// Integer i = ?
Integer.class.isAssignableFrom(Number.class); // false,因为Number不能赋值给Integer
9. 动态代理
我们都知道接口和类两者之间有着相似之处,我们也已经知道除了抽象类之外,其他的类都是可以 实例化的,接口虽然和类有相似之处但是我们知道接口是不能够直接实例化的,接口只能通过类实现,通过向上转型并指向某个实例的。
那么有没有可能不编写实现类,直接在运行期创建某个interface的实例呢?答案是可能的,因为Java标准库提供了一种动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface的实例。
一般普通的接口实现方式:这种方式就是我们通常编写代码的方式
interface Person {
public void speak();
}
class Student implements Person {
public int score = 100;
public int grade = 90;
public void printScore() {
System.out.println("Score is :" + score);
}
public void printGrade(String s) {
System.out.println(s + "grade is :" + grade);
}
@Override
public void speak() {
System.out.println("Speak");
}
}
public class ReflectionLearn {
public static void main(String[] args) throws Exception {
Student student = new Student();
student.speak();
}
}
--------------------------------------------------------------
Speak
动态创建的方式:动态创建的方式
interface Person {
public void speak();
}
public class ReflectionLearn {
public static void main(String[] args) throws Exception {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
if (method.getName().equals("speak")) {
System.out.println("Hello, World!");
}
return null;
}
};
Person person = (Person) Proxy.newProxyInstance(
Person.class.getClassLoader(), // 传入ClassLoader
new Class[] { Person.class }, // 传入要实现的接口
handler); // 传入处理调用方法的InvocationHandler
person.speak();
}
}
------------------------------------------------------------
public abstract void com.zhbi.source.Person.speak()
Hello, World!