9

最近我遇到了反射 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不是改变了数据封装的目的吗?为什么我们必须使用反射 API?我在一些网站上读到它可以用于测试目的,但根据我的说法,模块已经过测试,并且可以使用 JUnit 测试用例轻松完成。那么任何人都可以解释为什么我们有这样的黑客吗?

4

4 回答 4

12

反射 API 不是改变了数据封装的目的吗?

是和不是。

  • 是的,反射 API 的某些用途可能会破坏数据封装。
  • 不,并非所有反射 API 的使用都会破坏数据封装。事实上,聪明的程序员只有在有充分理由这样做时才通过反射 API 打破封装。
  • 不,反射 API 不会改变数据封装的目的。数据封装的目的保持不变……即使有人故意破坏它。

为什么我们必须使用反射 API?

反射的许多用途不会破坏封装;例如,使用反射来找出一个类有哪些超类型、它有哪些注解、它有哪些成员、调用可访问的方法和构造函数、读取和更新可访问的字段等等。

并且在某些情况下(在不同程度上)使用封装破坏反射类型是可以接受的:

  • 您可能需要查看封装类型(例如访问/修改私有字段)作为实现某些单元测试的最简单方法(或唯一方法)。

  • 某些形式的依赖注入(又名 IoC)、序列化和持久性需要访问和/或更新私有字段。

  • 偶尔,您需要打破封装以解决某些类中无法修复的错误。

我在一些网站上读到它可以用于测试目的,但根据我的说法,模块已经过测试,并且可以使用 JUnit 测试用例轻松完成。那么任何人都可以解释为什么我们有这样的黑客吗?

这取决于您的班级的设计。一个被设计为可测试的类要么是可测试的,而不需要访问“私有”状态,要么会暴露该状态(例如protectedgetter)以允许测试。如果类不这样做,那么 JUnit 测试可能需要使用反射来查看抽象内部。

这是不可取的(IMO),但是如果您正在为某人编写的类编写单元测试,并且您无法“调整” API 以提高可测试性,那么您可能必须在使用反射或根本不测试之间做出选择.


底线是数据封装是我们努力实现的理想(在 Java 中),但在某些情况下,实际正确的做法是破坏它或忽略它。

请注意,并非所有 OO 语言都像 Java 那样支持强数据封装。例如,Python 和 Javascript 都是无可争辩的 OO 语言,但它们都使一个类可以轻松访问和修改另一个类的对象的状态……甚至改变其他类的行为。强大的数据抽象并不是每个人对面向对象含义的看法的核心。

于 2013-05-19T13:34:38.160 回答
0

反射 API 不是改变了数据封装的目的吗?

不要太拘泥于规则。有时打破它们很有用。

反射非常有用。以我的记录查找更改的代码为例,以便我可以将它们记录到数据库中:

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,我将不得不为每个类编写一个单独的方法,并在每次添加新字段时进行修改。通过反射,我可以只编写一种方法来处理我的所有类。我在这里写下我记录更改的方法

于 2013-05-19T14:43:26.403 回答
0

你不能混合Encapsulation,Polymorphism与反射。

反射具有完全不同的目的。通过反射,您可以动态创建类型并执行它。您可以在运行时动态访问类的成员。


例如,最近我使用反射Plugins在我的应用程序中运行。即我从特定目录加载 dll,然后通过将其转换为共享接口通过反射创建类的对象。

于 2013-05-19T13:32:12.017 回答
0

是的,它确实违反了面向对象的概念。但是,它不会破坏任何 java 安全模型。如有必要,它可以由 java 安全管理器控制。Java 反射本身就是一个有用的东西。它用于注释和 IOC,它们是非常有用的概念,能够在运行时处理类。

于 2013-05-19T13:26:10.150 回答