內容

修復常見的型別問題

如果您在型別檢查方面遇到問題,此頁面可以提供幫助。要了解更多資訊,請閱讀有關 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

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

示例

#

以下程式碼使用幾個(String, int)對初始化一個對映。分析器推斷該對映的型別為<String, int>,但程式碼似乎假設為<String, dynamic><String, num>。當代碼新增(String, double)對時,分析器會報錯

✗ 靜態分析:失敗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);

衝突的超介面

#

一個implements多個超介面的類必須能夠為每個超介面的每個成員實現有效的覆蓋。每個具有給定名稱的成員都需要跨超介面具有相容的簽名。

超介面不得包含衝突的泛型。一個類不能同時實現C<A>C<B>,包括間接超介面。

示例

#

在以下程式碼中,類C具有衝突的泛型介面。某些成員的有效覆蓋的定義將是不可能的。

✗ 靜態分析:失敗dart
abstract class C implements List<int>, Iterable<num> {}

修復:使用一致的泛型或避免重複傳遞介面

#
✔ 靜態分析:成功dart
abstract class C implements List<int> {}

執行時錯誤

#

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

無效的強制轉換

#

為了確保型別安全,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的示例

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

class Mouse extends Animal { ... }

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

儘管此示例顯示了在子型別中使用covariant,但covariant關鍵字可以放置在超類或子類方法中。通常,超類方法是放置它的最佳位置。covariant關鍵字適用於單個引數,並且也支援設定器和欄位。