在构造函数中重写的方法调用有什么问题?
我有一个Wicket页面类,根据抽象方法的结果设置页面标题。
public abstract class BasicPage extends WebPage { public BasicPage() { add(new Label("title", getTitle())); } protected abstract String getTitle(); }
NetBeans用“构造函数中的Overridable方法调用”消息警告我,但是它会出现什么问题? 我能想象的唯一select是将其他抽象方法的结果传递给子类中的超级构造函数。 但是这可能很难用许多参数来阅读。
从构造函数调用覆盖的方法
简而言之,这是错误的,因为它不必要地开放了许多错误的可能性。 当@Override
被调用时,对象的状态可能不一致和/或不完整。
有效的Java第二版,第17项:devise和inheritance的文件,否则禁止它的引用:
还有一些类必须遵守允许inheritance的限制。 构造函数不能直接或间接地调用可重写的方法 。 如果违反这个规则,将导致程序失败。 超类构造函数在子类构造函数之前运行,所以在子类构造函数运行之前,子类中的重载方法将被调用。 如果覆盖方法依赖于子类构造函数执行的任何初始化,则此方法将不会按预期运行。
下面是一个例子来说明:
public class ConstructorCallsOverride { public static void main(String[] args) { abstract class Base { Base() { overrideMe(); } abstract void overrideMe(); } class Child extends Base { final int x; Child(int x) { this.x = x; } @Override void overrideMe() { System.out.println(x); } } new Child(42); // prints "0" } }
这里,当Base
构造函数调用overrideMe
, Child
尚未初始化final int x
,并且方法得到错误的值。 这几乎肯定会导致错误和错误。
相关问题
- 从父类构造函数调用重写的方法
- 基类构造函数调用Java中的重写方法时的派生类对象的状态
- 在抽象类的构造函数中使用抽象init()函数
也可以看看
- FindBugs – 从超类的构造函数中调用的字段方法的未初始化读取
多参数的对象构造
具有许多参数的构造器可能导致可读性较差,并且存在更好的替代scheme。
下面是Effective Java 2nd Edition的一句话,第2项:当面对许多构造函数参数时,请考虑构build器模式 :
传统上,程序员使用伸缩构造函数模式,在这种模式中,您只提供了一个只有必需参数的构造函数,另一个只有一个可选参数,第三个有两个可选参数,等等。
伸缩的构造函数模式基本上是这样的:
public class Telescope { final String name; final int levels; final boolean isAdjustable; public Telescope(String name) { this(name, 5); } public Telescope(String name, int levels) { this(name, levels, false); } public Telescope(String name, int levels, boolean isAdjustable) { this.name = name; this.levels = levels; this.isAdjustable = isAdjustable; } }
现在,您可以执行以下任何操作:
new Telescope("X/1999"); new Telescope("X/1999", 13); new Telescope("X/1999", 13, true);
但是,您目前不能设置name
并且是isAdjustable
,并且保持默认levels
。 你可以提供更多的构造函数重载,但显然随着参数数量的增加,数字会爆炸,甚至可能有多个boolean
和int
参数,这会使事情变得糟糕。
正如你所看到的,这不是一个愉快的写法,甚至不太好用(这里的“真实”是什么意思?什么是13?)。
布洛赫build议使用一个build造者模式,这将允许你写这样的东西,而不是:
Telescope telly = new Telescope.Builder("X/1999").setAdjustable(true).build();
请注意,现在这些参数被命名,您可以按照您想要的任何顺序来设置它们,并且可以跳过那些您想要保留的默认值。 这肯定比伸缩式构造函数更好,特别是当有大量参数属于相同types时。
也可以看看
- 维基百科/生成器模式
- 有效的Java第2版,第2项:当面对许多构造函数参数时考虑构build器模式 ( 摘录在线 )
相关问题
- 你什么时候使用Builder模式?
- 这是一个众所周知的devise模式? 它叫什么名字?
这是一个有助于理解的例子:
public class Main { static abstract class A { abstract void foo(); A() { System.out.println("Constructing A"); foo(); } } static class C extends A { C() { System.out.println("Constructing C"); } void foo() { System.out.println("Using C"); } } public static void main(String[] args) { C c = new C(); } }
如果你运行这个代码,你会得到如下输出:
Constructing A Using C Constructing C
你看? 在C的构造函数运行之前, foo()
使用了C语言。 如果foo()
需要C具有一个已定义的状态(即构造函数已经完成 ),那么它将在C中遇到一个未定义的状态,事情可能会中断。 而且由于你不知道被覆盖的foo()
是什么,你会得到一个警告。
在构造函数中调用一个可重写的方法允许子类破坏代码,所以你不能保证它的工作。 这就是为什么你得到一个警告。
在你的例子中,如果一个子类重写getTitle()
并返回null,会发生什么?
要“修复”这个,你可以使用工厂方法而不是构造函数,这是对象实例的常见模式。
如果在构造函数中调用子类覆盖的方法,则意味着如果将逻辑上的初始化分配到构造函数和方法之间,则不太可能引用不存在的variables。
看看这个示例链接http://www.javapractices.com/topic/TopicAction.do?Id=215
下面是一个例子,它揭示了在超级构造函数中调用可重写方法时可能发生的逻辑问题 。
class A { protected int minWeeklySalary; protected int maxWeeklySalary; protected static final int MIN = 1000; protected static final int MAX = 2000; public A() { setSalaryRange(); } protected void setSalaryRange() { throw new RuntimeException("not implemented"); } public void pr() { System.out.println("minWeeklySalary: " + minWeeklySalary); System.out.println("maxWeeklySalary: " + maxWeeklySalary); } } class B extends A { private int factor = 1; public B(int _factor) { this.factor = _factor; } @Override protected void setSalaryRange() { this.minWeeklySalary = MIN * this.factor; this.maxWeeklySalary = MAX * this.factor; } } public static void main(String[] args) { B b = new B(2); b.pr(); }
结果实际上是:
minWeeklySalary:0
maxWeeklySalary:0
这是因为B类的构造函数首先调用类A的构造函数,B中的可重写方法被执行。 但是在这个方法里面,我们使用了尚未初始化的实例variables因子 (因为A的构造函数尚未完成),因此因子是0而不是1,绝对不是2(程序员可能会认为是)。 想象一下,如果计算逻辑扭曲了十倍,跟踪错误将会是多么困难。
我希望能帮助别人。
在Wicket的具体情况中:这就是为什么我要求Wicket开发者在构build组件的生命周期中为显式的两阶段组件初始化过程添加支持的原因
- 施工 – 通过构造函数
- 初始化 – 通过onInitilize(虚拟方法工作后build设!)
有一个相当积极的争论是否有必要或不(这是完全有必要的恕我直言,因为这个链接演示http://apache-wicket.1842946.n4.nabble.com/VOTE-WICKET-3218-Component-onInitialize- is-broken-for-Pages-td3341090i20.html )
好消息是,Wicket的优秀开发者最终引入了两个阶段的初始化(使得最令人敬畏的Java UI框架更加棒!),所以使用Wicket,你可以在onInitialize方法中完成所有的后期构build初始化框架会自动覆盖它 – 在组件的生命周期的这一点上,它的构造函数已经完成了它的工作,所以虚拟方法按照预期工作。
我想Wicket最好在onInitialize()
调用add
方法(参见组件生命周期 ):
public abstract class BasicPage extends WebPage { public BasicPage() { } @Override public void onInitialize() { add(new Label("title", getTitle())); } protected abstract String getTitle(); }