5

我最近开始了一个新项目,我试图让我的实例变量始终初始化为某个值,因此它们在任何时候都不是空的。下面的小例子:

public class ItemManager {

  ItemMaster itemMaster;
  List<ItemComponentManager> components;

  ItemManager() {
    itemMaster = new ItemMaster();
    components = new ArrayList<ItemComponentManager>();
  }

  ...
}

重点主要是避免在代码中某处使用实例变量之前对null进行繁琐的检查。到目前为止,它运行良好,并且您大多不需要null值,因为您还可以检查空字符串或空列表等。我没有将这种方法用于方法范围的变量,因为它们的范围非常有限,所以不会影响代码的其他部分。

这一切都是实验性的,所以我想知道这种方法是否可行,或者是否有一些我还没有看到的陷阱。保持实例变量初始化通常是个好主意吗?

4

12 回答 12

9

我通常将空集合和空集合视为两个独立的事物:

一个空集合意味着我知道有零个可用项目。一个空集合会告诉我我不知道集合的状态,这是另一回事。

所以我真的不认为这是非此即彼的。如果我在构造函数中初始化它们,我会声明变量final 。如果您将其声明为 final,那么读者将非常清楚该集合不能为空。

于 2009-02-13T10:00:16.330 回答
4

首先,如果您想保留控制权,所有非最终实例变量都必须声明为私有!

还要考虑惰性实例化——这也避免了“坏状态”,但只在使用时初始化:

class Foo {
    private List<X> stuff;
    public void add(X x) {
        if (stuff == null)
            stuff = new ArrayList<X>();
        stuff.add(x);
    }
    public List<X> getStuff() {
        if (stuff == null)
            return Collections.emptyList();
        return Collections.unmodifiableList(stuff);
    }
}

(注意 Collections.unmodifiableList 的使用——除非你真的希望调用者能够从你的列表中添加/删除,你应该让它不可变)

考虑将创建多少个相关对象的实例。如果有很多,并且您总是创建列表(并且最终可能会得到许多空列表),那么您可能会创建比您需要的更多的对象。

除此之外,这实际上是一个品味问题,以及在构建时是否可以具有有意义的值。

如果您正在使用 DI/IOC,您希望框架为您完成工作(尽管您可以通过构造函数注入来完成;我更喜欢 setter)—— Scott

于 2009-02-13T12:52:07.053 回答
3

我会说这完全没问题 - 只要您记得那里有“空”占位符值而不是真实数据。

将它们保持为空的好处是迫使您处理它们 - 否则程序会崩溃。如果您创建了空对象,但忘记了它们,您会得到未定义的结果。

并且只是评论防御性编码 - 如果您是创建对象的人并且从不将它们设置为 null,则无需每次都检查 null。如果由于某种原因你得到空值,那么你就知道发生了灾难性的错误,程序无论如何都会崩溃。

于 2009-02-13T09:59:17.143 回答
2

如果可能的话,我会把它们做成最终的。然后它们必须在构造函数中初始化并且不能为空。

在任何情况下,您还应该将它们设为私有,以防止其他类将 null 分配给它们。如果您可以检查您的班级中从未分配过 null ,那么该方法将起作用。

于 2009-02-13T10:09:49.487 回答
2

我遇到过一些导致问题的情况。在反序列化期间,一些框架不会调用构造函数,我不知道他们如何或为什么选择这样做,但它确实发生了。这可能导致您的值是null. 我也遇到过调用构造函数但由于某种原因未初始化成员变量的情况。

实际上,我会使用以下代码而不是您的代码:

public class ItemManager {

  ItemMaster itemMaster = new ItemMaster();
  List<ItemComponentManager> components = new ArrayList<ItemComponentManager>();

  ItemManager() {
     ...
  }
  ...
}
于 2009-02-13T10:19:01.760 回答
2

我处理我声明的任何变量的方式是决定它是否会在对象(或类,如果它是静态的)的生命周期内发生变化。如果答案是“否”,那么我将其定为最终结果。

使其最终强制您在创建对象时给它一个值......我个人会执行以下操作,除非我知道我会改变要点:

  私人最终ItemMaster itemMaster;
  私有最终列表组件;

  // 实例初始化块 - 发生在构造时
  {
      itemMaster = new ItemMaster();
      组件 = 新的 ArrayList();
  }

您的代码现在的方式必须始终检查 null,因为您没有将变量标记为私有(这意味着同一包中的任何类都可以将值更改为 null)。

于 2009-02-13T17:56:29.367 回答
1

是的,在构造函数中初始化所有类变量是一个很好的主意。

于 2009-02-13T09:58:11.140 回答
1

重点主要是避免在代码中某处使用类变量之前对 null 进行繁琐的检查。

您仍然需要检查是否为空。第三方库甚至 Java API 有时会返回 null。

此外,实例化一个可能永远不会使用的对象是一种浪费,但这取决于你的类的设计。

于 2009-02-13T10:01:22.270 回答
1

对象在构建后应该 100% 准备好使用。用户不必检查空值。防御性编程是要走的路——保持检查。

为了 DRY 的利益,您可以将检查放在设置器中,并让构造函数调用它们。这样您就不会对检查进行两次编码。

于 2009-02-13T10:45:22.053 回答
0

如果在您设置的一种方法中会发生什么

itemMaster = null; 

或者您将对 ItemManager 的引用返回给其他类,并将 itemMaster 设置为 null。(您可以防止这很容易返回您的 ItemManager 等的克隆)

我会保留支票,因为这是可能的。

于 2009-02-13T09:56:54.167 回答
0

如果这是您的所有代码并且您想设置该约定,那么拥有它应该是一件好事。不过,我同意 Paul 的评论,没有什么可以阻止某些错误代码意外地将您的类变量之一设置为 null。作为一般规则,我总是检查空值。是的,这是一个 PITA,但防御性编码可能是一件好事。

于 2009-02-13T09:59:09.250 回答
0

从“ItemManager”类的名称来看,ItemManager 在某些应用程序中听起来像是一个单例。如果是这样,您应该进行调查,并且真的,真的,知道依赖注入。使用 Spring ( http://www.springsource.org/ ) 之类的东西来创建 ItemComponentManagers 列表并将其注入 ItemManager。

如果没有 DI,在严肃的应用程序中手动初始化是调试的噩梦,连接各种“管理器”类以进行狭窄的测试是地狱。

始终使用 DI(即使在构建测试时)。对于数据对象,创建一个 get() 方法,如果列表不存在则创建该列表。但是,如果对象很复杂,几乎可以肯定的是,使用 Factory 或 Builder 模式会让您的生活变得更好,并让 F/B 根据需要设置成员变量。

于 2009-02-13T19:23:13.027 回答