在构造函数内部或外部设置字段是否有区别?

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 ,那么如果我不使用适当的构造函数,我会遇到一些麻烦 – elementsnull !*我可以然后只要我需要它就可以实例化它,但我非常希望避免破坏一个可能新建和填充的列表。

这意味着很多代码看起来像这样:

 if(elements == null) { elements = new ArrayList<>(); } 

…然后我不得不担心它是线程安全的。 Sheesh,谈论麻烦。

使用Bar ,我保证在实例化时, elements中有一个列表实例,所以我不必担心它是null 。**

这被称为渴望实例化 。 你真的不想没有那个对象,所以为什么要等到你认为你需要它(或懒惰实例化 )?

*: 所有引用类型的默认值为 null

**:你必须担心被覆盖,但这个问题超出了这个问题的范围。

如果您没有进行任何计算或不采用任何参数,那么无论是初始化还是不在构造函数内初始化这些变量,上述任何一个都没有区别。

如果您将它们声明为第一个声明为:

 public class Test { int value = 100; public Test() { } } 
  • 它更像是一种可读格式,因为你直接为它们赋值,不需要从构造函数中查看。

  • 如果你有多个构造函数,那么你也不必重复初始化(你不能忘记它们)。

每当创建一个类时,首先初始化构造函数。 因此,当您在构造函数中声明或定义变量时,首先将内存分配给该变量,然后继续该过程。

目前在您的示例中,只有一个字段,您决定哪种初始化方式比另一种方式更好。

但是,如果你通过初始化很多字段(例如30或40)来增加复杂性,那么它确实会产生很大的不同。

在这种情况下,请考虑Joshua Bloch在通过构造函数初始化时所说的内容 。

以下是摘要,

  1. 伸缩构造函数模式有效,但是当有很多参数时很难编写客户端代码,并且更难以阅读它。
  2. 解决方案是Builder模式的一种forms,客户端不是直接创建所需对象,而是使用所有必需参数调用构造函数(或静态工厂)并获取构建器对象。

我不是在谈论字节码,但它们可以在语义上有所不同(如果你有多个构造函数),

如果您将字段定义如下,则该字段将始终初始化为100,无论调用哪个构造函数:

 int field = 100; 

但否则你应该初始化每个构造函数中的字段。

你的类可能只有一个构造函数,但只是想想,在你的类的未来版本中是否还有其他构造函数?

 public class Test { int value = 100; public Test() { } } 

这在初始化值可用时很有效,您可以在一行上声明并初始化字段。 然而,这种forms的初始化由于其简单性而具有局限性。 如果初始化需要一些逻辑(例如,error handling或validation或条件),则简单分配是不合适的。 使用构造函数初始化时,可能会执行error handling或其他逻辑。 为了为类变量提供相同的function,Java编程语言包括静态初始化块。 还有另外两种方法来初始化实例变量:

  1. 初始化块

    {//初始化}

  2. 最后的方法

     class Foo{ int age=initAge(); protected int initAge(){ //initialization code } }