概述
在实际开发中,有时候会用到反射技术
很多人对此了解的不是很深
今天就来做一个简单的介绍
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法。
对于任意一个对象,都能够调用它的任意一个方法和属性。
这种动态获取信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想剖析一个类,必须先要获取到该类的字节码文件对象。
而解剖使用的就是Class类中的方法,
所以要先获取到每一个字节码文件对应的Class类型的对象。
具体有三种方式
获取Class文件对象的三种方式
具体方式如下图
1:对于一个Java源文件,可以通过包名.类名的方法获取
2:源文件编译成字节码对象后,可以通过字节码对象.class属性获取
3:对象创建完成后,可以通过对象的getClass方法获取
那么,获取到Class文件对象后,如何对对象进行反射呢?
反射的使用
通过反射获得无参构造
在使用反射之前,我们先看下普通的情况下我们是如何调用对象的属性和方法的
先看一个简单的例子:
public class Demo {
public static void main(String[] args) {
Work work = new Work();
work.doWork(new Driver());
}
}
class Work {
public void doWork(Person person) {
person.workIng();
}
}
interface Person {
public void workIng();
}
class Driver implements Person {
@Override
public void workIng() {
System.out.println("司机师傅正在开车");
}
}
这里有一个Work类,里面有一个doWork方法
传入一个Person类进去,执行person的workIng方法
打印结果自然是:
司机师傅正在开车
现在我们用反射来实现一下以上的功能
public static void main(String[] args) throws Exception {
// Work work = new Work();
// work.doWork(new Driver());
Class cls = Class.forName("com.example.javastudy.demo.Driver");
// //newInstance使用类的无参构造去创建对象,如果类没有无参构造,就不能这样创建
Person person = (Person) cls.newInstance();
Work work = new Work();
work.doWork(person);
}
可以看到,这里反射了Driver这个司机类
然后doWork方法执行结果和刚刚是一样的
注意这个newInstance方法
如果类没有无参构造,是不能这样使用的
通过反射获得构造方法
如果要获得一个对象的构造方法,可以通过下面的方式
Class cls = Class.forName("com.example.javastudy.demo.Driver");
Constructor[] constructors = cls.getConstructors();//获得所有构造
getConstructors是获得这个对象的所有构造,以数组方式返回
如果要获得指定的一个构造呢?
可以看下面的例子
我们先对Driver类做一个修改
新增了一个年龄的有参构造
class Driver implements Person {
@Override
public void workIng() {
System.out.println("司机师傅正在开车");
}
int age;//司机年龄
public Driver() {
}
public Driver(int age) {
this.age = age;
}
}
然后进行反射操作:
public static void main(String[] args) throws Exception {
Class cls = Class.forName("com.example.javastudy.demo.Driver");
// Constructor c = cls.getConstructor(int.class);//获得有参构造
Constructor c = cls.getConstructor(int.class);
Driver driver = (Driver) c.newInstance(34);//通过有参构造去创建对象
System.out.println("司机年龄:" + driver.age);
Work work = new Work();
work.doWork(driver);
}
输出结果:
司机年龄:34
司机师傅正在开车
通过反射获得成员变量
现在我们再对Driver类新增一个性别属性,不过是私有的
然后再新增一个msg公有方法和一个printMsg私有方法
class Driver implements Person {
@Override
public void workIng() {
System.out.println("司机师傅正在开车");
}
public int age;//司机年龄
private String sex;//司机性别
public String getSex() {
return sex;
}
public void msg() {
System.out.println("年龄:" + age + ",性别:" + sex);
}
private void printMsg(String str) {
System.out.println(str);
}
public Driver() {
}
public Driver(int age) {
this.age = age;
}
@Override
public String toString() {
return "年龄:" + age + ",性别:" + sex;
}
}
那么如何反射这个私有的sex属性并进行修改呢?
public static void main(String[] args) throws Exception {
//通过反射获取成员变量
Class cls = Class.forName("com.example.javastudy.demo.Driver");
Constructor c = cls.getConstructor(int.class);//获得有参构造
Driver driver = (Driver) c.newInstance(25);//通过有参构造去创建对象
// Field[] fields = cls.getFields();//获得所有成员变量
// Field field = cls.getField("sex");//获得指定名称的成员变量,但不能获取私有的
Field field = cls.getDeclaredField("sex");//获得指定名称的私有成员变量
field.setAccessible(true);//去除私有变量的私有权限,这样就可以赋值了
System.out.println(driver.toString());
field.set(driver,"男");
String sex = (String) field.get(driver);//得到私有变量所对应的值,返回object,可以强转
System.out.println("修改后的性别:" + sex);
System.out.println(driver.toString());
}
打印结果:
年龄:25,性别:null
年龄:25,性别:男
可以看到,成功利用反射将司机的性别改成了男
这里需要注意的是:
getField只能获取公有成员变量
getDeclaredField可以获取私有成员变量
在修改变量属性前,需要先创建出变量对象
然后通过setAccessible方法去掉私有变量的私有权限
最后才可以通过Field的set方法去对私有变量做修改
如果是公有变量,是不需要调用setAccessible方法的
通过反射获得成员方法
获得方法和获得变量其实差不多
这里就直接贴代码了
public static void main(String[] args) throws Exception {
//通过反射获取方法并使用
Class cls = Class.forName("com.example.javastudy.demo.Driver");
Constructor c = cls.getConstructor(int.class);//获得有参构造
Driver driver = (Driver) c.newInstance(25);//通过有参构造去创建对象
// Method[] methods = cls.getDeclaredMethods();//得到所有私有方法
// Method[] methods1 = cls.getMethods();//得到所有方法
Method method2 = cls.getMethod("msg");//获取msg方法
method2.invoke(driver);//等价于 driver.msg();
Method method3 = cls.getDeclaredMethod("printMsg", String.class);//获取私有printMsg方法
method3.setAccessible(true);//去除方法的私有权限
method3.invoke(driver,"这是一个出租车司机");
}
打印结果:
年龄:25,性别:null
这是一个出租车司机
通过反射越过泛型检查
在Java中,
泛型只在编译期有效,在运行期会被擦除掉
所以我们可以利用这一点,越过泛型检查
例子如下:
public static void main(String[] args) throws Exception {
ArrayList<Integer> list = new ArrayList<>();
list.add(111);
list.add(222);
Class cls = Class.forName("java.util.ArrayList");//获取到字节码对象
Method addMethod = cls.getMethod("add", Object.class);
addMethod.invoke(list, "abc");
System.out.println(list);
}
打印结果:
[111, 222, abc]
可以看到,成功在一个integer的list集合
存入了一个String字符串
最后,关于反射的使用就介绍到这里了~