Calling overridable methods like Swing's add() in constructor

时间:2016-04-04 18:30:34

标签: java swing constructor override

I know that calling overridable methods from constructors is a bad idea. But I also see that it's being done everywhere with Swing, where code like add(new JLabel("Something")); occurs in constructors all the time.

Take NetBeans IDE, for example. It is very picky about overridable calls in constructors. And yet, when it generates Swing code, it puts all those add() method calls into an initializeComponents() method... which is then called from the constructor! A nice way to hide a problem and disable the warning (NetBeans doesn't have a “a private method that calls overridable methods is called from a constructor” warning). But not really a way to solve the problem.

What's going on here? I've been doing it for ages, but always had an uneasy feeling about this. Is there a better way of initializing Swing containers, except for making an additional init() method (and not forgetting to call it every time, which is kind of boring)?

Example

Here is an extremely contrived example of how things can go wrong:

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
        }
    }
}

4 个答案:

答案 0 :(得分:6)

为避免在构造函数中将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");
    }
}

答案 1 :(得分:1)

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构建器;)

答案 2 :(得分:0)

一段时间后回来并阅读接受的答案,我意识到有一种更简单的方法来解决这个问题。如果调用可覆盖方法的责任可以移到另一个类,那么也可以使用工厂方法模式将其移动到静态方法:

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
        }
    }
}

当然,在进行子类化时,仍然需要记住调用initializeComponents,但至少不是每次都创建实例时!正确记录,这种方法既简单又可靠。

答案 3 :(得分:-2)

Netbeans is generating the function private.

private initializeComponents() {...}

Thus the method is not overridable. Only protected and public methods are overridable.

An extra function keeps your code much cleaner for the Netbeans expample. But in general you can savely use private methods to initialize classes.

Moreover if you have multiple constructors it's practical to use one extra method for initialization.

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";
   }
}