从构造函数中调用setter

时间:2011-02-04 01:08:11

标签: java oop

从构造函数(如果有的话)调用mutator的pro和con是什么

即:

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

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

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

你有偏好吗? (这不是功课,只是查看我们的编码标准文档,它说在构造函数中设置实例var时始终会调用mutator而我并不总是这样做)

9 个答案:

答案 0 :(得分:76)

就个人而言,我会直接在大多数案例中设置变量。

方法通常期望实例在被调用时完全形成。特别是,从构造函数中调用重写的方法是一个难以理解的代码和难以发现的错误的秘诀。

话虽如此,我经常尝试使类无法变换,在这种情况下,不仅没有setter,而且从构造函数(或变量初始化程序)设置最终变量无论如何:)

在属性具有逻辑的情况下,setter逻辑通常是验证,有时会改变传播给观察者。我通常希望在方法开始时显式检查构造函数参数,并且在完全创建实例之前,你不会想要任何更改传播。

答案 1 :(得分:27)

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

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

  • 他们可能会调用的那些方法和方法必须小心,不要假设实例已完全初始化,并且
  • 重写那些方法的子类(甚至可能不知道超类构造函数调用这些方法的子类)必须不要假设子类构造函数和超类的构造函数已经完全执行。对于具有“邪恶”构造函数的超类,继承层次结构越深,这个问题就越严重。

是否值得拥有额外的认知包袱?您可以允许简单的变异器例外,它只为实例变量赋值,因为它没什么好处,即使看起来不值得

[[@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

答案 2 :(得分:2)

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

答案 3 :(得分:1)

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

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

答案 4 :(得分:1)

除非你的setter做的事情比this.x = x更复杂,否则我只是直接设置变量。该参数直接映射到您的实例变量,对我而言,它更具有意向性。

答案 5 :(得分:1)

我的偏好是在构造函数中直接设置它们有几个原因。首先,像this.x = x;这样的东西就像调用一个做同样事情的单独方法一样清晰。其次,该方法可能已经被覆盖,除非它被标记为final,并且在我的书中调用构造函数中可能被覆盖的方法是一个很大的禁忌。第三,大多数方法通常假设对象在执行时已经完成,而不是构造的一半。虽然这不应该在这个简单的情况下引起任何问题,但在更复杂的情况下,它可能会导致严重的细微错误,需要很长时间才能找到。

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

答案 6 :(得分:1)

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

这在代码库中节省了大量时间,已经达到200万行代码并且还可以简化重构。

答案 7 :(得分:1)

在构造函数中调用任何 public static non-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。

对于mo详细信息,请阅读Bruce Eckel "Thinking in Java"章节“Polymorphism”

答案 8 :(得分:0)

我也不介意,我没有强烈的pref [并且不遵守一些严格的标准],只是用你的判断。但是,如果你去设置任何覆盖类的人必须意识到该对象可能无法完全创建。话虽如此,确保setter中没有额外的代码可以发布this引用。

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

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

实际上c-tor是创建对象的3种可能方式之一(序列化+克隆是另外2种),某些瞬态可能需要在c-tor之外处理,所以你仍然需要考虑一下字段vs setters。 (编辑,还有另一半:不安全,但这是序列化使用的)