将窗口小部件的字段初始化为null的问题

时间:2016-02-09 00:00:03

标签: java swing

我正在查看一些视图代码,并注意到以下内容:

public class FooPanel extends JPanel {
   private JCheckBox checkBox = null;
   private JComboBox comboBox = null; 

   ...

   protected void initView() {
       checkBox = new JCheckBox();
       comboBox = new JComboBox();
   }
}

是否存在与将字段初始化为null相关的任何问题,即使它们是null而你没有声明它?

我看到的一件事是,如果按如下方式初始化为null,则会创建2个对象实例:

public class FooPanel extends JPanel {
   private JPanel innerPanel = null;

   ...

   protected void initView() {
       add.(getInnerPanel());
   }

   private JPanel getInnerPanel() {
       if(innerPanel == null) {
           innerPanel = new JPanel();
       }

       return innerPanel;
   }
}

在这种情况下,我得到2个内部面板。那么,惯例是什么?你应该为面板中的每个小部件编写一个getWidget()吗?我认为这些是由Eclipse中的WindowsBuilder插件生成的。

谢谢!

2 个答案:

答案 0 :(得分:0)

是否将字段分配给null或保持不变是没有区别的。这两种情况编译成完全相同的东西(参见字节码答案的底部)。

至于正在创建的2个实例:我不知道如何创建两个实例。如果尚未设置innerPanelnull,则构建并存储JPanel。如果确实存在,则返回时不触及任何内容。无论你做什么都是你的选择。

字节码:

$ cat TestNull.java
public class TestNull {
    public Object obj = null;
}
$ cat TestBlank.java
public class TestBlank {
    public Object obj;
}
$ javap -c TestNull
Compiled from "TestNull.java"
public class TestNull {
    public java.lang.Object obj;

    public TestNull();
      Code:
       0: aload_0
       1: invokespecial #1                  // super()
       4: aload_0
       5: aconst_null
       6: putfield      #2                  // obj = null
       9: return
}
$ javap -c TestBlank
Compiled from "TestBlank.java"
public class TestBlank {
    public java.lang.Object obj;

    public TestBlank();
      Code:
       0: aload_0
       1: invokespecial #1                  // super()
       4: aload_0
       5: aconst_null
       6: putfield      #2                  // obj = null
       9: return
}

答案 1 :(得分:0)

关于将成员值分配给null

在声明期间(或者在声明之外的其他时间),将值null分配给成员并不会产生任何问题。这种类型的赋值的一个优点是它使代码显式化,并使得您的意图对于稍后阅读代码的任何人都非常清楚。如果在声明期间没有为成员变量赋值,则不清楚作者是将该值设为null还是忘记编写赋值代码。

关于2 innerPanel个实例

初始分配给null,然后在需要时分配值(在代码中完成),不会导致创建2个实例。从代码中确切地说明您的getInnerPanel方法是如何使用的,但可能运行到多线程场景中,其中2个线程可能正在调用{{ 1}}大约在同一时间。

在这种情况下,您最终可能会有多个getInnerPanel成员分配(以及重复构造)。第一个线程可以检查innerPanel的值,发现它当前是innerPanel(从而输入null条件中的关联代码),然后运行{{ 1}}构造和赋值代码。如果不同的线程运行相同的代码,大约在同一时间,第二个线程可能执行相同的if值检查,发现该值仍然是JPanel(因为{{1}中的代码条件尚未执行构造和赋值),并输入相同的构造和赋值代码,导致多个构造出现。

您可以在innerPanel方法中设置null访问if,以防止这种情况发生:

第一个同步选项:

innerPanel

第二个同步选项:

synchronized

第二个选项是更好的选项,因为它减少了getInnerPanel代码的范围,但不是很大。如果您希望多次调用private synchronized JPanel getInnerPanel() { if(innerPanel == null) { innerPanel = new JPanel(); } return innerPanel; } 方法,则所显示的选项都不是最佳选项,因为调用该方法的每个线程都会因同步和private JPanel getInnerPanel() { synchronized(innerPanel) { if(innerPanel == null) { innerPanel = new JPanel(); } } return innerPanel; } 检查而变慢

在您的示例中,关键权衡以synchronized成员为中心。如果您知道将始终创建getInnerPanel或几乎总是创建,则最好删除延迟初始化(在需要时构造null的地方),只需构造{ {1}} innerPanel声明和分配的一部分,这也将消除innerPanel方法中JPanel检查的必要性。预先JPanel构造还消除了对innerPanel检查的任何需求,null方法可以简单地返回对getInnerPanel成员的引用。

另一方面,如果你确实有一些可能构建或不构造的面板,根据需要,你实现的延迟初始化非常有意义。

关于每个JPanel的get方法

对每个面板使用get方法是有意义的,如果您觉得它使代码更清晰,更容易使用,特别是如果它是您计划在整个项目中实现的模式。也就是说,应检查相关的延迟初始化和相关的JPanel代码块(基于上述权衡考虑因素),并以最符合您认为需要面板的方式实现和构造。