前言
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
什么是反射?
当我们编写完一个Java项目之后,每个Java文件都会被编译成一个.class文件,这些Class对象承载了这个类的所有信息,包括父类、接口、构造函数、方法、属性等,这些class文件在程序运行时会被ClassLoader加载到虚拟机中。当一个类被加载以后,Java虚拟机就会在内存中自动产生一个Class对象。我们通过new的形式创建对象实际上就是通过这些Class来创建,只是这个过程对于我们是不透明的而已。
反射的工作原理就是借助Class.java、Constructor.java、Method.java、Field.java这四个类在程序运行时动态访问和修改任何类的行为和状态。简单来说就是:反射就是把Java类中的各种成分映射成一个个的Java对象。比如:一个类中的方法,构造函数、属性等等。通过反射把它们变成一个对象。很多框架中都用到了反射,反射是框架设计的灵魂。
在正式讲解反射之前,我们先来讲解什么是正射,它们的区别是什么:
public class ReflectTest {
public String method(){
return "method";
}
public static void main(String[] args) {
//正射
ReflectTest reflectTest = new ReflectTest();
String method = reflectTest.method();
//反射
Class<?> aClass = null;
try {
aClass = Class.forName("com.test.ReflectTest");
Method declaredMethod = aClass.getDeclaredMethod("method");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
正射通过new来实例化对象,在程序运行之前确认对象类型,属于静态编译;反射属于动态编译,只有运行时他才会去获得该对象的实例。
获取Class对象
Class 类的实例表示正在运行的 Java 应用程序中的类和接口。也就是JVM中每个类都有该Class(包括基本数据类型)对象。Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass 方法自动构造的。也就是这不需要我们自己去处理创建,JVM已经帮我们创建好了。
要操作一个类的字节码,需要首先获取到这个类的字节码,有三种获取的方式:
public class ReflectTest {
public static void main(String[] args) {
//方式一:Object的方法
ReflectTest reflectTest = new ReflectTest();
reflectTest.getClass();
//方式二:静态方法
try {
Class<?> aClass = Class.forName("com.test.ReflectTest");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
//方式三:属性
Class reflectTestClass = ReflectTest.class;
}
}
在运行期间,一个类,只有一个Class对象产生。方式一的用法很有趣,对象都实例化了还需要反射,意义不大。
创建新实例
newInstance()
方法内部实际上调用了无参数构造方法,必须保证无参构造存在才可以。
public class ReflectTest {
public void method(){
System.out.println("method");
}
public static void main(String[] args) {
try {
Class<?> aClass = Class.forName("com.test.ReflectTest");
ReflectTest reflectTest = (ReflectTest) aClass.newInstance();
reflectTest.method();
/** Output:
* method
*/
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
构造函数
我们可以通过反射来调用类的构造函数:
public class ReflectTest {
private String p1;
private int p2;
public ReflectTest() {
}
public ReflectTest(String p1, int p2) {
this.p1 = p1;
this.p2 = p2;
}
private ReflectTest(String p1) {
this.p1 = p1;
}
public static void main(String[] args) {
try {
Class<?> aClass = Class.forName("com.test.ReflectTest");
System.out.println("==========获取所有公有的构造函数==========");
Constructor<?>[] constructors = aClass.getConstructors();
for (Constructor con:constructors) {
System.out.println(con);
}
System.out.println("==========获取指定公有的构造函数==========");
Constructor<?> constructor = aClass.getConstructor();
System.out.println(constructor);
System.out.println("==========获取所有(包括:私有、受保护、默认、公有)的构造函数==========");
constructors = aClass.getDeclaredConstructors();
for (Constructor con:constructors) {
System.out.println(con);
}
System.out.println("==========获取指定(包括:私有、受保护、默认、公有)的构造函数==========");
constructor = aClass.getDeclaredConstructor(String.class);
System.out.println(constructor);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
/** Output:
* ==========获取所有公有的构造函数==========
* public com.study.test.ReflectTest(java.lang.String,int)
* public com.study.test.ReflectTest()
* ==========获取指定公有的构造函数==========
* public com.study.test.ReflectTest()
* ==========获取所有(包括:私有、受保护、默认、公有)的构造函数==========
* private com.study.test.ReflectTest(java.lang.String)
* public com.study.test.ReflectTest(java.lang.String,int)
* public com.study.test.ReflectTest()
* ==========获取指定(包括:私有、受保护、默认、公有)的构造函数==========
* private com.study.test.ReflectTest(java.lang.String)
*/
}
}
getConstructors()
方法可以获取反射中所有公有的构造函数和getConstructor()
方法可以获取反射中指定公有的构造函数;getDeclaredConstructors()
方法可以获取反射中所有构造函数不受访问权限限制和getDeclaredConstructor()
方法可以获取反射中指定构造函数不受访问权限限制。
其中getConstructor()
方法和getDeclaredConstructor()
方法中可传入相对应的参数类型这样就能调用相对应的构造函数,如果是无参则可以不填,默认调用无参构造。
实例化
既然知道如何通过反射得到构造函数,就可以通过newInstance()
方法去调用对应的构造函数。
public class ReflectTest {
private String p1;
private int p2;
public ReflectTest() {
System.out.println("empty constructor");
}
public ReflectTest(String p1, int p2) {
System.out.println("p1:"+p1+",p2:"+p2);
this.p1 = p1;
this.p2 = p2;
}
private ReflectTest(String p1) {
System.out.println("p1:"+p1);
this.p1 = p1;
}
public static void main(String[] args) {
try {
Class<?> aClass = Class.forName("com.test.ReflectTest");
System.out.println("==========实例化指定公有的构造函数==========");
Constructor<?> constructor = aClass.getConstructor(String.class,int.class);
constructor.newInstance("test",12);
System.out.println("==========实例化指定(包括:私有、受保护、默认、公有)的构造函数==========");
constructor = aClass.getDeclaredConstructor(String.class);
constructor.newInstance("test");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
/** Output:
* ==========实例化指定公有的构造函数==========
* p1:test,p2:12
* ==========实例化指定(包括:私有、受保护、默认、公有)的构造函数==========
* p1:test
*/
}
}
在调用newInstance()
方法时可以对指定构造函数的参数类型进行赋值操作,如果没有,可忽略。
方法
反射除了可以调用构造函数之外,也可以调用方法:
public class ReflectTest {
public void method(){
}
public String method2(int param,String param2){
return "method2";
}
private void method3(){
}
public static void main(String[] args) {
try {
Class<?> aClass = Class.forName("com.test.ReflectTest");
System.out.println("==========获取所有公有的方法==========");
Method[] method = aClass.getMethods();
for (Method method1:method) {
System.out.println(method1);
}
System.out.println("==========获取指定公有的方法==========");
Method method2 = aClass.getMethod("method2", int.class, String.class);
System.out.println(method2);
System.out.println("==========获取所有(包括:私有、受保护、默认、公有)的方法==========");
method = aClass.getDeclaredMethods();
for (Method method1:method) {
System.out.println(method1);
}
System.out.println("==========获取指定(包括:私有、受保护、默认、公有)的方法==========");
Method method3 = aClass.getDeclaredMethod("method3");
System.out.println(method3);
/** Output:
* ==========获取所有公有的方法==========
* 省略系统自带原始方法...
* public void com.test.ReflectTest.method()
* public java.lang.String com.test.ReflectTest.method2(int,java.lang.String)
* ==========获取指定公有的方法==========
* public java.lang.String com.test.ReflectTest.method2(int,java.lang.String)
* ==========获取指定(包括:私有、受保护、默认、公有)的方法==========
* 省略系统自带原始方法...
* public void com.test.ReflectTest.method()
* public java.lang.String com.test.ReflectTest.method2(int,java.lang.String)
* private void com.test.ReflectTest.method3()
* ==========获取指定(包括:私有、受保护、默认、公有)的方法==========
* private void com.test.ReflectTest.method3()
*/
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
getMethods()
方法可以获取反射中所有公有方法和getMethod()
方法可以获取反射中指定公有方法;getDeclaredMethods()
方法可以获取反射中所有方法不受访问权限限制和getDeclaredMethod()
方法可以获取反射中指定方法不受访问权限限制。
其中getMethod()
方法和getDeclaredMethod()
方法中有两个参数:第一个参数是对应的方法名;第二个参数对应的参数类型(可传多个),如果方法没有参数则可以不填。
调用
既然知道如何通过反射得到方法,就可以通过invoke()
方法去调用对应的方法。
public class ReflectTest {
public void method(){
System.out.println("method");
}
public String method2(int param,String param2){
System.out.println("param:"+param+",param2:"+param2);
return "method2";
}
private void method3(){
System.out.println("method3");
}
public static void main(String[] args) {
try {
Class<?> aClass = Class.forName("com.test.ReflectTest");
Object reflectTest = aClass.newInstance();
System.out.println("==========获取指定公有的方法==========");
Method method2 = aClass.getMethod("method2", int.class, String.class);
method2.invoke(reflectTest,100,"test");
System.out.println("==========获取指定(包括:私有、受保护、默认、公有)的方法==========");
Method method3 = aClass.getDeclaredMethod("method3");
method3.invoke(reflectTest);
/** Output:
* ==========获取指定公有的方法==========
* param:100,param2:test
* ==========获取指定(包括:私有、受保护、默认、公有)的方法==========
* method3
*/
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
invoke()
方法有两个参数:第一个参数是调用方法的对象;第二个参数是方法的参数内容(可以有多个参数),如果没有参数,可忽略。
字段
当然可以通过反射来获取字段属性:
public class ReflectTest {
private String param;
public Long param1;
protected Integer param2;
public static void main(String[] args) {
try {
Class<?> aClass = Class.forName("com.test.ReflectTest");
Object reflectTest = aClass.newInstance();
System.out.println("==========获取所有公有的字段==========");
Field[] fields = aClass.getFields();
for (Field field:fields) {
System.out.println(field);
}
System.out.println("==========获取指定公有的字段==========");
Field param = aClass.getField("param1");
System.out.println(param);
System.out.println("==========获取所有(包括:私有、受保护、默认、公有)的字段==========");
fields = aClass.getDeclaredFields();
for (Field field:fields) {
System.out.println(field);
}
System.out.println("==========获取指定(包括:私有、受保护、默认、公有)的字段==========");
param = aClass.getDeclaredField("param2");
System.out.println(param);
/** Output:
* ==========获取所有公有的字段==========
* public java.lang.Long com.test.ReflectTest.param1
* ==========获取指定公有的字段==========
* public java.lang.Long com.test.ReflectTest.param1
* ==========获取所有(包括:私有、受保护、默认、公有)的字段==========
* private java.lang.String com.test.ReflectTest.param
* public java.lang.Long com.test.ReflectTest.param1
* protected java.lang.Integer com.test.ReflectTest.param2
* ==========获取指定(包括:私有、受保护、默认、公有)的字段==========
* protected java.lang.Integer com.test.ReflectTest.param2
*/
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
getFields()
方法可以获取反射中所有公有字段和getField()
方法可以获取反射中指定公有字段;getDeclaredFields()
方法可以获取反射中所有字段不受访问权限限制和getDeclaredField()
方法可以获取反射中指定字段不受访问权限限制。
其中getField()
方法和getDeclaredField()
方法通过传入指定字段名称,来获取字段信息。
赋值和获取
我们可以通过set()
方法和get()
方法,为字段赋值和获取值:
public class ReflectTest {
private String param;
public Long param1;
protected Integer param2;
public static void main(String[] args) {
try {
Class<?> aClass = Class.forName("com.test.ReflectTest");
Object reflectTest = aClass.newInstance();
System.out.println("==========获取指定公有的字段==========");
Field param = aClass.getField("param1");
param.set(reflectTest,1L);
System.out.println(param.get(reflectTest));
System.out.println("==========获取指定(包括:私有、受保护、默认、公有)的字段==========");
param = aClass.getDeclaredField("param2");
param.set(reflectTest,100);
System.out.println(param.get(reflectTest));
/** Output:
* ==========获取指定公有的字段==========
* 1
* ==========获取指定(包括:私有、受保护、默认、公有)的字段==========
* 100
*/
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
set()
方法有两个参数:第一个参数是赋值字段的对象;第二个参数字段的值。get()
方法通过传入对象参数,获取对象的字段值。
setAccessible()方法
上面的所有案例都是再本内中操作的,所以看不出效果,如果再其他类中使用,会看出不同的效果:
public class ReflectTest {
private String param;
public Long param1;
protected Integer param2;
}
public class Test {
public static void main(String[] args) {
try {
Class<?> aClass = Class.forName("com.study.test.ReflectTest");
Object reflectTest = aClass.newInstance();
System.out.println("==========获取指定(包括:私有、受保护、默认、公有)的字段==========");
Field param = aClass.getDeclaredField("param");
param.set(reflectTest,"13");
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
抛出错误:
java.lang.IllegalAccessException: Class com.test.Test can not access a member of class com.test.ReflectTest with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
at java.lang.reflect.Field.set(Field.java:761)
at com.study.test.Test.main(Test.java:28)
如果通过反射访问其他内的私有方法、属性等,需要通过setAccessible()
方法取消访问权限检查。
public class ReflectTest {
private String param;
public Long param1;
protected Integer param2;
}
public class Test {
public static void main(String[] args) {
try {
Class<?> aClass = Class.forName("com.test.ReflectTest");
Object reflectTest = aClass.newInstance();
System.out.println("==========获取指定(包括:私有、受保护、默认、公有)的字段==========");
Field param = aClass.getDeclaredField("param");
param.setAccessible(true);
param.set(reflectTest,"13");
System.out.println(param.get(reflectTest));
/** Output
* ==========获取指定(包括:私有、受保护、默认、公有)的方法==========
* 13
*/
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
}
setAccessible()
方法表示:值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查;由于JDK的安全检查耗时较多.所以通过setAccessible(true)
的方式关闭安全检查就可以达到提升反射速度的目的 。
反射main()方法
似乎实际也不怎么会用到,但还是要讲解:
public class ReflectTest {
public static void main(String[] args) {
System.out.println("invoke main()");
}
}
public class Test {
public static void main(String[] args) {
try {
Class<?> aClass = Class.forName("com.test.ReflectTest");
Object reflectTest = aClass.newInstance();
Method main = aClass.getMethod("main", String[].class);
main.invoke(reflectTest,new String[1]);
/** Output
* invoke main()
*/
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
运行配置文件内容
我们可以通过反射来获取配置文件信息,这样无需对源码进行更新,即可获取最新的配置内容。
先创建一个文本文件:
代码示例如下:
public class ReflectTest {
public static void main(String[] args) {
//工具类
Properties properties = new Properties();
try {
//加载文件
properties.load(new FileReader("D:\\mnt\\this.txt"));
//通过key获取value
Object aPackage = properties.get("package");
System.out.println(aPackage);
//通过key获取value
Object aClass = properties.get("class");
System.out.println(aClass);
/** Output:
* com.test.ReflectTest
* ReflectTest
*/
} catch (IOException e) {
e.printStackTrace();
} finally {
properties.clone();
}
}
}
越过泛型检查
泛型用在编译期,编译过后泛型擦除(消失掉)。所以可以通过反射越过泛型检查:
public class ReflectTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
Class aClass = list.getClass();
try {
Method add = aClass.getMethod("add", Object.class);
add.invoke(list,100);
System.out.println(list.toString());
/** Output:
* [a, b, c, 100]
*/
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
本章小结
在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量、方法或是属性是私有的或是只对系统应用开放,这时候就可以利用Java的反射机制通过反射来获取所需的私有成员或是方法。当然,也不是所有的都适合反射。