如何最好地转换触发 "The non-nullable variable must be assigned" 错误的旧 Dart 代码?

How best to convert old Dart code that triggers "The non-nullable variable must be assigned" error?

采用以下非空安全 Dart 代码:

static String appBarShiftTitleString(int fromEpochSeconds) {
    String monthWord;  
    String dayWord;

    DateTime dt = DateTime.fromMillisecondsSinceEpoch(fromEpochSeconds * 1000);

    switch (dt.month) {
      case 1:
        monthWord = "Jan";
        break;
      case 2:
        monthWord = "Feb";
        break;
      case 3:
        monthWord = "Mar";
        break;
      case 4:
        monthWord = "Apr";
        break;
      case 5:
        monthWord = "May";
        break;
      case 6:
        monthWord = "Jun";
        break;
      case 7:
        monthWord = "Jul";
        break;
      case 8:
        monthWord = "Aug";
        break;
      case 9:
        monthWord = "Sep";
        break;
      case 10:
        monthWord = "Oct";
        break;
      case 11:
        monthWord = "Nov";
        break;
      case 12:
        monthWord = "Dec";
        break;
    }

    switch (dt.weekday) {
      case 1:
        dayWord = "Mon";
        break;
      case 2:
        dayWord = "Tue";
        break;
      case 3:
        dayWord = "Wed";
        break;
      case 4:
        dayWord = "Thu";
        break;
      case 5:
        dayWord = "Fri";
        break;
      case 6:
        dayWord = "Sat";
        break;
      case 7:
        dayWord = "Sun";
        break;
    }

    return dayWord + ' ' + monthWord + ' ' + dt.day.toString();
}

Android Studio 表示,“不可为 null 的局部变量 'dayWord' 必须先赋值才能使用。”

我理解错误并发现我可以像这样简单地修改方法的前两行:

String monthWord = "error!";
String dayWord = "error!";

这样,我就满足了语言规则,如果我们达到了变量没有被赋值的不可能的情况,那就很明显了。

虽然这看起来很老套......所以在这些类型的场景中,将此代码转换为空安全的优雅和正确的方法是什么?如果有多种方法,那么优缺点是什么?

谢谢!

一般来说,您有以下几种选择:

1。将变量初始化为一些非空标记值和 assert 之后:

String monthWord = '';
// ...
switch (dt.month) {
  // ...
}
assert(monthWord.isNotEmpty);

如果您忽略在 switch.

中处理它的情况,这将导致调试版本在运行时抛出 AssertionError

2。使变量可为空并使用空断言运算符:

String? monthWord;
// ...
switch (dt.month) {
  // ...
}
monthWord!;

// Since `monthWord` is a local variable, it will now be promoted to a
// non-nullable `String` type.

如果您忽略将变量设置为非空值,这将在所有构建类型中引发 TypeError

3。使变量 late

将变量声明为 late 表示您承诺变量将在读取之前进行初始化。编译器将生成运行时检查,以验证在您尝试访问变量时变量是否已初始化。如果您忽略设置变量,这将在所有构建类型中抛出 LateInitializationError

4。添加一个抛出

default 案例

如果您的所有 case 都设置了局部变量,则添加抛出的 default 情况允许编译器推断如果 [=16= 之后的代码必须始终设置该变量] 语句到达:

String monthWord; // No explicit initialization required!
// ...
switch (dt.month) {
  case 1:
    monthWord = "Jan";
    break;

  // ... etc. ...

  default:
    throw AssertionError('Unhandled case: ${dt.month}');  
}

// The compiler now can deduce that `monthWord` is guaranteed to be
// initialized.

(请注意,如果您在 enum 类型。对于 enums,编译器和分析器可以确定您的 cases 是否详尽无遗,如果您不小心遗漏任何情况,将生成分析警告。)


至于使用哪种方法,主要是个人喜好问题。它们在大多数情况下都是等价的,因为它们会导致运行时错误。我个人会选择 #1 (assert) 或 #4 (default case) 以避免在发布版本中进行不必要的检查。

在您的特定示例中,我也将分别使用 DateTime.monthDateTime.day 作为 List 月份和日期名称的索引:

const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];

assert(months.length == 12);
assert(days.length == 7);

var monthWord = months[dt.month - 1];
var dayWord = days [dt.day - 1];