【问题标题】:Dosen't Reflection API break the very purpose of Data encapsulation?反射 API 不会破坏数据封装的目的吗?
【发布时间】:2013-05-14 03:43:55
【问题描述】:

最近我遇到了反射 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 测试用例轻松完成。那么任何人都可以解释为什么我们有这样的黑客吗?

【问题讨论】:

  • 反射 API 的实用工具之一是 IOC 模式,允许框架调用在其编译时(框架的)未知的方法。其他用途是检查类以执行转换(例如,直接从 bean 生成 SOAP 或 JSON 响应)
  • 在您的代码中,您不必使用fs.setAccessible(true);,因为您知道在同一类的其他方法中更改私有字段(可以访问每个字段,甚至是私有字段)。我稍微更改了您的示例,以表明通过反射您可以访问通常无法访问的私有字段。

标签: java reflection private encapsulation


【解决方案1】:

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

是和不是。

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

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

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

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

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

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

  • 在极少数情况下,您需要打破封装以解决某些类中无法修复的错误。

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

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

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


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

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

      【解决方案3】:

      您不能将Encapsulation,Polymorphism 与反射混合使用。

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


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

      【讨论】:

        【解决方案4】:

        反射 API 不是在改变 Data 的用途吗 封装?

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

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

        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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-05-10
          • 1970-01-01
          • 1970-01-01
          • 2013-04-16
          • 2022-11-03
          • 2012-11-28
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多