构造函数中的可重写方法调用有什么问题?
我有一个 Wicket 页面类,它根据抽象方法的结果设置页面标题。
public abstract class BasicPage extends WebPage {
public BasicPage() {
add(new Label("title", getTitle()));
}
protected abstract String getTitle();
}
NetBeans 警告我消息“构造函数中可重写方法调用”,但它应该有什么问题呢?我能想到的唯一选择是将抽象方法的结果传递给子类中的超级构造函数。但如果有很多参数,这可能很难阅读。
I have a Wicket page class that sets the page title depending on the result of an abstract method.
public abstract class BasicPage extends WebPage {
public BasicPage() {
add(new Label("title", getTitle()));
}
protected abstract String getTitle();
}
NetBeans warns me with the message "Overridable method call in constructor", but what should be wrong with it? The only alternative I can imagine is to pass the results of otherwise abstract methods to the super constructor in subclasses. But that could be hard to read with many parameters.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
关于从构造函数调用可重写方法
简单地说,这是错误的,因为它不必要地为许多错误带来了可能性。当调用@Override时,对象的状态可能不一致和/或不完整。
引用Effective Java 2nd Edition,第 17 条:继承的设计和文档,否则禁止:
下面举个例子来说明:
这里,当
Base
构造函数调用overrideMe
时,Child
还没有完成对final int x
的初始化>,并且该方法得到错误的值。这几乎肯定会导致错误和错误。相关问题
另请参阅
关于具有许多参数的对象构造
具有许多参数的构造函数可能会导致可读性较差,并且存在更好的替代方案。
下面引用了《Effective Java 第二版》第 2 条:面对许多构造函数参数时考虑构建器模式:
......伸缩构造函数模式本质上是这样的:
现在您可以执行以下任何操作:
但是,当前您不能仅设置
name
和isAdjustable
,然后离开默认的级别
。您可以提供更多的构造函数重载,但显然,随着参数数量的增加,数量会爆炸,您甚至可能有多个 boolean 和 int 参数,这确实会导致把事情搞乱。正如您所看到的,这不是一个写起来令人愉快的模式,使用起来更不愉快(“true”在这里意味着什么?13 是什么?)。
Bloch 建议使用构建器模式,它允许您编写如下所示的内容:
请注意,现在参数已命名,您可以按照您想要的任何顺序设置它们,并且可以跳过要保留默认值的参数价值观。这肯定比伸缩构造函数好得多,特别是当存在大量属于许多相同类型的参数时。
另请参阅
相关问题
On invoking overridable method from constructors
Simply put, this is wrong because it unnecessarily opens up possibilities to MANY bugs. When the
@Override
is invoked, the state of the object may be inconsistent and/or incomplete.A quote from Effective Java 2nd Edition, Item 17: Design and document for inheritance, or else prohibit it:
Here's an example to illustrate:
Here, when
Base
constructor callsoverrideMe
,Child
has not finished initializing thefinal int x
, and the method gets the wrong value. This will almost certainly lead to bugs and errors.Related questions
See also
On object construction with many parameters
Constructors with many parameters can lead to poor readability, and better alternatives exist.
Here's a quote from Effective Java 2nd Edition, Item 2: Consider a builder pattern when faced with many constructor parameters:
The telescoping constructor pattern is essentially something like this:
And now you can do any of the following:
You can't, however, currently set only the
name
andisAdjustable
, and leavinglevels
at default. You can provide more constructor overloads, but obviously the number would explode as the number of parameters grow, and you may even have multipleboolean
andint
arguments, which would really make a mess out of things.As you can see, this isn't a pleasant pattern to write, and even less pleasant to use (What does "true" mean here? What's 13?).
Bloch recommends using a builder pattern, which would allow you to write something like this instead:
Note that now the parameters are named, and you can set them in any order you want, and you can skip the ones that you want to keep at default values. This is certainly much better than telescoping constructors, especially when there's a huge number of parameters that belong to many of the same types.
See also
Related questions
下面是一个有助于理解这一点的示例:
如果运行此代码,您将得到以下输出:
看到了吗?
foo()
在 C 的构造函数运行之前使用 C。如果foo()
要求C具有已定义的状态(即构造函数已完成),那么它将在C中遇到未定义的状态,并且事情可能会中断。由于您无法知道 A 中被覆盖的 foo() 期望什么,因此您会收到警告。Here's an example which helps to understand this:
If you run this code, you get the following output:
You see?
foo()
makes use of C before C's constructor has been run. Iffoo()
requires C to have a defined state (i.e. the constructor has finished), then it will encounter an undefined state in C and things might break. And since you can't know in A what the overwrittenfoo()
expects, you get a warning.在构造函数中调用可重写的方法允许子类破坏代码,因此您不能保证它不再有效。这就是您收到警告的原因。
在您的示例中,如果子类重写 getTitle() 并返回 null 会发生什么?
要“修复”此问题,您可以使用 工厂方法 而不是构造函数,这是一种常见模式对象实例化。
Invoking an overridable method in the constructor allows subclasses to subvert the code, so you can't guarantee that it works anymore. That's why you get a warning.
In your example, what happens if a subclass overrides
getTitle()
and returns null ?To "fix" this, you can use a factory method instead of a constructor, it's a common pattern of objects instanciation.
下面的示例揭示了在超级构造函数中调用可重写方法时可能发生的逻辑问题。
结果实际上是:
minWeeklySalary: 0
maxWeeklySalary: 0
这是因为类 B 的构造函数首先调用类 A 的构造函数,其中执行 B 中的可重写方法。但是在方法内部我们使用了实例变量factor,它尚未初始化(因为A的构造函数尚未完成),因此factor是0而不是1并且绝对不是 2(程序员可能认为会是这样)。想象一下,如果计算逻辑扭曲十倍,那么追踪错误会有多困难。
我希望这会对某人有所帮助。
Here is an example that reveals the logical problems that can occur when calling an overridable method in the super constructor.
The result would actually be:
minWeeklySalary: 0
maxWeeklySalary: 0
This is because the constructor of class B first calls the constructor of class A, where the overridable method inside B gets executed. But inside the method we are using the instance variable factor which has not yet been initialized (because the constructor of A has not yet finished), thus factor is 0 and not 1 and definitely not 2 (the thing that the programmer might think it will be). Imagine how hard would be to track an error if the calculation logic was ten times more twisted.
I hope that would help someone.
如果您在构造函数中调用子类重写的方法,则意味着如果您在构造函数和方法之间逻辑划分初始化,则不太可能引用尚不存在的变量。
查看此示例链接 http://www.javapractices.com/topic /TopicAction.do?Id=215
If you call methods in your constructor that subclasses override, it means you are less likely to be referencing variables that don’t exist yet if you divide your initialization logically between the constructor and the method.
Have a look on this sample link http://www.javapractices.com/topic/TopicAction.do?Id=215
我当然同意在某些情况下最好不要从构造函数中调用某些方法。
让它们私有消除了所有疑虑:“你不能通过” 。
但是,如果您确实想让事情保持开放怎么办?
正如我试图解释的那样,不仅仅是访问修饰符才是真正的问题此处。说实话,
private
是一个明显的阻碍,而protected
通常仍然会允许(有害的)解决方法。更一般的建议:
不要直接从你的构造函数中这样做。这包括从构造函数调用的私有/受保护函数执行任何这些操作。
从构造函数中调用
start()
方法肯定是一个危险信号。相反,您应该提供 public
init()
、start()
或connect()
方法。并将责任留给消费者。简单地说,您想要将“准备”时刻与“点火”时刻分开。
PS:考虑实现 Closeable 与之相关的接口。
I certainly agree that there are cases where it is better not to call some methods from a constructor.
Making them private takes away all doubt: "You shall not pass".
However, what if you DO want to keep things open.
It's not just the access modifier that is the real problem, as I tried to explain here. To be completely honest,
private
is a clear showstopper whereprotected
usually will still allow a (harmful) workaround.A more general advice:
Don't do so (in)directly from your constructor. That includes doing any of these actions from a private/protected function which is called by the constructor.
Calling an
start()
method from your constructor could certainly be a red flag.Instead, you should provide a public
init()
,start()
orconnect()
method. And leave the responsibility to the consumer.Simply put, you want to separate the moment of "preparation" from the "ignition".
PS: consider implementing the Closeable interface along with it.
在Wicket的具体情况下:这就是我问Wicket的原因
开发人员在构建组件的框架生命周期中添加对显式两阶段组件初始化过程的支持,即
对于是否有必要或是否有必要进行了相当激烈的争论不是(恕我直言,这完全是必要的),因为此链接演示了 http://apache-wicket.1842946.n4.nabble.com/VOTE-WICKET-3218-Component-onInitialize-is-broken-for-Pages-td3341090i20.html< /a>)
好消息是,Wicket 的优秀开发人员最终引入了两阶段初始化(以使最棒的 Java UI 框架变得更加棒!),因此使用 Wicket,您可以在 onInitialize 方法中完成所有构建后初始化如果您覆盖它,框架会自动调用它 - 此时在组件生命周期中,其构造函数已完成其工作,因此虚拟方法按预期工作。
In the specific case of Wicket: This is the very reason why I asked the Wicket
devs to add support for an explicit two phase component initialization process in the framework's lifecycle of constructing a component i.e.
There was quite an active debate about whether it was necessary or not (it fully is necessary IMHO) as this link demonstrates http://apache-wicket.1842946.n4.nabble.com/VOTE-WICKET-3218-Component-onInitialize-is-broken-for-Pages-td3341090i20.html)
The good news is that the excellent devs at Wicket did end up introducing two phase initialization (to make the most aweseome Java UI framework even more awesome!) so with Wicket you can do all your post construction initialization in the onInitialize method that is called by the framework automatically if you override it - at this point in the lifecycle of your component its constructor has completed its work so virtual methods work as expected.
我想对于 Wicket 来说,最好在
onInitialize()
中调用add
方法(参见 组件生命周期) :I guess for Wicket it's better to call
add
method in theonInitialize()
(see components lifecycle) :可重写的方法应包含在构造函数上调用它们的文档。继承者应该考虑到这一点,并且只使用重写来更改超类上分配的值的验证,并且始终调用超类方法来更改实际值(如果它是setter)。
我确实在构造函数上使用了很多可重写的方法,并且没有导致程序失败。这是因为我确实遵循这个原则 - 覆盖仅允许检查值,或者可能用子类上不同的合适值替换它。通常,重写的方法是不使用子类的内部状态的值检查器,但允许更改允许的值。
The overridable methods should include documentation that they are called on constructor. The inheritors should take this into account and only use overriding to change the validation of the values assigned on the super class, and always call superclass method to alter the actual value, if it is setter.
I do have used overridable methods on constructors a lot, and no program failure has resulted. This is due the fact I do follow this principle - overriding only allows checking the value, or possibly replacing it with different suitable value on sub classes. Usually the overridden method is checker of a value without using internal state of the subclass, but allows changing the allowed values.