在构造函数中调用可覆盖的方法,例如 Swing 的 add()
Calling overridable methods like Swing's add() in constructor
我知道从构造函数中调用可重写的方法是个坏主意。但我也看到它在 Swing 中无处不在,其中像 add(new JLabel("Something"));
这样的代码一直出现在构造函数中。
以 NetBeans IDE 为例。它对构造函数中的可覆盖调用非常挑剔。然而,当它生成 Swing 代码时,它会将所有这些 add()
方法调用放入一个 initializeComponents()
方法中……然后从构造函数中调用该方法!隐藏问题和禁用警告的好方法(NetBeans 没有“调用可覆盖方法的私有方法是从构造函数调用的”警告)。但不是真正解决问题的方法。
这是怎么回事?我已经这样做了很多年,但对此总是有一种不安的感觉。有没有更好的初始化 Swing 容器的方法,除了制作一个额外的 init()
方法(并且不要忘记每次都调用它,这有点无聊)?
例子
这是一个极其人为的例子,说明事情是如何出错的:
public class MyBasePanel extends JPanel {
public MyBasePanel() {
initializeComponents();
}
private void initializeComponents() {
// layout setup omitted
// overridable call
add(new JLabel("My label"), BorderLayout.CENTER);
}
}
public class MyDerivedPanel extends MyBasePanel {
private final List<JLabel> addedLabels = new ArrayList<>();
@Override
public void add(Component comp, Object constraints) {
super.add(comp);
if (comp instanceof JLabel) {
JLabel label = (JLabel) comp;
addedLabels.add(label); // NPE here
}
}
}
Netbeans 正在生成私有函数。
private initializeComponents() {...}
因此该方法不可重写。只有受保护和 public 方法是可覆盖的。
一个额外的函数使您的代码对于 Netbeans 示例更加清晰。
但一般来说,你可以节省地使用私有方法来初始化 类.
此外,如果您有多个构造函数,使用一种额外的方法进行初始化是可行的。
class Foo {
int x,y;
String bar;
public Foo(x) {
this.x = x;
init();
}
public Foo(y) {
this.y = y;
init();
}
private void init() {
// .. something complicated or much to do
bar = "bla";
}
}
为了避免在构造函数中将 Swing 组件连接在一起,您可以简单地将连接的责任交给另一个对象。例如,您可以将布线职责交给工厂:
public class MyPanelFactory {
public MyBasePanel myBasePanel() {
MyBasePanel myBasePanel = new MyBasePanel();
initMyBasePanel(myBasePanel);
return myBasePanel;
}
public MyDerivedPanel myDerivedPanel() {
MyDerivedPanel myDerivedPanel = new MyDerivedPanel();
initMyBasePanel(myDerivedPanel);
return myDerivedPanel;
}
private void initMyBasePanel(MyBasePanel myBasePanel) {
myBasePanel.add(new JLabel("My label"), BorderLayout.CENTER);
}
}
或者您可以全力以赴,使用依赖项注入容器实例化所有 Swing 组件,并让容器触发连接。这是 Dagger 的示例:
@Module
public class MyPanelModule {
static class MyBasePanel extends JPanel {
private final JLabel myLabel;
MyBasePanel(JLabel myLabel) {
this.myLabel = myLabel;
}
void initComponents() {
this.add(myLabel, BorderLayout.CENTER);
}
}
static class MyDerivedPanel extends MyBasePanel {
private final List<JLabel> addedLabels = new ArrayList<>();
MyDerivedPanel(JLabel myLabel) {
super(myLabel);
}
@Override
public void add(Component comp, Object constraints) {
super.add(comp);
if (comp instanceof JLabel) {
JLabel label = (JLabel) comp;
addedLabels.add(label);
}
}
}
@Provides MyBasePanel myBasePanel(@Named("myLabel") JLabel myLabel) {
MyBasePanel myBasePanel = new MyBasePanel(myLabel);
myBasePanel.initComponents();
return myBasePanel;
}
@Provides MyDerivedPanel myDerivedPanel(@Named("myLabel") JLabel myLabel) {
MyDerivedPanel myDerivedPanel = new MyDerivedPanel(myLabel);
myDerivedPanel.initComponents();
return myDerivedPanel;
}
@Provides @Named("myLabel") JLabel myLabel() {
return new JLabel("My label");
}
}
OOP 原则之一是:优先组合而不是继承。当我创建一个 Swing GUI 时,我从不扩展 Swing 组件,除非我创建一个新的通用 Swing 组件(如 JTreeTable、JGraph、JCalendar 等)。
所以我的代码看起来像:
public class MyPanel {
private JPanel mainPanel;
public MyPanel() {
init();
}
private void init() {
mainPanel = new JPanel();
}
public Component getComponent() {
return mainPanel;
}
}
public class MyComposedPanel {
private JPanel mainPanel;
public MyComposedPanel() {
init();
}
private void init() {
mainPanel = new JPanel();
mainPanel.add(new MyPanel().getComponent());
}
public Component getComponent() {
return mainPanel;
}
}
这种方式有一个缺点:没有支持它的 GUI 生成器 ;)
一段时间后回来阅读接受的答案,我意识到有一种更简单的方法可以解决这个问题。如果调用可覆盖方法的责任可以转移到另一个 class,也可以使用工厂方法模式转移到静态方法:
class MyBasePanel extends JPanel {
public static MyBasePanel create() {
MyBasePanel panel = new MyBasePanel();
panel.initializeComponents();
return panel;
}
protected MyBasePanel() {
}
protected void initializeComponents() {
// layout setup omitted
// overridable call
add(new JLabel("My label"), BorderLayout.CENTER);
}
}
class MyDerivedPanel extends MyBasePanel {
private final List<JLabel> addedLabels = new ArrayList<>();
public static MyDerivedPanel create() {
MyDerivedPanel panel = new MyDerivedPanel();
panel.initializeComponents();
return panel;
}
protected MyDerivedPanel() {
}
@Override
public void add(Component comp, Object constraints) {
super.add(comp);
if (comp instanceof JLabel) {
JLabel label = (JLabel) comp;
addedLabels.add(label); // no more NPE here
}
}
}
当然,在subclassing的时候还是要记得调用initializeComponents
,但至少不是每次创建实例的时候!适当记录,这种方法既简单又可靠。
我知道从构造函数中调用可重写的方法是个坏主意。但我也看到它在 Swing 中无处不在,其中像 add(new JLabel("Something"));
这样的代码一直出现在构造函数中。
以 NetBeans IDE 为例。它对构造函数中的可覆盖调用非常挑剔。然而,当它生成 Swing 代码时,它会将所有这些 add()
方法调用放入一个 initializeComponents()
方法中……然后从构造函数中调用该方法!隐藏问题和禁用警告的好方法(NetBeans 没有“调用可覆盖方法的私有方法是从构造函数调用的”警告)。但不是真正解决问题的方法。
这是怎么回事?我已经这样做了很多年,但对此总是有一种不安的感觉。有没有更好的初始化 Swing 容器的方法,除了制作一个额外的 init()
方法(并且不要忘记每次都调用它,这有点无聊)?
例子
这是一个极其人为的例子,说明事情是如何出错的:
public class MyBasePanel extends JPanel {
public MyBasePanel() {
initializeComponents();
}
private void initializeComponents() {
// layout setup omitted
// overridable call
add(new JLabel("My label"), BorderLayout.CENTER);
}
}
public class MyDerivedPanel extends MyBasePanel {
private final List<JLabel> addedLabels = new ArrayList<>();
@Override
public void add(Component comp, Object constraints) {
super.add(comp);
if (comp instanceof JLabel) {
JLabel label = (JLabel) comp;
addedLabels.add(label); // NPE here
}
}
}
Netbeans 正在生成私有函数。
private initializeComponents() {...}
因此该方法不可重写。只有受保护和 public 方法是可覆盖的。
一个额外的函数使您的代码对于 Netbeans 示例更加清晰。 但一般来说,你可以节省地使用私有方法来初始化 类.
此外,如果您有多个构造函数,使用一种额外的方法进行初始化是可行的。
class Foo {
int x,y;
String bar;
public Foo(x) {
this.x = x;
init();
}
public Foo(y) {
this.y = y;
init();
}
private void init() {
// .. something complicated or much to do
bar = "bla";
}
}
为了避免在构造函数中将 Swing 组件连接在一起,您可以简单地将连接的责任交给另一个对象。例如,您可以将布线职责交给工厂:
public class MyPanelFactory {
public MyBasePanel myBasePanel() {
MyBasePanel myBasePanel = new MyBasePanel();
initMyBasePanel(myBasePanel);
return myBasePanel;
}
public MyDerivedPanel myDerivedPanel() {
MyDerivedPanel myDerivedPanel = new MyDerivedPanel();
initMyBasePanel(myDerivedPanel);
return myDerivedPanel;
}
private void initMyBasePanel(MyBasePanel myBasePanel) {
myBasePanel.add(new JLabel("My label"), BorderLayout.CENTER);
}
}
或者您可以全力以赴,使用依赖项注入容器实例化所有 Swing 组件,并让容器触发连接。这是 Dagger 的示例:
@Module
public class MyPanelModule {
static class MyBasePanel extends JPanel {
private final JLabel myLabel;
MyBasePanel(JLabel myLabel) {
this.myLabel = myLabel;
}
void initComponents() {
this.add(myLabel, BorderLayout.CENTER);
}
}
static class MyDerivedPanel extends MyBasePanel {
private final List<JLabel> addedLabels = new ArrayList<>();
MyDerivedPanel(JLabel myLabel) {
super(myLabel);
}
@Override
public void add(Component comp, Object constraints) {
super.add(comp);
if (comp instanceof JLabel) {
JLabel label = (JLabel) comp;
addedLabels.add(label);
}
}
}
@Provides MyBasePanel myBasePanel(@Named("myLabel") JLabel myLabel) {
MyBasePanel myBasePanel = new MyBasePanel(myLabel);
myBasePanel.initComponents();
return myBasePanel;
}
@Provides MyDerivedPanel myDerivedPanel(@Named("myLabel") JLabel myLabel) {
MyDerivedPanel myDerivedPanel = new MyDerivedPanel(myLabel);
myDerivedPanel.initComponents();
return myDerivedPanel;
}
@Provides @Named("myLabel") JLabel myLabel() {
return new JLabel("My label");
}
}
OOP 原则之一是:优先组合而不是继承。当我创建一个 Swing GUI 时,我从不扩展 Swing 组件,除非我创建一个新的通用 Swing 组件(如 JTreeTable、JGraph、JCalendar 等)。
所以我的代码看起来像:
public class MyPanel {
private JPanel mainPanel;
public MyPanel() {
init();
}
private void init() {
mainPanel = new JPanel();
}
public Component getComponent() {
return mainPanel;
}
}
public class MyComposedPanel {
private JPanel mainPanel;
public MyComposedPanel() {
init();
}
private void init() {
mainPanel = new JPanel();
mainPanel.add(new MyPanel().getComponent());
}
public Component getComponent() {
return mainPanel;
}
}
这种方式有一个缺点:没有支持它的 GUI 生成器 ;)
一段时间后回来阅读接受的答案,我意识到有一种更简单的方法可以解决这个问题。如果调用可覆盖方法的责任可以转移到另一个 class,也可以使用工厂方法模式转移到静态方法:
class MyBasePanel extends JPanel {
public static MyBasePanel create() {
MyBasePanel panel = new MyBasePanel();
panel.initializeComponents();
return panel;
}
protected MyBasePanel() {
}
protected void initializeComponents() {
// layout setup omitted
// overridable call
add(new JLabel("My label"), BorderLayout.CENTER);
}
}
class MyDerivedPanel extends MyBasePanel {
private final List<JLabel> addedLabels = new ArrayList<>();
public static MyDerivedPanel create() {
MyDerivedPanel panel = new MyDerivedPanel();
panel.initializeComponents();
return panel;
}
protected MyDerivedPanel() {
}
@Override
public void add(Component comp, Object constraints) {
super.add(comp);
if (comp instanceof JLabel) {
JLabel label = (JLabel) comp;
addedLabels.add(label); // no more NPE here
}
}
}
当然,在subclassing的时候还是要记得调用initializeComponents
,但至少不是每次创建实例的时候!适当记录,这种方法既简单又可靠。