示例1:
class A {
B b = new B();
}
示例2:
class A {
B b;
A() {
b = new B();
}
}
#1 楼
没有什么区别-实例变量的初始化实际上是由编译器放入构造函数中的。
第一个变量更易于阅读。
第一个变体。
另外还有初始化块,编译器也将其放在构造函数中:
{
a = new A();
}
检查Sun的解释和建议
来自本教程:
但是,字段声明不是任何方法的一部分,因此无法执行如陈述。相反,Java编译器会自动生成实例字段初始化代码,并将其放入该类的一个或多个构造函数中。初始化代码按照在源代码中出现的顺序插入到构造函数中,这意味着字段初始化器可以使用在其之前声明的字段的初始值。
此外,您可能想要延迟初始化您的字段。如果初始化字段是一项昂贵的操作,则可以在需要时立即对其进行初始化:
ExpensiveObject o;
public ExpensiveObject getExpensiveObject() {
if (o == null) {
o = new ExpensiveObject();
}
return o;
}
最终(如Bill所指出),对于为了进行依赖性管理,最好避免在类中的任何地方使用
new
运算符。相反,最好使用Dependency Injection-即让其他人(另一个类/框架)实例化并将依赖项注入您的类中。#2 楼
另一个选择是使用依赖注入。class A{
B b;
A(B b) {
this.b = b;
}
}
这消除了从
B
的构造函数创建A
对象的责任。从长远来看,这将使您的代码更具可测试性,并且更易于维护。这个想法是要减少两个类A
和B
之间的耦合。这给您带来的好处是,您现在可以将任何扩展了B
的对象(或实现为接口的B
)传递给A
的构造函数,它将起作用。一个缺点是您放弃了B
对象的封装,因此它对A
构造函数的调用者公开。您必须考虑这些好处是否值得这种折衷,但在许多情况下是值得的。评论
另一方面,从现在您使A和B之间的链接更加可见的意义上来说,它增加了耦合。以前,使用B是A的内部问题,如果事实证明更好的设计是不使用B,则您的建议很难更改。
– JaakkoK
2010年1月3日,7:51
无论如何,耦合是存在的-A需要B。但是在类中实例化它意味着“ A恰好需要这个B”,而DI允许使用许多不同的B。
– Bozho
2010年1月3日在8:57
A现在在此设计中需要B,我的意思是这种情况是否会改变。
– JaakkoK
2010年1月3日,9:52
@jk:如果使用DI和Factory类将对象的创建与业务逻辑的任何地方(特别是在创建A的地方)分开,则根本就不难更改。它只需要在一个地方更改,即创建A对象的工厂。如果您对此保持一致,则一点都不难掌握。我认为收益大于成本。减少了耦合,整体设计更易于测试和维护。
–比尔蜥蜴
2010年1月3日,14:33
@BilltheLizard即使对于诸如List
–短跑运动员
2015年1月22日23:11
#3 楼
我今天被一种有趣的方式烧死了: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
的初始化。评论
这里的教训是永远不要从构造函数中调用可重写的函数! :)有效的Java,第17项对此进行了很好的讨论。
– Mohit Chugh
15年8月26日在6:33
优点。通过在声明时进行初始化,您将无法精确控制何时初始化变量。那可以,但是您可以使用a $$(是的,编译器也确实会更改其实现!)。
– SMBiggs
16年6月10日在6:40
@MohitChugh:的确如此。实际上,如今,如果您从构造函数中调用可重写的方法,则像NetBeans这样的现代Java IDE(当然还有其他人)也会向您发出警告。而这正是Edward Falk遇到的原因。
– GeertVc
18年7月30日在9:01
#4 楼
我个人的“规则”(几乎从未中断过)是:在一个块的开头声明所有变量
使所有变量最终确定,除非它们
不能
每行声明一个变量
不要在
声明的情况下初始化变量
仅在需要
数据的情况下在
构造函数中初始化
构造函数执行
初始化
,所以我会像这样的代码:
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%确定在哪里查找变量声明(在块的开头),以及它们的赋值(在声明之后有意义的时候)。由于您永远都不会使用未使用的值初始化变量(例如,声明和初始化vars,然后在一半的var需要具有值之前引发异常),因此这也可能使效率更高。您也不会做无意义的初始化(例如int i = 0;然后,在使用“ i”之前,i = 5;。)
我非常重视一致性,所以下面这个“规则”是我一直在做的事情,它使使用代码变得容易得多,因为您不必四处寻找东西。
您的工作量可能会有所不同。
评论
可能是因为“永远不要在声明的位置初始化变量”(尽管不是我)。或“换行符”,它被认为是C / C ++习惯用法。无论如何,我要投票赞成;)
– Bozho
2010年1月4日在6:42
我宁愿人们出于技术原因而不是美学原因({}位置或它们的不必要用法)投下反对票。如果人们投了反对票,他们至少应该说出他们认为答案是错误的...从技术上讲,这没有什么错,这是我过去20年用C / C ++ / Java编码的方式(以及Java 16)因此,我100%确信它会起作用:-)(感谢您的反投票:-)
–豆腐啤酒
2010年1月4日,下午6:47
这是罪恶的丑陋,那就是问题所在。在使用三元运算符之前您会吃掉自己的眼睛,但是相对于OOP合适的构造函数,它更喜欢使用多个静态初始化块,这真是很有趣。您的方式完全打破了依赖注入(从表面上看,是的,编译器本质上是通过将所有内容移至构造函数来为您解决的,但实际上您是在教人们依靠编译器魔术,而不是正确的事情),这种方法难以维护,使我们回到了C ++的糟糕日子。新手读者,请不要这样做。
–有远见的软件解决方案
13年8月6日在6:23
如果您要包含关于使变量可以是final,final的规则。然后,您实际上应该已经包括了使所有变量可以私有的私有的内容。
– Cruncher
2013年9月3日20:40在
@TofuBeer:不用担心。大多数Java开发人员倾向于过于over腐和挑剔。我相信即使Joshua Bloch编写了代码,他们也会选择(假设他们不知道是他)。个人品味就是个人品味;最终,CPU和JRE都不关心语法风格。
–水彩画
2013年9月20日22:38在
#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.
}
}
// ...
}
#6 楼
我认为这几乎只是一个口味问题,只要初始化很简单并且不需要任何逻辑即可。如果不使用初始化程序块,则构造函数方法会更脆弱,因为如果以后添加第二个构造函数而忘记在那里初始化b,则只会得到null b使用最后一个构造函数时。
有关Java初始化的更多详细信息,以及有关的解释,请参见http://java.sun.com/docs/books/tutorial/java/javaOO/initial.html。初始化块和其他未知的初始化功能)。
评论
那就是为什么你有DI和@Required :)
– Sujoy
2010年1月3日,12:39
是。我只是在描述两个示例之间的差异。
– Vinko Vrsalovic
2010年1月3日,13:12
可能会有大量的构造函数,这意味着您违反了单一职责原则,并且在设计中遇到了更大的问题。
–有远见的软件解决方案
13年8月6日在6:31
#7 楼
最好使用依赖项注入或惰性初始化,如在其他答案中已经详细说明的那样。当您不希望或不能使用这些模式时,对于原始数据类型,有三种我能想到的令人信服的理由,为什么最好在构造函数之外初始化类属性:
避免重复=如果您有多个构造函数,或者何时使用需要添加更多内容,您将不必一遍又一遍地在所有构造函数主体中重复初始化;
提高了可读性=您可以一目了然地判断哪些变量必须从外部初始化该类;
减少的代码行=对于声明中进行的每次初始化,构造函数中的代码行都将减少。
#8 楼
我在回复中没有看到以下内容:在声明时进行初始化的可能好处可能是在当今的IDE中,您可以很容易地跳转到变量的声明(通常是
Ctrl-<hover_over_the_variable>-<left_mouse_click>
)在代码中的任何位置。然后,您立即看到该变量的值。否则,您必须“搜索”完成初始化的位置(主要是构造函数)。这个优势当然是所有其他逻辑推理的第二要因,但是对于某些具有“特征”的人而言可能更重要。
#9 楼
两种方法都是可以接受的。请注意,在后一种情况下,如果存在另一个构造函数,则b=new B()
可能不会初始化。将构造函数外部的初始化程序代码视为通用构造函数,然后执行该代码。 #10 楼
我认为示例2是可取的。我认为最好的做法是在构造函数外部声明并在构造函数中初始化。#11 楼
第二个是延迟初始化的示例。第一个是更简单的初始化,它们本质上是相同的。#12 楼
还有一个微妙的原因可以在构造函数之外进行初始化,这是以前没有人提到的(我必须说的很具体)。如果您使用UML工具从代码中生成类图(逆向工程),我相信大多数工具都会注意到示例1的初始化并将其转移到图上(如果您希望它显示初始值,例如我做)。他们不会从示例2中获取这些初始值。同样,这是一个非常具体的原因-如果您使用的是UML工具,但是一旦我了解到这一点,我便会尝试将所有默认值带到构造函数之外,除非前面提到,存在可能引发异常或复杂逻辑的问题。#13 楼
第二个选项是可取的,因为允许在ctor中使用不同的逻辑进行类实例化,并使用ctors链接。例如,class A {
int b;
// secondary ctor
A(String b) {
this(Integer.valueOf(b));
}
// primary ctor
A(int b) {
this.b = b;
}
}
因此第二个选项更加灵活。
#14 楼
实际上,这是完全不同的:声明在构造之前发生。假设如果在两个地方都初始化了变量(在这种情况下为b),则构造函数的初始化将替换在类级别完成的初始化。
因此在类级别声明变量,然后在构造函数中对其进行初始化。
#15 楼
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";
}
...
评论
@Bozho对象初始化在初始化块之前还是之后进入构造函数?
– Cruncher
2013年9月3日20:34在
我认为之前。但不确定:)
– Bozho
2013年9月4日上午10:55
第一个变体是更具“可读性”的,这是可以讨论的:如果在构造函数中初始化所有字段,则您确切地知道在阅读代码时,只有一个地方可以搜索...
–nbro
15年5月8日在10:42
@Bozho-您能解释一下为什么不能使用第一个变量进行异常处理吗?
– Mario Galea
2015年11月23日14:52
“并且最终(如Bill所指出的那样),为了进行依赖管理,最好避免在类中的任何地方使用new运算符。相反,最好使用Dependency Injection。”至少你说更好。如果热心跟随鲍勃叔叔,可能会引起很多问题(例如工厂爆炸)。新操作符没有什么问题,也不是所有的依赖项都需要注入,特别是如果您对社交测试感到满意的话。
–brumScouse
17年1月13日在11:42