內容

修正常見的型別問題

如果您在類型檢查方面遇到問題,此頁面可以提供協助。如需深入了解,請閱讀有關 Dart 類型系統 的資訊,並參閱 這些其他資源

疑難排解

#

Dart 採用嚴謹的類型系統。這表示您無法編寫變數值與其靜態類型不同的程式碼。具有 int 類型的變數無法儲存帶有小數點的數字。Dart 會在 編譯時期執行時期 檢查變數值是否與其類型相符。

您無法遇到儲存在變數中的值與變數的靜態類型不同的情況。與大多數現代靜態類型語言一樣,Dart 會透過結合 靜態(編譯時期)動態(執行時期) 檢查來達成此目的。

例如,下列類型錯誤會在編譯時期偵測到

✗ 靜態分析:失敗dart
List<int> numbers = [1, 2, 3];
List<String> string = numbers;

由於 List<int>List<String> 都不是彼此的子類型,因此 Dart 會在靜態時排除此情況。

您可以在下列各節中看到其他靜態分析錯誤範例,以及其他錯誤類型。

無型別錯誤

#

如果您未看到預期的錯誤或警告,請確定您使用的是最新版本的 Dart,而且您已正確設定 IDE 或編輯器

您也可以使用 dart analyze 指令,透過命令列對您的程式執行分析。

若要驗證分析是否按預期運作,請嘗試將下列程式碼新增到 Dart 檔案中。

✗ 靜態分析:失敗dart
bool b = [0][0];

如果設定正確,分析器會產生下列錯誤

error - A value of type 'int' can't be assigned to a variable of type 'bool'. Try changing the type of the variable, or casting the right-hand type to 'bool'. - invalid_assignment

靜態錯誤和警告

#

此節說明如何修正您可能從分析器或 IDE 中看到的某些錯誤和警告。

靜態分析無法捕捉所有錯誤。如需協助修正僅出現在執行時期的錯誤,請參閱 執行時期錯誤

未定義的成員

#
error - The <member> '...' isn't defined for the type '...' - undefined_<member>

這些錯誤可能會在下列情況下出現

  • 變數在靜態上已知為某個超類別,但程式碼假設為子類別。
  • 泛型類別具有受約束的類型參數,但類別的實例建立表達式省略了類型參數。

範例 1:變數在靜態上已知為某個超類別,但程式碼假設為子類別

#

在下列程式碼中,分析器抱怨 context2D 未定義

✗ 靜態分析:失敗dart
var canvas = querySelector('canvas')!;
canvas.context2D.lineTo(x, y);
error - The getter 'context2D' isn't defined for the type 'Element'. Try importing the library that defines 'context2D', correcting the name to the name of an existing getter, or defining a getter or field named 'context2D'. - undefined_getter

修正:以明確的類型宣告或向下轉型取代成員的定義

#

querySelector() 的傳回類型為 Element?! 轉換為 Element),但程式碼假設它是子類別 CanvasElement(定義了 context2D)。canvas 欄位宣告為 var,這允許 Dart 推斷 canvasElement

您可以使用明確的向下轉型修正此錯誤

✔ 靜態分析:成功dart
var canvas = querySelector('canvas') as CanvasElement;
canvas.context2D.lineTo(x, y);

否則,在無法使用單一類型的情況下使用 dynamic

✔ 靜態分析:成功dart
dynamic canvasOrImg = querySelector('canvas, img');
var width = canvasOrImg.width;

範例 2:省略的類型參數預設為其類型約束

#

考量下列具有延伸 Iterable受約束類型參數泛型類別

dart
class C<T extends Iterable> {
  final T collection;
  C(this.collection);
}

下列程式碼建立此類別的新實例(省略類型參數)並存取其 collection 成員

✗ 靜態分析:失敗dart
var c = C(Iterable.empty()).collection;
c.add(2);
error - The method 'add' isn't defined for the type 'Iterable'. Try correcting the name to the name of an existing method, or defining a method named 'add'. - undefined_method

雖然 List 類型具有 add() 方法,但 Iterable 沒有。

修正:指定類型參數或修正下游錯誤

#

當泛型類別在沒有明確類型參數的情況下實例化時,如果明確給定,每個類型參數預設為其類型約束(此範例中為 Iterable),否則預設為 dynamic

您需要逐案處理修正此類錯誤。深入了解原始設計意圖有所幫助。

明確傳遞類型參數是協助識別類型錯誤的有效方式。例如,如果您將程式碼變更為指定 List 作為類型參數,分析器就能偵測建構函數參數中的類型不符。透過提供適當類型的建構函數參數(例如清單文字)來修正錯誤

✔ 靜態分析:成功dart
var c = C<List>([]).collection;
c.add(2);

無效的方法覆寫

#
error - '...'  isn't a valid override of '...' - invalid_override

當子類別透過指定原始類別的子類別來強化方法的參數類型時,通常會發生這些錯誤。

範例

#

在以下範例中,add() 方法的參數為 int 類型,為 num 的子類型,而 num 是父類別中使用的參數類型。

✗ 靜態分析:失敗dart
abstract class NumberAdder {
  num add(num a, num b);
}

class MyAdder extends NumberAdder {
  @override
  num add(int a, int b) => a + b;
}
error - 'MyAdder.add' ('num Function(int, int)') isn't a valid override of 'NumberAdder.add' ('num Function(num, num)'). - invalid_override

考量以下將浮點數值傳遞至 MyAdder 的情境

✗ 執行時間:失敗dart
NumberAdder adder = MyAdder();
adder.add(1.2, 3.4);

如果允許覆寫,程式碼會在執行時間產生錯誤。

修正:擴充方法的參數類型

#

子類別的方法應接受父類別的方法所接受的每個物件。

透過擴充子類別中的類型來修正範例

✔ 靜態分析:成功dart
abstract class NumberAdder {
  num add(num a, num b);
}

class MyAdder extends NumberAdder {
  @override
  num add(num a, num b) => a + b;
}

如需更多資訊,請參閱 覆寫方法時使用適當的輸入參數類型


缺少型別參數

#
error - '...'  isn't a valid override of '...' - invalid_override

範例

#

在以下範例中,Subclass 延伸 Superclass<T> 但未指定類型引數。分析器推論出 Subclass<dynamic>,這會導致 method(int) 發生無效的覆寫錯誤。

✗ 靜態分析:失敗dart
class Superclass<T> {
  void method(T param) { ... }
}

class Subclass extends Superclass {
  @override
  void method(int param) { ... }
}
error - 'Subclass.method' ('void Function(int)') isn't a valid override of 'Superclass.method' ('void Function(dynamic)'). - invalid_override

修正:為泛型子類別指定類型引數

#

當泛型子類別忽略指定類型引數時,分析器會推論出 dynamic 類型。這可能會導致錯誤。

您可以透過在子類別上指定類型來修正範例

✔ 靜態分析:成功dart
class Superclass<T> {
  void method(T param) { ... }
}

class Subclass extends Superclass<int> {
  @override
  void method(int param) { ... }
}

考量在 嚴格原始類型 模式中使用分析器,這可確保您的程式碼指定泛型類型引數。以下是針對專案的 analysis_options.yaml 檔案啟用嚴格原始類型的範例

yaml
analyzer:
  language:
    strict-raw-types: true

如需進一步了解如何自訂分析器的行為,請參閱 自訂靜態分析


集合元素型別意外

#
error - A value of type '...' can't be assigned to a variable of type '...' - invalid_assignment

當您建立一個簡單的動態集合,而分析器以您未預期的方式推論類型時,有時會發生這種情況。當您稍後新增不同類型的值時,分析器會回報一個問題。

範例

#

以下程式碼使用多個 (Stringint) 成對初始化一個映射。分析器推論該映射為 <String, int> 類型,但程式碼似乎假設為 <String, dynamic><String, num>。當程式碼新增一個 (Stringdouble) 成對時,分析器會抱怨

✗ 靜態分析:失敗dart
// Inferred as Map<String, int>
var map = {'a': 1, 'b': 2, 'c': 3};
map['d'] = 1.5;
error - A value of type 'double' can't be assigned to a variable of type 'int'. Try changing the type of the variable, or casting the right-hand type to 'int'. - invalid_assignment

修正:明確指定類型

#

範例可以透過明確定義地圖的類型為 <String, num> 來修正。

✔ 靜態分析:成功dart
var map = <String, num>{'a': 1, 'b': 2, 'c': 3};
map['d'] = 1.5;

或者,如果你希望這個地圖接受任何值,請將類型指定為 <String, dynamic>


建構函式初始化清單 super() 呼叫

#
error - The superconstructor call must be last in an initializer list: '...'. - super_invocation_not_last

當建構函式的初始化清單中 super() 呼叫不是最後一個時,就會發生這個錯誤。

範例

#
✗ 靜態分析:失敗dart
HoneyBadger(Eats food, String name)
    : super(food),
      _name = name { ... }
error - The superconstructor call must be last in an initializer list: 'Animal'. - super_invocation_not_last

修正:將 super() 呼叫放在最後

#

如果編譯器依賴於 super() 呼叫出現在最後,它可以產生更簡單的程式碼。

透過移動 super() 呼叫來修正這個錯誤

✔ 靜態分析:成功dart
HoneyBadger(Eats food, String name)
    : _name = name,
      super(food) { ... }

參數型別 ... 無法指定給參數型別 ...

#
error - The argument type '...' can't be assigned to the parameter type '...'. - argument_type_not_assignable

在 Dart 1.x 中,dynamic 既是 頂層類型(所有類型的超類型)也是 底層類型(所有類型的子類型),這取決於內容。這表示可以將函式(其參數類型為 String)指定給預期函式類型(其參數為 dynamic)的地方。

然而,在 Dart 2 中,使用 dynamic(或其他 頂層 類型,例如 Object?)以外的參數類型會導致編譯時期錯誤。

範例

#
✗ 靜態分析:失敗dart
void filterValues(bool Function(dynamic) filter) {}
filterValues((String x) => x.contains('Hello'));
error - The argument type 'bool Function(String)' can't be assigned to the parameter type 'bool Function(dynamic)'. - argument_type_not_assignable

修正:新增類型參數 明確從 dynamic 轉型

#

如果可能,請透過新增類型參數來避免這個錯誤

✔ 靜態分析:成功dart
void filterValues<T>(bool Function(T) filter) {}
filterValues<String>((x) => x.contains('Hello'));

否則使用轉型

✔ 靜態分析:成功dart
void filterValues(bool Function(dynamic) filter) {}
filterValues((x) => (x as String).contains('Hello'));

型別推論不正確

#

在少數情況下,Dart 的類型推論可能會推論出泛型建構函式呼叫中函式文字引數錯誤的類型。這主要會影響 Iterable.fold

範例

#

在以下程式碼中,類型推論會推論出 a 的類型為 Null

✗ 靜態分析:失敗dart
var ints = [1, 2, 3];
var maximumOrNull = ints.fold(null, (a, b) => a == null || a < b ? b : a);

修正:提供適當的類型作為明確的類型引數

#
✔ 靜態分析:成功dart
var ints = [1, 2, 3];
var maximumOrNull =
    ints.fold<int?>(null, (a, b) => a == null || a < b ? b : a);

執行時期錯誤

#

本節討論的錯誤會在 執行時期 報告。

無效的轉型

#

為了確保類型安全,Dart 需要在某些情況下插入 執行時期 檢查。考慮以下的 assumeStrings 方法

✔ 靜態分析:成功dart
void assumeStrings(dynamic objects) {
  List<String> strings = objects; // Runtime downcast check
  String string = strings[0]; // Expect a String value
}

strings 的指定是將 dynamic 向下轉型List<String>(就像你寫了 as List<String> 一樣),因此如果你在執行時期傳入 objects 的值是 List<String>,則轉型會成功。

否則,轉型會在執行時期失敗

✗ 執行時間:失敗dart
assumeStrings(<int>[1, 2, 3]);
Exception: type 'List<int>' is not a subtype of type 'List<String>'

修正:收緊或修正類型

#

有時,缺乏類型,特別是對於空的集合,表示會建立 <dynamic> 集合,而不是你預期的類型化集合。新增明確的類型引數可以有所幫助

runtime-successdart
var list = <String>[];
list.add('a string');
list.add('another');
assumeStrings(list);

你也可以更精確地輸入局部變數,並讓推論提供協助

runtime-successdart
List<String> list = [];
list.add('a string');
list.add('another');
assumeStrings(list);

在您處理未建立的集合(例如來自 JSON 或外部資料來源)時,可以使用 Iterable 實作(例如 List)提供的 cast() 方法。

以下是建議解決方案的範例:收緊物件的型別。

runtime-successdart
Map<String, dynamic> json = fetchFromExternalSource();
var names = json['names'] as List;
assumeStrings(names.cast<String>());

附錄

#

covariant 關鍵字

#

有些(很少使用)的編碼模式仰賴透過以子型別覆寫參數的型別來收緊型別,這是不正確的。在這種情況下,您可以使用 covariant 關鍵字告訴分析器您是故意這樣做的。這會移除靜態錯誤,並在執行階段檢查無效的引數型別。

以下顯示如何使用 covariant

✔ 靜態分析:成功dart
class Animal {
  void chase(Animal x) { ... }
}

class Mouse extends Animal { ... }

class Cat extends Animal {
  @override
  void chase(covariant Mouse x) { ... }
}

雖然這個範例顯示在子型別中使用 covariant,但 covariant 關鍵字可以放在超類別或子類別方法中。通常,超類別方法是放置它的最佳位置。covariant 關鍵字套用於單一參數,也支援在設定值和欄位中使用。