【发布时间】:2010-12-31 22:35:20
【问题描述】:
这两种方法有什么优势吗?
示例 1:
class A {
B b = new B();
}
示例 2:
class A {
B b;
A() {
b = new B();
}
}
【问题讨论】:
标签: java constructor instance-variables
这两种方法有什么优势吗?
示例 1:
class A {
B b = new B();
}
示例 2:
class A {
B b;
A() {
b = new B();
}
}
【问题讨论】:
标签: java constructor instance-variables
class MyClass extends FooClass {
String a = null;
public MyClass() {
super(); // Superclass calls init();
}
@Override
protected void init() {
super.init();
if (something)
a = getStringYadaYada();
}
}
关于上述,
String a = null;
null init 可以避免,因为无论如何它是默认值。 但是,如果您需要另一个默认值, 然后,由于不受控制的初始化顺序, 我会修复如下:
class MyClass extends FooClass
{
String a;
{
if( a==null ) a="my custom default value";
}
...
【讨论】:
其实很不一样:
声明发生在构造之前。所以说如果一个人在两个地方都初始化了变量(在这种情况下是 b),构造函数的初始化将替换在类级别完成的初始化。
所以在类级别声明变量,在构造函数中初始化它们。
【讨论】:
我没有在回复中看到以下内容:
在声明时进行初始化的一个可能优势可能是现在的 IDE,您可以在其中非常轻松地跳转到变量的声明(主要是
Ctrl-<hover_over_the_variable>-<left_mouse_click>) 来自代码中的任何位置。然后您会立即看到该变量的值。否则,您必须“搜索”完成初始化的位置(主要是:构造函数)。
这个优势当然次于所有其他逻辑推理,但对于某些人来说,“特征”可能更重要。
【讨论】:
第二个选项更可取,因为它允许在 ctors 中使用不同的逻辑进行类实例化并使用 ctors 链接。例如
class A {
int b;
// secondary ctor
A(String b) {
this(Integer.valueOf(b));
}
// primary ctor
A(int b) {
this.b = b;
}
}
所以第二个选项更灵活。
【讨论】:
另一种选择是使用Dependency Injection。
class A{
B b;
A(B b) {
this.b = b;
}
}
这消除了从A 的构造函数创建B 对象的责任。从长远来看,这将使您的代码更具可测试性并且更易于维护。这个想法是减少A和B这两个类之间的耦合。这给您带来的一个好处是,您现在可以将扩展B(或实现B,如果它是一个接口)的任何对象传递给A 的构造函数,并且它将起作用。一个缺点是您放弃了B 对象的封装,因此它暴露给A 构造函数的调用者。您必须考虑这些好处是否值得这种权衡,但在许多情况下确实如此。
【讨论】:
A 和B 之间的链接更加可见。之前B的使用是A内部的事情,如果发现更好的设计不是使用B,你的建议就更难改变了。
A 在 this 设计中需要 B now,我的意思是如果这种情况发生变化。
List<Integer> intList = new ArrayList<>(); 这样简单的东西吗?这可能完全是一个内部实现细节。将 ArrayList 传递给构造函数似乎与良好的封装完全相反。
另外还有一个初始化块,它也是由编译器放入构造函数中的:
{
a = new A();
}
查看Sun's explanation and advice
然而,字段声明不是任何方法的一部分,因此它们不能像语句一样执行。相反,Java 编译器会自动生成实例字段初始化代码并将其放入类的构造函数或构造函数中。初始化代码按照它在源代码中出现的顺序插入到构造函数中,这意味着字段初始化器可以使用在它之前声明的字段的初始值。
此外,您可能希望延迟初始化您的字段。如果初始化字段是一项昂贵的操作,您可以在需要时立即对其进行初始化:
ExpensiveObject o;
public ExpensiveObject getExpensiveObject() {
if (o == null) {
o = new ExpensiveObject();
}
return o;
}
最终(正如 Bill 所指出的),为了依赖管理,最好避免在类中的任何位置使用new 运算符。相反,使用Dependency Injection 更可取 - 即让其他人(另一个类/框架)实例化并在您的类中注入依赖项。
【讨论】:
the first variant is more "readable" 这是可以讨论的:如果您在构造函数中初始化所有字段,您就会清楚地知道,当您阅读代码时,您只有一个可以搜索的地方...
我个人的“规则”(几乎从未被打破)是:
所以我会有这样的代码:
public class X
{
public static final int USED_AS_A_CASE_LABEL = 1; // only exception - the compiler makes me
private static final int A;
private final int b;
private int c;
static
{
A = 42;
}
{
b = 7;
}
public X(final int val)
{
c = val;
}
public void foo(final boolean f)
{
final int d;
final int e;
d = 7;
// I will eat my own eyes before using ?: - personal taste.
if(f)
{
e = 1;
}
else
{
e = 2;
}
}
}
这样我总是 100% 确定在哪里查找变量声明(在块的开头),以及它们的赋值(只要在声明之后有意义)。这最终可能会更有效,因为您从不使用未使用的值初始化变量(例如声明和初始化变量,然后在一半需要具有值的变量之前抛出异常)。你也不会做无意义的初始化(比如 int i = 0; 然后稍后,在使用“i”之前,do i = 5;.
我非常看重一致性,所以我一直都遵循这个“规则”,而且它使代码的工作变得更加容易,因为您不必四处寻找东西。
您的里程可能会有所不同。
【讨论】:
在构造函数之外进行初始化还有一个更微妙的原因,之前没有人提到过(我必须说非常具体)。如果您使用 UML 工具从代码生成类图(逆向工程),我相信大多数工具都会记录示例 1 的初始化并将其转换为图表(如果您希望它显示初始值,例如我愿意)。他们不会从示例 2 中获取这些初始值。同样,这是一个非常具体的原因 - 如果您正在使用 UML 工具,但是一旦我了解到这一点,我就会尝试将我的所有默认值都放在构造函数之外,除非像以前那样前面提到过,存在可能抛出异常或逻辑复杂的问题。
【讨论】:
我今天被一种有趣的方式烫伤了:
class MyClass extends FooClass {
String a = null;
public MyClass() {
super(); // Superclass calls init();
}
@Override
protected void init() {
super.init();
if (something)
a = getStringYadaYada();
}
}
看到错误了吗?事实证明,a = null 初始化程序在调用超类构造函数之后被调用。由于超类构造函数调用 init(),a 的初始化在之后是a = null 的初始化。
【讨论】:
使用依赖注入或延迟初始化总是更可取的,正如在其他答案中已经详细解释的那样。
当您不想或不能使用这些模式时,对于原始数据类型,我可以想到三个令人信服的理由,为什么最好在构造函数之外初始化类属性:
【讨论】:
示例 2 不太灵活。如果添加另一个构造函数,则需要记住在该构造函数中实例化字段。只需直接实例化该字段,或在 getter 的某处引入延迟加载。
如果实例化需要的不仅仅是简单的new,请使用初始化程序块。这将运行无论使用的构造函数。例如
public class A {
private Properties properties;
{
try {
properties = new Properties();
properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("file.properties"));
} catch (IOException e) {
throw new ConfigurationException("Failed to load properties file.", e); // It's a subclass of RuntimeException.
}
}
// ...
}
【讨论】:
我认为这几乎只是一个品味问题,只要初始化简单且不需要任何逻辑即可。
如果您不使用初始化块,构造函数方法会更加脆弱,因为如果您稍后添加第二个构造函数并忘记在那里初始化 b,那么只有在使用最后一个构造函数时您才会得到空 b .
请参阅http://java.sun.com/docs/books/tutorial/java/javaOO/initial.html,了解有关 Java 初始化的更多详细信息(以及有关初始化程序块和其他不为人知的初始化功能的说明)。
【讨论】:
第二个是惰性初始化的例子。第一个是更简单的初始化,它们本质上是相同的。
【讨论】:
我认为示例 2 更可取。我认为最好的做法是在构造函数之外声明并在构造函数中初始化。
【讨论】:
这两种方法都可以接受。请注意,在后一种情况下,如果存在另一个构造函数,b=new B() 可能不会被初始化。将构造函数之外的初始化代码视为通用构造函数,然后执行代码。
【讨论】: