一. 什么是反射
反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。当我们编写完一个Java项目之后,每个java文件都会被编译成一个.class文件,这些Class对象承载了这个类的所有信息,包括父类、接口、构造函数、方法、属性等,这些class文件在程序运行时会被ClassLoader加载到虚拟机中。
二. 反射有哪些功能
反射机制主要提供了以下功能:
-
在运行时判断任意一个对象所属的类;
-
在运行时构造任意一个类的对象;
-
在运行时判断任意一个类所具有的成员变量和方法;
-
运行时改变一个类的属性的值;
-
在运行时调用任意一个对象的方法;
-
生成动态代理。
三. 如何使用相关API
1. 反射相关的类
Class —— 使用反射的最基本类
Constructor —— 表示一个类的构造器的类
Method —— 表示一个类的方法的类
Field —— 表示一个类属性的类
Member —— 成员是一种接口,反映有关单个成员(字段或方法)或构造方法的标识信息
Modifier —— 表示成员权限修饰符的类
Proxy —— 提供动态地生成代理类和类实例的静态方法
2. 各主要类的常用API
(1)Class 类
获取Class实例
Class clazz = TestClass.class; //通过类名.class
Class clazz = obj.getClass(); //通过对象的getClass方法
Class clazz = Class.forName("com.whx.test.TestClass"); //通过Class的forName方法,传入一个类的全限定名
获取一个对象的父类与实现的接口
Class<?> parentClass = clazz.getSuperclass(); // 取得父类
Class<?> intes[] = clazz.getInterfaces(); // 获取所有的接口
获取某个类中的构造方法
Constructor<T> con = clazz.getConstructor(Class<?>... parameterTypes); //根据给定参数获得构造方法对象,仅public
Constructor<T> con = clazz.getDeclaredConstructor(Class<?>... parameterTypes); // 根据给定参数获得构造方法对象,所有
Constructor<?>[] cons = clazz.getConstructors(); // 获取到所有public构造方法对象
Constructor<?>[] cons = clazz.getDeclaredConstructors(); // 获取到所有构造方法对象
获取某个类中的属性
Field field = clazz.getDeclaredField(String name); //根据属性名获取到属性对象,任意访问权限
Field field = clazz.getField(String name); //根据属性名获取到属性对象,仅public
Field[] field = clazz.getDeclaredFields(); //获得当前类的所有属性
Field[] field = clazz.getFields(); //获得所有public属性,包括继承而来的public属性
获取某个类中的方法
Method method = clazz.getMethod(String name, Class<?>... parameterTypes); //根据方法名和参数类型获得方法对象,仅能获取public方法
Method method = clazz.getDeclaredMethod(String name, Class<?>... parameterTypes); //根据方法名和参数类型获得方法对象
Method[] methods = clazz.getMethods(); //获取所有public方法,包括父类的public方法
Method[] methods = clazz.getDeclaredMethods(); //获取当前类的所有方法
(2)Method类
获取方法的相关信息
int modifiers = method.getModifiers(); // 获取方法的访问权限
System.out.print(Modifier.toString(temp)); // 输出访问修饰符
Class<?> returnType = method.getReturnType(); // 方法返回类型
System.out.print(returnType.getName());
String name = method.getName(); // 方法名
Class<?> params[] = method.getParameterTypes(); // 所有参数类型
Parameter[] parameters = method.getParameters(); // 所有参数对象
Class<?>[] exceptionTypes = method.getExceptionTypes(); // 方法抛出的所有异常类型
通过反射调用私有方法
//1. 获取 Class 类实例
TestClass testClass = new TestClass();
Class clazz = testClass.getClass();
//2. 获取私有方法
//第一个参数为要获取的私有方法的名称
//第二个为要获取方法的参数的类型,参数为 Class...,没有参数就是null
//方法参数也可这么写 :new Class[]{String.class , int.class}
Method privateMethod = clazz.getDeclaredMethod("privateMethod", String.class, int.class);
//3. 开始操作方法
if (privateMethod != null) {
//获取私有方法的访问权
//只是获取访问权,并不是修改实际权限
privateMethod.setAccessible(true);
//使用 invoke 反射调用私有方法
//privateMethod 是获取到的私有方法
//testClass 要操作的对象
//后面两个参数传实参
privateMethod.invoke(testClass, "Java Reflect ", 666);
}
反射调用公有方法:
与上面类似,不过如果可能还是直接通过实例调用吧 = _ =
(3)Field类
获取属性的相关信息
int mo = field.getModifiers(); //权限修饰符,包括static、final等
Class<?> type = field.getType(); //类型, 返回全限定名
String name = field.getName(); //属性名
获取属性的值
Object obj = field.get(Object o); //获取类的成员对象,有时需要进行强转
//基本类型提供了诸如getInt、getBoolean等方法
int num = field.getInt(Object o);
...
//所有这些get方法都可能抛出IllegalArgumentException, IllegalAccessException异常
//如果属性是private的,需要在操作之前进行如下设置
field.setAccessible(true);
设置属性的值
field.set(Object obj, Object value); //第一个参数是要操作的对象,第二个参数是新值
//同样针对基本类型,提供了setInt、setBoolean等不需要使用封装类型如Integer的方法
field.setInt(Object obj, int value);
...
针对set方法,如果value是null的话会抛NPE,如果属性是private或者是final的将会抛出IllagalAccessException,设置 field.setAccessible(true); 即可修改。
(4)反射机制的动态代理
实例
interface Animal {
void say(String name, String word);
}
class Cat implements Animal {
@Override
public void say(String name, String word) {
System.out.println("I'm a " + name + " I say " + word);
}
}
//如果想要完成动态代理,首先需要定义一个InvocationHandler接口的子类,以完成代理的具体操作。
class MyInvocationHandler implements InvocationHandler {
private Object obj = null;
public Object bind(Object obj) {
this.obj = obj;
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(this.obj, args);
}
}
public class TestProxy {
public static void main(String[] args) {
MyInvocationHandler demo = new MyInvocationHandler();
Animal animal = (Animal) demo.bind(new Cat());
animal.say("cat", "meow");
}
}
(5)基于反射的工厂模式
对于普通的工厂模式当我们在添加一个子类的时候,就需要对应的修改工厂类。 当我们添加很多的子类的时候,会很麻烦。现在我们利用反射机制实现工厂模式,可以在不修改工厂类的情况下添加任意多个子类。但是
有一点仍然很麻烦,就是需要知道完整的包名和类名,这里可以使用properties配置文件来完成。
interface Fruit {
void eat();
}
class Apple implements Fruit {
@Override
public void eat() {
System.out.println("eat apple");
}
}
class Orange implements Fruit {
@Override
public void eat() {
System.out.println("eat orange");
}
}
class Factory {
public static Fruit getInstance(String className) {
Fruit fruit = null;
try {
fruit = (Fruit) Class.forName(className).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return fruit;
}
}
public class TestFactory {
public static void main(String[] args) {
Fruit f = Factory.getInstance("com.whx.practice.reflect.Apple");
if (f != null) {
f.eat();
}
}
}
四. 使用反射的注意事项
- 遍历循环一个类中的属性还是属性名的时候一定要注意:Android studio2.2之后的Instant Run功能的使用会导致JavaBean对象在编译之后多产生两个属性。可以通过如下方法过滤
for(Field field : fields) {
if (declaredFields[i].isSynthetic()) { //属性是否是Java语言自动生成的
continue;
}
/**忽略serialVersionUID**/
if (declaredFields[i].getName().equals("serialVersionUID")) {
continue;
}
}
-
有用到反射的类不能被混淆
-
使用反射访问Android的API时需要注意因为不同API版本导致的兼容性问题
-
生成内部类的Class对象时,注意类名的写法,有一个【$】符号
Class c = Class.forName("com.whx.test.OuterClass$InnerClass");
五. 反射的优缺点
优点
- 灵活、自由度高:不受类的访问权限限制,可以在运行时对一些类做定制化工作;
缺点
-
性能问题:通过反射访问、修改类的属性和方法时会远慢于直接操作,但性能问题的严重程度取决于在程序中是如何使用反射的。如果使用得很少,不是很频繁,性能将不会是什么问题;
-
安全性问题:反射可以随意访问和修改类的所有状态和行为,破坏了类的封装性,如果不熟悉被反射类的实现原理,随意修改可能导致潜在的逻辑问题;
-
兼容性问题:因为反射会涉及到直接访问类的方法名和实例名,不同版本的API如果有变动,反射时找不到对应的属性和方法时会报异常;
网友评论