【问题标题】:Is there a difference in setting fields inside or outside the constructor?在构造函数内部或外部设置字段有区别吗?
【发布时间】:2014-01-24 16:12:46
【问题描述】:
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;
    }
}

或者我可能需要做一些特殊的计算。

但如果我这样做,还有其他好的理由吗?

【问题讨论】:

  • 这些是字段,而不是属性。
  • 必须仔细考虑您的范围。
  • 在执行构造函数的主体之前初始化字段。如果您需要在构造函数中泄漏 this 引用(不要,但如果需要),最好在其声明中而不是在构造函数中初始化该字段,以便获得引用的人不会看不到完全未初始化的状态。
  • 看看这两种情况的字节码,也许你会发现一些相似之处。 :)
  • 出于风格问题,我总是在构造函数中初始化变量。我在构造函数的 JavaDoc 中记录了初始值,因此实际上将它们分配在那里。将初始值放在构造函数中记录的顶部对我来说并不合适。

标签: java


【解决方案1】:
public class Test {
   int value = 100;
   public Test() {

   }
}

当初始化值可用并且您可以在一行上声明和初始化字段时,这很有效。但是,这种形式的初始化由于其简单性而具有局限性。如果初始化需要一些逻辑(例如,错误处理或验证或条件),那么简单的赋值是不够的。当您使用构造函数初始化时,您可能会执行错误处理或其他逻辑。为了为类变量提供相同的功能,Java 编程语言包括静态初始化块。还有另外两种初始化实例变量的方法:

  1. 初始化块

    { // 初始化 }

  2. 最终方法

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

【讨论】:

    【解决方案2】:

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

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

    int field = 100;
    

    但除此之外,您应在每个构造函数中初始化该字段。

    您的类可能只有一个构造函数,但请想一想,在您的类的未来版本中还会有任何其他构造函数吗?

    【讨论】:

      【解决方案3】:

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

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

      在这种情况下,考虑一下Joshua Bloch has to say on initializing through constructors

      以下是总结,

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

      【讨论】:

        【解决方案4】:

        这完全取决于您打算如何使用它。我将假设您不打算将 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."<init>":()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."<init>":()V
               4: aload_0
               5: bipush        100
               7: putfield      #12                 // Field value:I
              10: return
        }
        
        D:\eclipse\workspace\asdf\bin>
        

        你猜怎么着?完全相同的!为什么?因为在使用 new 关键字创建对象之前,您无法使用值。

        oracle docs 声明:

        如您所见,您通常可以为字段提供初始值 在其声明中:

        public class BedAndBreakfast {
            // initialize to 10
            public static int capacity = 10;
        
            // initialize to false
            private boolean full = false;
        } 
        

        当初始化值可用并且可以进行初始化时,这很好用 一条线。但是,这种形式的初始化有局限性,因为 它的简单性。如果初始化需要一些逻辑(例如, 错误处理或用于填充复杂数组的 for 循环),简单 分配不足。实例变量可以在 构造函数,其中可以使用错误处理或其他逻辑。到 为类变量提供相同的能力,Java 编程 语言包括静态初始化块。

        所以现在您已经确认在构造函数中执行此操作的全部意义在于,如果您正在执行一些复杂的操作,例如初始化一个数组,否则您可以在声明字段时直接在此处执行。

        如果您要使用static,那么您显然在做两件不同的事情。这几乎就像检查是否有人曾经创建过这个对象的实例。在有人创建对象之前,您的变量将是 0,然后是 100

        【讨论】:

          【解决方案5】:

          好吧,这取决于。

          在第二种情况下,value 将填充 0with its default value,仅在实例化时使用 100 重新分配。在第一种情况下,value 立即被赋予100 的值。

          从语义上讲,这将有助于程序员 - 他们会看到这个特定值的含义不仅仅是它是任意的(尽管它应该在某处是一个常量值)。

          以编程方式,如果将原语设置为某个初始值,则不会有任何痛苦。这意味着其中有某些东西供您使用,如果您的程序依赖于非负值或错误值,George 它将工作。 p>

          在处理对象引用时,事情变得更加明确。以这两个类为例:

          public class Foo {
              List<String> elements;
              public Foo() {
              }
          
              public Foo(String... items) {
                  elements = new ArrayList<>();
                  for(String item : items) {
                      elements.add(item);
                  }
             }
          }
          
          public class Bar {
              List<String> elements = new ArrayList<>();
              public Bar() {
              }
          
              public Bar(String... items) {
                  for(String item : items) {
                      elements.add(item);
                  }
             }
          }
          

          故意使用无参数构造函数来强调这一点 - 对于Foo,如果我尝试使用elements,那么如果我不使用适当的构造函数,我就会遇到一些麻烦 - @ 987654331@ 是 null!* 我可以然后在需要时实例化它,但我非常希望避免破坏可能新的和填充的列表。

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

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

          ...那么我不得不担心它是线程安全的。哎呀,说个麻烦。

          对于Bar,我保证在实例化时,elements 中有一个列表实例,所以我不必担心关于它是null.**

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

          *:default value for all reference typesnull

          **:您确实必须担心会被覆盖,但这是本问题范围之外的问题。

          【讨论】:

            【解决方案6】:

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

            【讨论】:

              【解决方案7】:

              如果您不进行任何计算或不带任何参数,则无论您在构造函数中初始化还是不初始化这些变量,以上两者都没有区别。

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

              public class Test {
                  int value = 100;
                  public Test() {
              
                  }
              }
              
              • 当您直接为它们赋值时,它会更具可读性,无需从构造函数中查看。

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

              【讨论】:

                【解决方案8】:

                字段初始化代码被复制到每个构造函数中...如果您有多个构造函数并且希望在每个(甚至大多数)中使用相同的值初始化字段,那么最好在声明时初始化并覆盖 构造函数中的值。

                【讨论】:

                • 或者只使用构造函数链。
                猜你喜欢
                • 2020-06-18
                • 2011-04-16
                • 1970-01-01
                • 2011-10-23
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多