【问题标题】:calling setters from a constructor从构造函数调用 setter
【发布时间】:2011-06-21 01:59:14
【问题描述】:

从构造函数(如果有的话)调用 mutator 的优缺点是什么

即:

public MyConstructor(int x) {
  this.x = x;
}

对比:

public MyConstructor(int x) {
  setX(x);
}

public void setX(int x) {
  this.x = x;
}

你有偏好吗? (这不是家庭作业,只是看看我们的编码标准文档,它说在构造函数中设置实例 var 时总是调用 mutators,而我并不总是这样做)

【问题讨论】:

    标签: java oop


    【解决方案1】:

    就我个人而言,大多数情况下我会直接设置变量。

    方法通常期望实例在它们被调用时已经完全形成。特别是,从构造函数调用被覆盖的方法是难以理解的代码和难以发现的错误的秘诀。

    话虽如此,我还是经常尝试使类不可变,在这种情况下,不仅没有设置器,而且您必须从构造函数(或变量初始化器)设置最终变量无论如何:)

    在属性有逻辑的地方,setter 逻辑通常是验证,有时会更改传播到观察者。我通常希望在方法开始时显式检查构造函数参数,并且您不会希望在实例完全创建之前发生任何更改传播。

    【讨论】:

    • 这是一个有趣的观点。但是,我认为如果您使用 AspectJ 或某些此类切面功能,这些会有所帮助。此外,我还看到它也被用于 TDD(允许一些有趣的模拟实现)。
    • 有一些事情需要考虑。很多时候,类似 bean 的类的参数 c-tor 为零(以及一些方便的参数丰富的 c-tor),因此 setter 是配置实例的一种“官方”方式。
    【解决方案2】:

    我遵循关于构造函数的两条规则来最小化问题,这就是我不使用 mutator 方法的原因:

    (非最终类的)构造函数应该只调用最终或私有方法。如果您决定忽略此规则并让构造函数调用非最终/非私有方法,那么:

    • 这些方法和它们可能调用的任何方法都必须小心,不要假设实例已完全初始化,并且
    • 覆盖这些方法的子类(甚至可能不知道超类构造函数正在调用这些方法的子类)不得假定子类构造函数和超类的构造函数已完全执行。带有“邪恶”构造函数的超类在继承层次结构中越深入,这个问题就越严重。

    所有这些额外的认知包袱值得吗?您可以允许只为实例变量赋值的简单突变器例外,因为没有什么好处,即使这似乎也不值得.

    [[ @Jon Skeet 在他的回答中提到了这一点:“......特别是,从构造函数调用重写的方法是难以理解的代码和难以发现的错误的秘诀。”但我认为这个问题的后果并没有得到足够的强调。 ]]

    在实例完全初始化之前,构造函数应该小心泄漏this虽然前面的规则是关于类和子类中的方法访问 ivars,但您必须在this 完全初始化之前,还要小心(甚至是最终/私有)方法将this 传递给其他类和实用程序函数。构造函数调用的非私有的、可覆盖的方法越多,泄漏this 的风险就越大。


    关于构造函数调用非最终、非私有方法的一些参考:

    https://www.securecoding.cert.org/confluence/display/java/MET05-J.+Ensure+that+constructors+do+not+call+overridable+methods

    http://www.javaworld.com/article/2074669/core-java/java-netbeans--overridable-method-call-in-constructor.html

    http://www.javaspecialists.eu/archive/Issue210.html

    【讨论】:

    • 您在答案中发布的链接已失效。
    【解决方案3】:

    在构造函数中调用任何 publicstaticnon-final 方法取决于您,但最佳做法是永远不要在构造函数中调用此类方法,因为可以在子类中重写此方法,并且实际上只会调用此方法的重写版本(如果您使用多态行为)。

    例如:

    public class Foo {
    
        public Foo() {
            doSmth(); // If you use polymorphic behavior this method will never be invoked
        }
    
        public void doSmth() {
            System.out.println("doSmth in super class");
        }
    
        public static void main(String[] args) {
            new Bar(200);
        }
    }
    
    class Bar extends Foo {
    
        private int y;;
    
        public Bar(int y) {
            this.y = y;
        }
    
        @Override
        public void doSmth() { // This version will be invoked even before Barr object initialized
            System.out.println(y);
        }
    
    }
    

    它将打印 0。

    更多细节请阅读Bruce Eckel "Thinking in Java"“多态性”一章

    【讨论】:

      【解决方案4】:

      出于几个原因,我的偏好是直接在构造函数中设置它们。首先,this.x = x; 之类的内容与调用执行相同操作的单独方法一样清楚,甚至更清楚。其次,除非它被标记为 final,否则该方法可能已被覆盖,并且从构造函数调用可能被覆盖的方法在我的书中是一个很大的禁忌。第三,大多数方法通常假设对象在执行时已经完成,而不是在构建的一半。虽然这在这种简单的情况下不会导致任何问题,但在更复杂的情况下,它可能会导致需要很长时间才能发现的严重微妙的错误。

      在任何地方都使用 setter / getter 的主要论点是,这意味着您可以通过在 3 个位置更改其名称、其定义、getter / setter 方法来重命名该字段,并且所有这些都应该编译并且没问题。在我看来,这个论点现在是无效的,因为任何体面的现代 IDE 都会使用简单的键盘快捷键重命名所有出现的此类字段。

      【讨论】:

        【解决方案5】:

        除非你的 setter 做的事情比this.x = x 更复杂,否则我会直接设置变量。该参数直接映射到您的实例变量,对我来说它更能显示意图。

        【讨论】:

          【解决方案6】:

          我很少这样做,因此我从来没有遇到过问题。它也可能产生意想不到的后果,特别是如果您的 setter 在子类中被覆盖,或者如果您的 setter 触发了在初始化期间可能不合适的其他方法调用。

          【讨论】:

            【解决方案7】:

            我们的编码标准要求使用访问器(也可能是私有的)只有一个原因。 如果您想快速找出更改字段的代码,只需在 Eclipse 中调出 setter 的调用层次结构!

            这对于已达到 200 万行代码的代码库来说是一个巨大的节省时间,并且还简化了重构。

            【讨论】:

              【解决方案8】:

              这取决于你如何对待二传手。如果您认为可以从该类派生,您可以允许这样的调用,以便您可以覆盖其他地方的行为。否则,您可以将其设置为它。

              当然,允许显式调用 setter 还会提供在设置期间注入其他行为的方法(例如在需要时应用安全性)。

              【讨论】:

                【解决方案9】:

                我也不介意,我也没有很强的偏好[并且不遵守一些严格的标准],请自行判断。但是,如果您使用 setter,则任何覆盖该类的人都必须意识到该对象可能未完全创建。话虽如此,请确保 setter 中没有额外的代码可以发布 this 参考。

                Setter 可用于标准范围检查等,因此不需要额外的代码来验证输入。再次使用 setter 是稍微脏一点的方法。

                使用该字段具有明显的好处(例如确保任何子类都无法恶意更改行为)并且通常是首选,尤其是。在图书馆里。

                实际上 c-tor 是创建对象的 3 种可能方法之一(序列化 + 克隆是另外 2 个),可能需要在 c-tor 之外处理一些瞬态,因此您仍然需要考虑字段与二传手。 (编辑,还有另一种方式:不安全,但这就是序列化使用的)

                【讨论】:

                  猜你喜欢
                  • 2013-07-30
                  • 2011-07-10
                  • 1970-01-01
                  • 2011-03-24
                  • 2017-02-08
                  • 1970-01-01
                  • 1970-01-01
                  • 2010-10-05
                  相关资源
                  最近更新 更多