反射
一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。
反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。这时候,我们使用 JDK 提供的反射 API 进行反射调用。反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法或者属性。
反射机制主要提供以下几种功能
- 在运行时构造任意类的对象。
- 在运行时获取一个类的成员变量和方法。
- 在运行时调用某个类对象的方法和成员变量。
在 Java 这个面向对象的语言中,万事万物皆为对象,使用什么来用于表示每一个 Java 类呢?在 Java 中使用 Class
这个类来表示所有的类。
Class 类
Class 类用于描述一个类的所有信息,例如构造方法,成员变量,成员方法等,类中每一个元素都有一个对应的类来表示。
- 成员变量使用 Field 表示
- 成员方法使用 Method 表示
- 构造函数使用 Constructor 表示
- ...
Class 类常用的方法:
Class 类反射的入口的第一步是首先获取类的 Class 对象
获取一个类的 Class 对象有三种方式
-
通过类名.class 获取
-
通过对象.getClass() 获取
-
通过 Class.forName() 获取
Class<Person> personClass = Person.class;
Class<? extends Person> personClass2 = new Person().getClass();
//forName 还不知道具体的类
Class<?> personClass3 = Class.forName("com.example.reflect.Person");
Constructor
Constructor 代表某个类的构造方法。
获取构造类的 Construtor 对象
- 获取类中所有的构造方法
Constructor<String>[] constructors = (Constructor<String>[]) Class.forName("java.lang.String").getConstructors();
System.out.println("---------------获取所有的构造器start---------------");
for (Constructor<?> constructor : constructors) {
System.out.println(constructor);
}
System.out.println("---------------获取所有的构造器end---------------");
- 获取类的某个构造方法
Constructor<byte[]> constructor = (Constructor<byte[]>) Class.forName("java.lang.String").getConstructor(byte[].class);
创建对象
- 正常创建实例对象
String str = new String(new StringBuffer("Hello World"));
- 反射方式创建对象的两种方式
方式一:
//拿到指定构造器Constructor对象
Constructor<?> constructor = Class.forName("java.lang.String").getConstructor(StringBuffer.class);
//通过 newInstance 创建对象
String str = (String) constructor.newInstance( new StringBuffer("Hello World"));
方式二:
该方法
Class.forName
内部会得到无参构造方法 Constroctor 实例,然后调用Constructor.newInstance()
,它内部会使用缓存机制来保存默认构造方法的示例对象。
Class.forName("java.lang.String").newInstance();
通过源码分析
Class.forName("java.lang.String").newInstance()
内部是如何实现缓存的?
在 ① 处判断缓存的 cachedConstructor
是否为空,它表示的是当前对象的一个无参构造,第一次使用时肯定是为空,那么通过 ② getConstructor0
得到一个 Constructor 无参实例。如果缓存的 cachedConstructor
不为空,那么直接通过③直接创建对象 Constructor.newInstance()。
如果一个类没有无参构造,而直接去调用 Class.forName(...).newInstance() 则会报错。
//Class.java
public T newInstance()
throws InstantiationException, IllegalAccessException
{
//①
if (cachedConstructor == null) {
//代码省略...
try {
Class<?>[] empty = {};
//②
final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
cachedConstructor = c;
} catch (NoSuchMethodException e) {
throw (InstantiationException)
new InstantiationException(getName()).initCause(e);
}
}
Constructor<T> tmpConstructor = cachedConstructor;
//代码省略...
// Run constructor
try {
//②创建对象
return tmpConstructor.newInstance((Object[])null);
} catch (InvocationTargetException e) {
Unsafe.getUnsafe().throwException(e.getTargetException());
// Not reached
return null;
}
}
下面是具体 Class.newInstance()内部调用逻辑
Method
Method 代表某个类的一个方法。
获取 Method 的方式有两种
获取当前类和父类中所有的 public 方法。
public Method getMethod(String name, Class<?>... parameterTypes)
获取本类中所有方法,不包括父类方法。
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
注意:通过 invoke 方法调用私有方法时,需要设置 method.setAccessible(true);
实践:通过反射调用 DoSomething 的 main 方法。
//DoSomething.java
package com.example.reflect;
public class DoSomething {
public static void main(String[] args) {
System.out.println(DoSomething.class.getSimpleName());
for (String arg : args) {
System.out.println(arg);
}
}
@Override
public int hashCode() {
return super.hashCode();
}
}
- 获取表示 main 方法的 Method 对象
Class<?> clazz = Class.forName("com.example.reflect.DoSomething");
//public static void main(String[] args)
Method mainMethod = clazz.getMethod("main", String[].class);
- 反射调用 main 方法
//调用 main 方法。
mainMethod.invoke(null, new Object[]{new String[]{"Hello"}});
这里需要注意一点 JDK1.4 和 JDk1.5 关于 method#invoke 方法的区别:
JDK1.5 public Object invoke(Object obj,Object...args)
JDK1.4 public Object invoke(Object obj,Object[] args)
如果是以一个字符串数组
传入给 invoke
方法,那么 javac
会按照哪种语法给处理呢?
因为 JDK1.5
需要兼容
JDk1.4
因此会按照 JDK1.4
的语法来执行。
表示传入的 method 的参数 new Object[]{}
,这个数组存放的元素才是真正我们要传入的参数,那么这个数组需要存放的是 String[]{"","",""}
数组
所以最终表示为:method.invoke(null,new Object[]{new String[]{"Hello"}})
Field
Field 表示一个类的某一个成员变量。
public class Person {
private int age;
private String name;
public Person() {
}
public Person(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
实践:反射获取一个对象的成员变量和给该成员变量赋值。
//Field 类:代表某个类的一个成员变量
Class<Person> clazz = (Class<Person>) Class.forName("com.example.reflect.Person");
Field ageField = clazz.getDeclaredField("age");
//私有属性需要设置ageField.setAccessible(true)
ageField.setAccessible(true);
Person person = clazz.newInstance();
person.setAge(11);
//获取 age 属性的值
int age = (int) ageField.get(person);
System.out.println(age);
//给 age 属性赋值
ageField.set(person,12);
int ageValue = (int) ageField.get(person);
System.out.println(ageValue);
记录于2019年4月3日
网友评论