最近我遇到了Reflection API,令我惊讶的是我们可以访问甚至更改私有变量。我尝试了以下代码
import java.lang.reflect.Field;
public class SomeClass{
private String name = "John";
}
public class Test{
public static void main(String args[]) throws Exception {
SomeClass myClass = new SomeClass();
Field fs = myClass.getClass().getDeclaredField("name");
fs.setAccessible(true);
System.out.println("Variable is " + fs.getName() + " and value is "
+ fs.get(myClass));
fs.set(myClass, "Sam");
System.out.println("Variable is " + fs.getName() + " and value is "
+ fs.get(myClass));
}
}
我得到了以下输出。
Variable is name and value is John
Variable is name and value is Sam
我们说Java是一种面向对象的语言,它的主要功能是数据封装,继承,多态......等不是反射API改变了数据封装的目的吗?为什么我们必须使用Reflection API?我在一些网站上读到它可以用于测试目的,但据我说,模块已经过测试,可以使用JUnit测试用例轻松完成。所以任何人都可以解释为什么我们有这样的黑客攻击?
答案 0 :(得分:9)
反射API是不是改变了数据封装的目的?
是和否。
为什么我们必须使用Reflection API?
很多使用反射不要打破封装;例如使用反射来找出类的超类型,它有什么注释,它有什么成员,调用可访问的方法和构造函数,读取和更新可访问的字段等等。
有些情况可以接受(在不同程度上)使用封装破坏各种反射:
您可能需要查看封装类型(例如访问/修改私有字段)作为实现某些单元测试的最简单方法(或唯一方法)。
某些形式的依赖注入(也称为IoC),序列化和持久性需要访问和/或更新私有字段。
偶尔,您需要打破封装以解决某些无法修复的类中的错误。
我在一些网站上读到它可以用于测试目的,但据我说,模块已经过测试,可以使用JUnit测试用例轻松完成。所以任何人都可以解释为什么我们有这样的黑客攻击?
这取决于你班级的设计。设计为可测试的类将是可测试的,无需访问“私有”状态,或者将公开该状态(例如protected
getters)以允许测试。如果类没有这样做,那么JUnit测试可能需要使用反射来查看抽象内部。
这是不可取的(IMO),但如果您正在为某人编写的类编写单元测试,并且您无法“调整”API以提高可测试性,那么您可能必须选择是否使用反射完全测试。
最重要的是,数据封装是我们努力实现的理想(在Java中),但有些情况下,实际上正确的做法是打破它或忽略它。
请注意,并非所有OO语言都支持像Java那样强大的数据封装。例如,Python和Javascript都是无可争议的OO语言,但它们都使一个类可以轻松访问和修改另一个类的对象状态......甚至可以更改其他类的行为。强大的数据抽象并不是每个人对面向对象意味着什么的看法的核心。
答案 1 :(得分:0)
是的,它确实违反了OO概念。但是它不会破坏任何Java安全模型。如有必要,它可以由java安全管理器控制。 Java反射本身是一件有用的事情。它在Annotations和IOC中使用,它们是非常有用的概念,能够在运行时处理类。
答案 2 :(得分:0)
您不能将Encapsulation
,Polymorphism
与反射混合。
Reflection的目的完全不同。使用反射,您可以动态创建类型并执行它。您可以在运行时动态访问类的成员。
例如,最近我在我的应用程序中使用了反射运行Plugins
。我从特定目录加载了dll,然后通过反射将其转换为共享接口来创建类的对象。
答案 3 :(得分:0)
反射API是不是改变了数据的目的 封装
不要太依赖规则。有时候打破它们是有用的。
Reflextion非常有用。以我的代码为例,记录查找更改,以便我可以将它们记录到数据库中:
public static void log(String userID, Object bOld, Object bNew)
throws IntrospectionException, IllegalAccessException, InvocationTargetException {
String res = "";//String to hold the change record
boolean changed = false;
try {
if(bOld != null){ //if this is an update
BeanInfo beanInfo = Introspector.getBeanInfo(bOld.getClass());
res = bOld.getClass().getSimpleName() + " - ";
//loop and compare old values with new values and add them to our string if they are changed
for (PropertyDescriptor prop : beanInfo.getPropertyDescriptors()) {
Method getter = prop.getReadMethod();
Object vOld = getter.invoke(bOld); //old value
Object vNew = getter.invoke(bNew); //new value
if (vOld == vNew || (vOld != null && vOld.equals(vNew))) {
continue;
}
changed = true;
res = res + "(" + prop.getName() + ", " + vOld + ", " + vNew + ")";
}
}
这样做更容易。如果我使用getter,我将不得不为每个类编写一个单独的方法,并在每次添加新字段时进行修改。通过反射,我可以编写一个方法来处理我的所有类。我写了关于记录更改的方法here