Dart中成员变量的初始化是否有区别?

8
在Dart中,立即赋值和在构造函数中赋值是否有像Java那样的区别?
class Example {
    int x = 3;
}

vs

class Example {
    int x;
    Example() {
        x = 3;
    }
}

我之所以问这个问题,是因为当我使用Flutter并尝试将一个使用setState的Function赋值给一个变量时,前一种方法无法实现,但后一种方法可以。

1个回答

30

在您的简单情况下,这并不重要。

一般来说,您可以通过以下几种方式初始化实例变量:

内联(字段初始化器)

class Example1 {
  T x = value;
}

优点:

  • 直接、简洁。
  • 成员将在所有构造函数中初始化。
  • 可用于初始化final或非空成员。
  • 成员在调用基类构造函数之前初始化,这在基类构造函数调用被派生类覆盖的成员函数时非常重要。

缺点:

初始化列表

class Example2 {
  T x;

  Example2() : x = value;
}

优点:

  • 可以用于初始化final或非空成员。
  • 在调用基类构造函数之前初始化成员,当基类构造函数调用被派生类重写的成员函数时,这一点非常重要。
  • 可以利用构造参数。
  • 初始化的变量始终引用一个成员变量,而不是构造函数参数。

缺点:

  • 如果类有多个构造函数,则需要复制初始化代码,或者构造函数应该重定向到一个公共的构造函数。
  • 不能依赖this,因为初始化发生在this变得有效之前(即不能依赖其他实例成员)。
  • 只能初始化封闭类的成员。因为初始化列表在调用基类构造函数之前执行,所以它们不能设置基类成员。

构造函数体

class Example3 {
  T x;

  Example3() {
    x = value;
  } 
}

优点:

  • 可以使用构造参数。
  • 可用于执行更复杂的初始化,例如成员无法通过单个表达式进行初始化的情况。
  • 可以使用 this(即可以使用其他实例成员)。
  • 可用于设置基类成员。

缺点:

  • 不能用于初始化非late final或非可空成员。
  • 如果类有多个构造函数,则需要复制初始化代码或重构初始化代码(例如,重定向到公共构造函数)。
  • 成员在调用基类构造函数后初始化。
  • 如果构造函数具有与成员变量同名的参数,很容易意外引用参数而不是成员变量。(有关详细信息,请参见https://github.com/dart-lang/linter/issues/2552。)

可能还有一些我忘记的要点,但我认为这应该涵盖了主要内容。

直接内联初始化首先发生,然后是初始化列表,最后是构造函数体。另请参见分配参数列表和初始化器列表中值的差异,它解释了为什么this仅在对象初始化的后期阶段才变为有效。

以下是一个成员初始化顺序很重要的示例:

class Base {
  Base() {
    doSomething();
  }

  void doSomething() {}
}

class DerivedEarly extends Base {
  int? x;

  DerivedEarly() : x = 42;

  @override
  void doSomething() => print(x);
}

class DerivedLate extends Base {
  int? x;

  DerivedLate() {
    x = 42;
  }

  @override
  void doSomething() => print(x);
}

void main() {
  DerivedEarly(); // Prints: 42
  DerivedLate(); // Prints: null
}

1
谢谢您提供这个并且保持它的最新,非常有信息量和组织性。 - Huthaifa Muayyad
正如您的列表所示,初始化相互依赖的 final 成员目前需要使用 late 关键字,这有点不符合直觉。在 Github 上有一个问题(https://github.com/dart-lang/language/issues/1394)讨论了这个问题。 - Sebastian
构造函数体... 不能用于初始化非延迟的最终成员。这就是我拒绝使用Dart的原因。你应该能够从构造函数体中仅一次设置最终实例变量。 - undefined
@still_dreaming_1 然后将其声明为“晚”。构造函数体在执行其他初始化操作之后执行。此外,C++也没有太大的区别;初始化列表几乎总是优先于构造函数体中的工作。 - undefined
@jamesdlin 同时,请停止使用 late。这并不是解决问题的好办法。它只会使代码变得不够安全,并阻止编译器提供更多的安全性。当一种语言无法提供 late 的良好替代方案时,这是语言的缺陷。 - undefined
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接