反射主要用来动态操纵java代码,能够动态查询类能力。
能够分析类能力的程序成为反射。
Class类
在程序运行期间,Java运行时系统始终为所有对象维护一个运行时类型标识。这个信息会跟踪每个对象所属的类。虚拟机利用运行时类型信息选择要执行的正确方法。
不过,在我们写代码时,可以 使用一个也是的Java类访问这些信息。保存这些信息的类名为Class。
获取Class对象有两种方法:
- Object类的
getClass()
方法; - 静态方法
forName(Sring name)
获得类名name对应的Class对象。
通过class对象的getConstructor()
方法可以获取类的构造器,通过构造器的newInstance()
方法可以构造出对象。其中getConstructor方法会有受检异常抛出NoSuchMethodException异常,newInstance会抛出 InstantiationException, IllegalAccessException, IllegalArgumentException异常。
示例:
先创建两个实体类,一个员工类Employee,一个后端开发Backend继承Employee。
Employee
public class Employee {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
Backend
public class Backend extends Employee{
private String Computer;
public Backend() {
}
public Backend(String computer) {
Computer = computer;
}
public String getComputer() {
return Computer;
}
public void setComputer(String computer) {
Computer = computer;
}
}
测试类
import java.lang.reflect.InvocationTargetException;
import java.util.Random;
public class ReflectTest01 {
public static void main(String[] args) throws Exception {
Backend backend = new Backend();
Class<? extends Backend> cl = backend.getClass();
System.out.println(cl.getName());
Random random = new Random();
System.out.println(random.getClass().getName());
// 还可以通过Class.forName获取类对象
Class<?> backend1 = Class.forName("demo03.Backend");
System.out.println(backend1.getName());
// 虚拟机为每个类型管理一个唯一的Class对象。因此可以通过==比较两个类对象
if (backend.getClass() == Backend.class) {
System.out.println("backend is Backend instance");
}
// 通过类对象构造实例
Class<?> random2 = Class.forName("java.util.Random");
Object obj = random2.getConstructor().newInstance();
System.out.println(obj);
}
}
反射分析类的方法
使用反射机制,可以检查类的结构。
java.lang.reflect
包中有Field
、Method
、Constructor
三个类,分别用于描述类的字段、方法和构造器。
java.lang.Class
对象有以下方法:
Field[] getFields()
:返回一个包含Field对象的数组,这些对象对应这个类或其父类的公共字段。没有则返回空数组。Field[] getDeclaredFields()
:返回的是这个类的全部字段对象的Field数组。Method[] getMethods()
:返回一个包含Method对象的数组,这些对象对应这个类或其父类的公共方法。Method[] getDeclaredMethods()
:返回这个类或接口的全部方法,但是不包括由父类继承来的方法。Constructor[] getConstructors()
:返回一个包含Constructor对象的数组,包含所有公共构造器。Constructor[] getDeclaredConstructors()
:返回一个包含Constructor对象的数组,这个类的全部构造器。isInterface
:如果这个Class对象描述一个interface,则返回true。isEnum
:如果这个Class对象描述一个enum,则返回true。getPackageName
:得到这个类的包的包名,如果这个类型时一个数组,则返回元素类型所属的包,如果这个类型时基本类型,则返回"java.lang"。
其中Field
、Method
、Constructor
三个类的对象还可以做进一步分析,有如下方法:
Class getDeclaringClass()
:返回一个Class对象,表示定义了这个构造器、方法或字段的类。String getName()
:返回构造器名、方法名或字段名。int getModifiers
:返回一个整数,描述这个构造器、方法或字段的修饰符。进一步,可使用Modifier
类的方法来分析这个返回值。Class[] getParameterTypes()
:构造器对象或方法对象使用,返回一个Class对象数组,其中各个对象表示参数的类型。Class[] getReturnTypes()
:方法对象使用,返回一个Class对象数组,其中各个对象表示返回值的类型。Class[] getExceptionTypes()
:构造器对象或方法对象使用,返回一个Class对象数组,其中各个对象表示返回值的类型。
上面已经讲到,Modifier
类可以进一步分析,包含以下方法:
static String toString(int Modifiers)
:返回一个字符串,包含tModifiers中设置的二进制位所对应的修饰符。static boolean isAbstract(int Modifiers)
static boolean isFinal(int Modifiers)
static boolean isInterface(int Modifiers)
static boolean isPrivate(int Modifiers)
static boolean isPublic(int Modifiers)
static boolean isStatic(int Modifiers)
static boolean isSynchronized(int Modifiers)
简单示例如下:
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class ReflectionTest02 {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> cl = Class.forName("demo03.Backend");
String modifiers = Modifier.toString(cl.getModifiers());
if (modifiers.length() > 0) System.out.println("modifiers:" + modifiers);
if (cl.isEnum()) System.out.println("enum " + cl.getName());
else if (cl.isInterface()) System.out.println("interface:" + cl.getName());
else System.out.println("class:" + cl.getName());
// 获取父类的Class对象
Class<?> superclass = cl.getSuperclass();
if (superclass != null && superclass != Object.class) {
System.out.println("super class:" + superclass.getName());
}
// 分析constructor
printConstructors(cl);
// 分析fields
printFields(cl);
}
public static void printConstructors(Class cl) {
System.out.println("===== constructor");
Constructor[] constructors = cl.getDeclaredConstructors();
for (Constructor c : constructors) {
String name = c.getName();
System.out.println("constructor name:" + name);
String modifiers = Modifier.toString(c.getModifiers());
System.out.println("modifiers:"+ modifiers);
// 参数类型
Class[] parameterTypes = c.getParameterTypes();
for (Class parameterType : parameterTypes) {
System.out.println(parameterType.getName());
}
}
}
public static void printFields(Class cl) {
System.out.println("===== fields");
Field[] declaredFields = cl.getDeclaredFields();
for (Field f : declaredFields) {
Class<?> type = f.getType();
System.out.println("type:" + type.getName());
String name = f.getName();
System.out.println("name:" + name);
String modifiers = Modifier.toString(f.getModifiers());
System.out.println("modifiers:"+ modifiers);
}
}
}
Field 对象分析
上面已经可以利用反射来获取Class对象的field,利用反射机制,还可以查看在编译时还不知道的对象字段。
方法如下:
Field getField(String name)
:得到指定名字的公共字段。Field[] getFields()
:得到所有字段的一个数组。Field getDeclaredField(String name)
:得到l类中声明的指定名字的公共字段。Field getDeclaredField()
:得到l类中声明的所有字段的一个数组。
同时Field对象有如下方法:
Object get(Object obj)
:返回obj对象中用这个Field对象描述的字段的值。void set(Object obj, Object value)
:将obj对象中这个Field对象描述的字段设置为一个新值。
但是,如果字段是一个私有字段,则没有方法权限,反射机制受限于Java的访问机制,所以get和set方法会抛出IllegalAccessException异常。不过可以使用setAccessible
方法覆盖Java的访问控制。
void setAccessible(boolean flag)
:设置或取消访问对象的可访问标志,如果拒绝访问则抛出IllegalAccessException异常。boolean trySetAccessible()
:为这个可访问对象设置可访问标志,如果拒绝则返回false。
示例:
import java.lang.reflect.Field;
public class ReflectTest03 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Employee employee = new Employee("张三", 30);
Class<? extends Employee> cl = employee.getClass();
Field f = cl.getDeclaredField("name");
f.setAccessible(true);
Object v = f.get(employee);
System.out.println(v);
f.set(employee, "李四");
System.out.println(employee.getName());
}
}
Method对象分析
Method类有一个invoke
方法,允许调用包装在当前Method对象中的方法。
Object invoke(Object obj, Object... args)
第一个参数时隐式参数,其余的对象是显式参数。对于静态方法,第一个参数会忽略,可以设置为null。
如果返回类型时基本类型,则invoke返回其包装器类型。
除了使用getDeclaredMethods等方法获取Method数组,同Field对象的getField类似,还可以使用Class类的getMethod方法:
Method getMethod(String name, Class... parameterTypes)
示例:
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ReflectTest04 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Employee e = new Employee("张三", 30);
Method m = e.getClass().getMethod("getName");
String s = (String) m.invoke(e);
System.out.println(s);
}
}
虽然可以实现上述功能,但是很容易出错,不是很推荐,如果在调用方法的时候提供了错误的参数,则会抛异常。
另外,invoke的参数和返回值都是Object类型。这意味着必须来回进行多次强转。编译时无法检查错误。步进如此,使用烦着获得方法指针的代码比直接调用方法的代码慢很多。