Java反射机制
一、概念
什么是反射?
在运行状态中,对于任意一个类,都能够获取到这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有的方法和属性),这种动态获取的信息以及动态调用对象的方法的功能就称为java语言的反射机制。通俗点讲,通过反射,该类对我们来说是完全透明的,想要获取任何东西都可以。
Class 类与 java.lang.reflect 类库一起对反射的概念进行了支持,该类库包含了 Field,Method,Constructor 类 (每个类都实现了 Member 接口)。这些类型的对象时由 JVM 在运行时创建的,用以表示未知类里对应的成员。
这样你就可以使用 Constructor 创建新的对象,用 get() 和 set() 方法读取和修改与 Field 对象关联的字段,用 invoke() 方法调用与 Method 对象关联的方法。另外,还可以调用 getFields() getMethods() 和 getConstructors() 等很便利的方法,以返回表示字段,方法,以及构造器的对象的数组。这样匿名对象的信息就能在运行时被完全确定下来,而在编译时不需要知道任何事情。
二、反射机制的具体使用
1.获取Class对象(三种方式)
- 通过使用Object中的getClass();方法来获取,但是需要先new一个对象;
- 任何数据类型(基本数据类型+引用数据类型)都有一个“静态”的Class属性:类名.class,但是需要import;
- 通过Class类的静态方法Class.forName(String className); 其中className为类的全称(带包名)。
由于前两种方法都是在知道该类的情况下获取该类的字节码对象,因此不会有异常,但是 Class.forName() 方法如果写错类的路径会报 ClassNotFoundException 的异常。
package com.misert.reflect;
class ObjectDemo{
//定义一个类
}
public class ReflectDemo {
public static void main(String[] args) {
//1.第一种实现方式
ObjectDemo od = new ObjectDemo();
Class<?> objDemo1 = od.getClass();
//2.第二种实现方式
Class<?> objDemo2 = ObjectDemo.class;
//3.第三种实现方式
Class<?> objDemo3 = null;
try {
objDemo3 = Class.forName("com.misert.reflect.ObjectDemo");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
2.通过反射获取构造方法
在Java中,对象的创建是通过new+构造器,而要想通过反射来创建对象,则需要先通过反射获取到构造器,通过构造来创建对象。下面来说下如何通过反射获得构造器:
- 单个获取:
- Constructor<T> getConstructor(类<?>... parameterTypes); 获取单个public的构造方法;
- Constructor<T> getDeclaredConstructor(类<?>... parameterTypes); 获取单个全权限的构造方法;
- 对于类<?>... parameterTypes 具体写法如 String.class ,无参数时可以不写也可以写null
- 批量获取:
- Constructor<?>[] getConstructors(); 获取所有的public的构造方法;
- Constructor<?>[] getDeclaredConstructors(); 获取所有的构造方法(全权限);
- 通过上述方法我们可以获得一个Constructor对象,通过Constructor的newInstance(Object...initargs);来进行实例化。
//定义一个Person类
class Person{
//public构造方法
public Person() {
System.out.println("public构造方法");
}
public Person(String name){
System.out.println("public构造方法 ,参数为:"+ name);
}
//protected构造方法
protected Person(int id) {
System.out.println("protected构造方法 ,参数为:"+id);
}
//私有构造方法
private Person(String name, int id) {
System.out.println("private构造方法 ,参数为: " + name +" + "+id);
}
}
具体实现操作
String classname = "com.misert.reflect.Person";
//1.加载类
Class<?> class1 = Class.forName(classname);
//Class class1 = Person.class;
//2.获得单个public构造器
System.out.println("=============获得单个构造器===============");
//获得public 构造方法
Constructor<Person> con1 = (Constructor<Person>) class1.getConstructor(String.class);
Person p1 = con1.newInstance("hello");
//获得private构造方法
Constructor<Person> con2 = (Constructor<Person>) class1.getDeclaredConstructor(String.class,int.class);
con2.setAccessible(true);// 值为true则指示反射的对象在使用时应该取消Java语言访问检查。
Person p2 = con2.newInstance("java Reflect",10); //创建实例
System.out.println("=============获得所有的构造器===============");
//获得所有的public的构造方法
Constructor[] conArray1 = class1.getConstructors();
System.out.println("总共有多少个public构造器"+conArray1.length);
//获得所有的构造方法
Constructor[] conArray2 = class1.getDeclaredConstructors();
System.out.println("总共有多少个构造器"+conArray2.length);
注意:通过Constructor访问private构造方法进行newInstance()时,需要先将setAccessible(true);值为true则表示反射的对象在使用时应该取消Java语言访问检查。
Class类中的newInstance()与Constructor中的newInstance()的区别:Class类中的newInstance()只能调用public的无参数的构造方法,而Constructor中的则可以调用所有的构造方法。
3.通过反射获得成员变量
- 单个获取:
- Field<T> getField(String name); 获取一个public的名为name的成员变量(字段);
- Field<T> getDeclaredField(String name); 获取单个全权限的名为name的成员变量;
- 批量获取:
- Field<T>[] getFields(); 获所有的public的成员变量(字段);
- Field<T>[] getDeclaredFields(); 获取所有的成员变量;
class Person{
//定义字段
private String name ;
public String title ;
public String text;
protected int id;
double salary;
}
通过反射获取属性获取属性:
String classname = "com.misert.reflect.Person";
//1.加载类
Class<?> class1 = Class.forName(classname);
//2.创建person对象
Person p1 = (Person)class1.newInstance();
System.out.println("=============单个获得属性 ===============");
//访问public属性
Field title = class1.getField("title");
System.out.println(title.getType());
title.set(p1, "标题");
System.out.println(title.getName()+p1.title);
//访问private属性
Field name = class1.getDeclaredField("name");
System.out.println("name字段: "+name.getType());
name.setAccessible(true);
name.set(p1, "张三");
System.out.println("p1的name字段值为:"+name.get(p1));
System.out.println("=============获得所有属性 ===============");
//所有public的字段
Field[] fs1 = class1.getFields();
for (Field field : fs1) {
System.out.println(field.getName());
}
System.out.println("-----------------------------------");
//所有的字段
Field[] fs2 = class1.getDeclaredFields();
for (Field field : fs2) {
System.out.println(field.getName());
}
输出结果:
public构造方法
=============单个获得属性 ===============
class java.lang.String
title标题
name字段: class java.lang.String
p1的name字段值为:张三
=============获得所有属性 ===============
title
text
-----------------------------------
name
title
text
id
salary
注意:
几个常用的Field类的方法
Object get(Object obj) //返回指定对象obj的该字段的值
void set(Object obj, Object value); //设置obj对象的当前Field字段的值为value
访问私有属性时,需要通过Field的setAccessible(true);才能访问和操作。
4.通过反射访问成员方法
- 单个获取:
- Method getMethod(String name,Class<?>...paramType); 获取一个public的成员方法;
- Method getDeclaredMethod(String name,Class<?>...paramType); 获取单个全权限的成员方法;
- 批量获取:
- Method[] getMethods(); 获所有public的成员方法(包含所有父类的方法,如Object的方法)
- Method[] getDeclaredMethods(); 获取所有的成员方法(但不包括继承的);
class Person{
//定义一些成员方法
public void getName() {
System.out.println("getName()");
}
public void getTitle(String title) {
System.out.println("getTitle():"+ title);
}
private int getId() {
return 1234;
}
protected void getText(String name,int id) {
System.out.println("getText(): "+name+" , "+id);
}
void getSalary() {
System.out.println("getSalary()");
}
}
通过方式访问:
public class ReflectTest {
public static void main(String[] args) throws Exception{
String classname = "com.misert.reflect.Person";
//1.加载类
Class<?> class1 = Class.forName(classname);
Person p1 = (Person)class1.newInstance();
System.out.println("=============获得单个成员方法===============");
//获得public的成员方法
Method m1 = class1.getMethod("getTitle", String.class);
m1.invoke(p1, "hello"); //执行方法
//获得private成员方法
Method m2 = class1.getDeclaredMethod("getId", null);
m2.setAccessible(true);
System.out.println((int)m2.invoke(p1)+"");
System.out.println("=============获得所有的成员方法===============");
//获得所有public方法(包括继承来的)
Method[] ms1 = class1.getMethods();
for (Method method : ms1) {
System.out.println(method.getName());
}
System.out.println("-----------------");
//获得所有权限方法(不包括继承得到的)
Method[] ms2 = class1.getDeclaredMethods();
for (Method method : ms2) {
System.out.println(method.getName());
}
}
}
同样的,在访问private成员方法时,需要通过Method设置setAccessible(true);
通过发射调用方法:Method下的 -----> public Object invoke(Object obj, Object... args);
- obj为要调用该方法的对象,如果是static的方法,则可以为null
- args为实参列表,如果没有参数可以不写
3. 反射的应用
1. 通过反射运行配置文件内容
定义一个Student类
public class Student {
public void show(){
System.out.println("is show()");
}
public void show1(String name){
System.out.println("is show1():"+name);
}
}
写一个pro.properties配置文件:
classname=com.misert.reflect.Student
methodname=show1
param=String
测试类:通过反射读取配置文件
package com.misert.reflect;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Properties;
public class ReflectUse {
public static void main(String[] args) {
String classname = getValue("classname");
String methodname = getValue("methodname");
String paramType = getValue("param");
System.out.println(classname+","+methodname);
Class<?> clazz ;
try {
clazz = Class.forName(classname);
Method m = clazz.getMethod(methodname,paramType.getClass() );
m.invoke(clazz.newInstance(), "hello world !");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static String getValue(String name) {
//1.创建Properties对象
Properties p = new Properties();
try {
//2.创建BufferedInputStreamd对象,给文件插上管道
BufferedInputStream bin = new BufferedInputStream(
new FileInputStream("src/com/misert/reflect/doc/pro.properties"));
//3.load配置文件
p.load(bin);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//4.根据key值查询并返回value
return p.getProperty(name);
}
}
需求:
当我们升级这个系统时,不要Student类,而需要新写一个Person的类时,这时只需要更改的pro.properties文件内容就可以了。代码就一点不用改动。
关于Class.forName()进一步了解可以参考:https://www.cnblogs.com/beast-king/p/6807257.html