在构造函数内部或外部设置字段是否有区别?
public class Test { int value = 100; public Test() { } }
和
public class Test { int value; public Test() { value = 100; } }
是等价的吧? 我有理由为什么喜欢做另一个? 显然,如果构造函数接受稍后赋予字段的参数是一个原因:
public class Test { int value; public Test(int value) { this.value = value; } }
或许我需要做一些特别的计算。
但如果我不这样做,还有另一个好理由吗?
那么这一切都取决于你打算如何使用它。 我将假设你不打算让value
静止,但它只是出于内部目的。
首先让我们看一下字节码。
D:\eclipse\workspace\asdf\bin>javap -c A.class Compiled from "A.java" public class A { int value; public A(); Code: 0: aload_0 1: invokespecial #10 // Method java/lang/Object."":()V 4: aload_0 5: bipush 100 7: putfield #12 // Field value:I 10: return } D:\eclipse\workspace\asdf\bin>javap -c B.class Compiled from "B.java" public class B { int value; public B(); Code: 0: aload_0 1: invokespecial #10 // Method java/lang/Object." ":()V 4: aload_0 5: bipush 100 7: putfield #12 // Field value:I 10: return } D:\eclipse\workspace\asdf\bin>
你猜怎么着? 完全相同的! 为什么? 因为在使用new
关键字创建对象之前不能使用值。
oracle文档指出:
如您所见,您通常可以在其声明中为字段提供初始值:
public class BedAndBreakfast { // initialize to 10 public static int capacity = 10; // initialize to false private boolean full = false; }
当初始化值可用并且初始化可以放在一行上时,这很有效。 然而,这种forms的初始化由于其简单性而具有局限性。 如果初始化需要一些逻辑(例如,error handling或for循环来填充复杂数组),则简单的赋值是不合适的。 实例变量可以在构造函数中初始化,其中可以使用error handling或其他逻辑。 为了为类变量提供相同的function,Java编程语言包括静态初始化块。
因此,现在您已经确认在构造函数中执行此操作的重点是,如果您正在执行复杂的操作,例如初始化数组,则在声明字段时可以随意执行此操作。
如果您想使用static
那么您显然会做两件不同的事情。 这几乎就像检查某人是否曾创建过这个对象的实例。 你的变量将为0
直到有人创建一个对象然后它将是100
。
字段初始化代码被复制到每个构造函数中……如果你有多个构造函数并希望在每个构建函数中初始化相同的值(甚至只是大多数),那么最好在声明时初始化并覆盖构造函数中的值。
这得看情况。
对于第二种情况,将使用其默认值 0
填充值 ,仅在实例化时使用100
重新分配。 在第一种情况下, value
立即给出值100
。
在语义上,这将有助于程序员 – 他们会看到这个特定值意味着某种东西不仅仅是它是任意的(尽管,它应该是某个地方的常数值)。
以编程方式,如果将基元设置为某个初始值,则没有痛苦。 这意味着你有一些东西供你使用,如果你的程序依赖于非负或假值,乔治就可以了。
在处理对象引用时,事情变得更加明确。 举个例子,这两个类:
public class Foo { List elements; public Foo() { } public Foo(String... items) { elements = new ArrayList<>(); for(String item : items) { elements.add(item); } } } public class Bar { List elements = new ArrayList<>(); public Bar() { } public Bar(String... items) { for(String item : items) { elements.add(item); } } }
有意识地没有arg构造函数可以说明这一点 – 对于Foo
,如果我尝试使用elements
,那么如果我不使用适当的构造函数,我会遇到一些麻烦 – elements
为null
!*我可以然后只要我需要它就可以实例化它,但我非常希望避免破坏一个可能新建和填充的列表。
这意味着很多代码看起来像这样:
if(elements == null) { elements = new ArrayList<>(); }
…然后我不得不担心它是线程安全的。 Sheesh,谈论麻烦。
使用Bar
,我保证在实例化时, elements
中有一个列表实例,所以我不必担心它是null
。**
这被称为渴望实例化 。 你真的不想没有那个对象,所以为什么要等到你认为你需要它(或懒惰实例化 )?
*: 所有引用类型的默认值为 null
。
**:你必须担心被覆盖,但这个问题超出了这个问题的范围。
如果您没有进行任何计算或不采用任何参数,那么无论是初始化还是不在构造函数内初始化这些变量,上述任何一个都没有区别。
如果您将它们声明为第一个声明为:
public class Test { int value = 100; public Test() { } }
-
它更像是一种可读格式,因为你直接为它们赋值,不需要从构造函数中查看。
-
如果你有多个构造函数,那么你也不必重复初始化(你不能忘记它们)。
每当创建一个类时,首先初始化构造函数。 因此,当您在构造函数中声明或定义变量时,首先将内存分配给该变量,然后继续该过程。
目前在您的示例中,只有一个字段,您决定哪种初始化方式比另一种方式更好。
但是,如果你通过初始化很多字段(例如30或40)来增加复杂性,那么它确实会产生很大的不同。
在这种情况下,请考虑Joshua Bloch在通过构造函数初始化时所说的内容 。
以下是摘要,
- 伸缩构造函数模式有效,但是当有很多参数时很难编写客户端代码,并且更难以阅读它。
- 解决方案是Builder模式的一种forms,客户端不是直接创建所需对象,而是使用所有必需参数调用构造函数(或静态工厂)并获取构建器对象。
我不是在谈论字节码,但它们可以在语义上有所不同(如果你有多个构造函数),
如果您将字段定义如下,则该字段将始终初始化为100,无论调用哪个构造函数:
int field = 100;
但否则你应该初始化每个构造函数中的字段。
你的类可能只有一个构造函数,但只是想想,在你的类的未来版本中是否还有其他构造函数?
public class Test { int value = 100; public Test() { } }
这在初始化值可用时很有效,您可以在一行上声明并初始化字段。 然而,这种forms的初始化由于其简单性而具有局限性。 如果初始化需要一些逻辑(例如,error handling或validation或条件),则简单分配是不合适的。 使用构造函数初始化时,可能会执行error handling或其他逻辑。 为了为类变量提供相同的function,Java编程语言包括静态初始化块。 还有另外两种方法来初始化实例变量:
-
初始化块
{//初始化}
-
最后的方法
class Foo{ int age=initAge(); protected int initAge(){ //initialization code } }