Java 我应该在声明中实例化实例变量还是在构造函数中实例化实例变量?
这两种方法都有好处吗 例1:Java 我应该在声明中实例化实例变量还是在构造函数中实例化实例变量?,java,constructor,instance-variables,Java,Constructor,Instance Variables,这两种方法都有好处吗 例1: class A { B b = new B(); } 例2: class A { B b; A() { b = new B(); } } 这两种方法都是可以接受的。请注意,在后一种情况下,如果存在另一个构造函数,则可能无法初始化b=new b()。将构造函数外的初始值设定项代码视为公共构造函数,然后执行代码 我认为示例2更可取。我认为最好的做法是在构造函数外部声明并在构造函数中初始化。第二个例子是延迟初始化。第
class A {
B b = new B();
}
例2:
class A {
B b;
A() {
b = new B();
}
}
这两种方法都是可以接受的。请注意,在后一种情况下,如果存在另一个构造函数,则可能无法初始化
b=new b()。将构造函数外的初始值设定项代码视为公共构造函数,然后执行代码 我认为示例2更可取。我认为最好的做法是在构造函数外部声明并在构造函数中初始化。第二个例子是延迟初始化。第一个是更简单的初始化,它们本质上是相同的。我认为这几乎只是一个品味问题,只要初始化简单,不需要任何逻辑
如果不使用初始值设定项块,则构造函数方法更为脆弱,因为如果稍后添加第二个构造函数并忘记在那里初始化b,则只有在使用最后一个构造函数时,才会得到null b
有关Java初始化的更多详细信息(以及有关初始化程序块和其他不知名的初始化功能的说明),请参阅。
- 没有区别-实例变量初始化实际上是由编译器放入构造函数中的
- 第一种变体更具可读性
- 不能对第一个变量进行异常处理
- 另外还有初始化块,编译器也会将其放入构造函数中:
{
a = new A();
}
检查
发件人:
然而,字段声明不是任何方法的一部分,因此它们不能像语句那样执行。相反,Java编译器会自动生成实例字段初始化代码,并将其放入类的构造函数中。初始化代码按照它在源代码中出现的顺序插入到构造函数中,这意味着字段初始值设定项可以使用在它之前声明的字段的初始值
此外,您可能希望延迟初始化字段。如果初始化字段是一项昂贵的操作,您可以在需要时立即对其进行初始化:
ExpensiveObject o;
public ExpensiveObject getExpensiveObject() {
if (o == null) {
o = new ExpensiveObject();
}
return o;
}
最后(正如比尔所指出的),为了进行依赖关系管理,最好避免在类中的任何地方使用new
操作符。相反,使用更可取-即让其他人(另一个类/框架)实例化并将依赖项注入到您的类中。另一个选项是使用
这将从A
的构造函数中删除创建B
对象的责任。从长远来看,这将使您的代码更易于测试和维护。其思想是减少两个类A
和B
之间的耦合。这给您带来的一个好处是,您现在可以将任何扩展B
(或实现B
,如果它是一个接口)的对象传递给A
的构造函数,并且它将工作。一个缺点是您放弃了B
对象的封装,因此它向A
构造函数的调用方公开。你必须考虑利益是否值得权衡,但在很多情况下是这样的。 < P>我个人的“规则”(几乎从未被打破)是:
- 在开始时声明所有变量
街区
- 将所有变量设为最终变量,除非
不可能
- 每行声明一个变量
- 不要在以下情况下初始化变量:
宣布
- 只初始化一个文件中的某个内容
构造函数,当它需要来自
执行以下操作的构造函数:
初始化
所以我会有这样的代码:
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%确定在哪里查找变量声明(在块的开头)和它们的赋值(只要在声明之后有意义)。这可能会更有效,因为您从不使用未使用的值初始化变量(例如,declare和init vars,然后在这些变量中有一半需要有值之前抛出异常)。您也不会最终执行无意义的初始化(比如int i=0;然后,在使用“i”之前,do i=5;)
我非常重视一致性,所以遵循这个“规则”是我一直在做的事情,它使使用代码变得更加容易,因为您不必四处寻找来找到东西
您的里程数可能会有所不同。示例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.
}
}
// ...
}
使用依赖注入或延迟初始化总是可取的,正如在其他答案中已经详细解释的那样
当您不想要或不能使用这些模式时,对于原始数据类型,我可以考虑以下三个令人信服的原因,即为什么最好在构造函数外部初始化类属性:
避免重复=如果您有多个构造函数,或者当您需要添加更多构造函数时,您不必在所有构造函数体中重复初始化
改进的可读性=您可以很容易地从类外判断哪些变量必须初始化
减少的代码行数=对于在声明中完成的每个初始化,构造函数中将少一行
今天我被一种有趣的方式烧伤了:
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的初始化
class MyClass extends FooClass {
String a = null;
public MyClass() {
super(); // Superclass calls init();
}
@Override
protected void init() {
super.init();
if (something)
a = getStringYadaYada();
}
}
class A {
int b;
// secondary ctor
A(String b) {
this(Integer.valueOf(b));
}
// primary ctor
A(int b) {
this.b = b;
}
}
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;
class MyClass extends FooClass
{
String a;
{
if( a==null ) a="my custom default value";
}
...