修正常見的型別問題
如果您在型別檢查方面遇到問題,此頁面可以提供協助。若要深入了解,請閱讀關於Dart 的型別系統,並參閱其他資源。
疑難排解
#Dart 強制執行健全的型別系統。這表示您無法撰寫變數的值與其靜態型別不同的程式碼。具有 int
型別的變數無法儲存小數點後的數字。Dart 會在編譯時期和執行階段檢查變數值是否符合其型別。
您不可能遇到變數中儲存的值與變數的靜態型別不同的情況。與大多數現代靜態型別語言一樣,Dart 會結合靜態(編譯時期)和動態(執行階段)檢查來完成此操作。
例如,以下型別錯誤會在編譯時期偵測到
List<int> numbers = [1, 2, 3];
List<String> string = numbers;
由於 List<int>
和 List<String>
都不是彼此的子型別,因此 Dart 會靜態地排除此情況。
您可以在以下章節中看到其他靜態分析錯誤的範例,以及其他錯誤型別。
沒有型別錯誤
#如果您沒有看到預期的錯誤或警告,請確認您使用的是最新版本的 Dart,並且已正確設定您的IDE 或編輯器。
您也可以使用 dart analyze
命令,在命令列上對程式執行分析。
若要驗證分析是否如預期般運作,請嘗試將以下程式碼新增至 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
未定義
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 推斷 canvas
為 Element
。
您可以使用明確的向下轉換來修正此錯誤
var canvas = querySelector('canvas') as CanvasElement;
canvas.context2D.lineTo(x, y);
否則,在無法使用單一型別的情況下,請使用 dynamic
dynamic canvasOrImg = querySelector('canvas, img');
var width = canvasOrImg.width;
範例 2:省略的型別參數會預設為其型別界限
#考慮以下具有延伸 Iterable
的有界限型別參數的泛型類別
class C<T extends Iterable> {
final T collection;
C(this.collection);
}
以下程式碼會建立此類別的新實例 (省略型別引數) 並存取其 collection
成員
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
作為型別引數,分析器可以偵測建構子引數中的型別不符。透過提供適當型別的建構子引數來修正錯誤,例如列表常值
var c = C<List>([]).collection;
c.add(2);
無效的方法覆寫
#error - '...' isn't a valid override of '...' - invalid_override
當子類別透過指定原始類別的子類別來收緊方法的參數型別時,通常會發生這些錯誤。
範例
#在以下範例中,add()
方法的參數型別為 int
,這是 num
的子型別,而 num
是父類別中使用的參數型別。
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
的情境
NumberAdder adder = MyAdder();
adder.add(1.2, 3.4);
如果允許覆寫,程式碼會在執行階段引發錯誤。
修正:擴展方法的參數型別
#子類別的方法應接受父類別的方法所接受的每個物件。
透過擴展子類別中的型別來修正此範例
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)
上產生無效的覆寫錯誤。
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
型別。這很可能會導致錯誤。
您可以透過在子類別上指定型別來修正此範例
class Superclass<T> {
void method(T param) { ... }
}
class Subclass extends Superclass<int> {
@override
void method(int param) { ... }
}
請考慮使用分析器在嚴格原始型別模式下執行,以確保您的程式碼指定泛型型別引數。以下是在專案的 analysis_options.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
) 配對的映射 (map)。分析器會推斷該映射的類型為 <String, int>
,但程式碼似乎假設為 <String, dynamic>
或 <String, num>
。當程式碼加入一個 (String
, double
) 配對時,分析器會發出警告。
// 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>
來修正。
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()
呼叫不是建構子初始化列表中的最後一個項目時,就會發生此錯誤。
範例
#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()
呼叫來修正此錯誤
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?
)會導致編譯時期錯誤。
範例
#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 進行轉換
#在可能的情況下,透過加入類型參數來避免此錯誤
void filterValues<T>(bool Function(T) filter) {}
filterValues<String>((x) => x.contains('Hello'));
否則請使用轉換
void filterValues(bool Function(dynamic) filter) {}
filterValues((x) => (x as String).contains('Hello'));
不正確的型別推論
#在極少數情況下,Dart 的類型推斷可能會為泛型建構子調用中的函式字面值參數推斷出錯誤的類型。這主要影響 Iterable.fold
。
範例
#在以下程式碼中,類型推斷會推斷出 a
的類型為 Null
var ints = [1, 2, 3];
var maximumOrNull = ints.fold(null, (a, b) => a == null || a < b ? b : a);
修正方式:提供適當的類型作為明確的類型引數
#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
具有衝突的泛型介面。某些成員的有效覆寫定義是不可能的。
abstract class C implements List<int>, Iterable<num> {}
修正方式:使用一致的泛型或避免重複的傳遞介面
#abstract class C implements List<int> {}
執行階段錯誤
#本節討論的錯誤會在 執行階段 報告。
無效的轉換
#為了確保類型安全,Dart 在某些情況下需要插入執行階段檢查。請考慮以下 assumeStrings
方法
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>
,那麼轉換就會成功。
否則,轉換將在執行階段失敗
assumeStrings(<int>[1, 2, 3]);
Exception: type 'List<int>' is not a subtype of type 'List<String>'
修正方式:收緊或修正類型
#有時,缺少類型,尤其是在空的集合中,意味著會建立 <dynamic>
集合,而不是您預期的類型化集合。加入明確的類型引數可以提供幫助
var list = <String>[];
list.add('a string');
list.add('another');
assumeStrings(list);
您也可以更精確地為局部變數輸入類型,並讓推斷提供幫助
List<String> list = [];
list.add('a string');
list.add('another');
assumeStrings(list);
在您使用不建立的集合(例如來自 JSON 或外部資料來源)的情況下,您可以使用 Iterable
實作(例如 List
)提供的 cast() 方法。
以下是一個首選解決方案的範例:收緊物件的類型。
Map<String, dynamic> json = fetchFromExternalSource();
var names = json['names'] as List;
assumeStrings(names.cast<String>());
附錄
#covariant 關鍵字
#一些(很少使用的)編碼模式依賴於透過使用子類型覆寫參數類型來收緊類型,這是無效的。在這種情況下,您可以使用 covariant
關鍵字來告訴分析器您是有意這樣做的。這會移除靜態錯誤,而是在執行階段檢查無效的引數類型。
以下顯示如何使用 covariant
class Animal {
void chase(Animal x) { ... }
}
class Mouse extends Animal { ... }
class Cat extends Animal {
@override
void chase(covariant Mouse x) { ... }
}
雖然這個範例顯示在子類型中使用 covariant
,但 covariant
關鍵字可以放置在超類別或子類別方法中。通常超類別方法是放置它的最佳位置。 covariant
關鍵字適用於單個參數,也支援設定器和欄位。
除非另有說明,否則本網站上的文件反映了 Dart 3.6.0。頁面最後更新於 2024-12-16。 檢視原始碼 或 回報問題。