Dartの型システム
Dart言語は型安全です。静的型チェックとランタイムチェックを組み合わせて、変数の値が常に変数の静的型と一致するようにします。これは、健全な型付けと呼ばれることもあります。型は必須ですが、型推論により、型の注釈はオプションです。
静的型チェックの利点の1つは、Dartの静的アナライザーを使用してコンパイル時にバグを見つけることができることです。
ほとんどの静的解析エラーは、ジェネリッククラスに型注釈を追加することで修正できます。最も一般的なジェネリッククラスは、コレクション型`List
たとえば、次のコードでは、`printInts()`関数は整数リストを出力し、`main()`はリストを作成して`printInts()`に渡します。
void printInts(List<int> a) => print(a);
void main() {
final list = [];
list.add(1);
list.add('2');
printInts(list);
}
上記のコードは、`printInts(list)`の呼び出し時に`list`(上記で強調表示)で型エラーが発生します
error - The argument type 'List<dynamic>' can't be assigned to the parameter type 'List<int>'. - argument_type_not_assignable
エラーは、`List
リストの作成時に型注釈(`int`)を追加すると(下記で強調表示)、アナライザーは、文字列引数を`int`パラメーターに割り当てることができないと不満を述べます。`list.add('2')`の引用符を削除すると、静的解析に合格し、エラーや警告なしに実行されるコードになります。
void printInts(List<int> a) => print(a);
void main() {
final list = <int>[];
list.add(1);
list.add(2);
printInts(list);
}
健全性とは?
#健全性とは、プログラムが特定の無効な状態にならないようにすることです。健全な型システムとは、式の静的型と一致しない値に式が評価される状態になることがないことを意味します。たとえば、式の静的型が`String`の場合、実行時に評価すると文字列のみが取得されることが保証されます。
Dartの型システムは、JavaやC#の型システムと同様に、健全です。静的チェック(コンパイル時エラー)とランタイムチェックを組み合わせて健全性を強制します。たとえば、`String`を`int`に割り当てることは、コンパイル時エラーです。オブジェクトを`as String`を使用して`String`にキャストすると、オブジェクトが`String`でない場合はランタイムエラーで失敗します。
健全性のメリット
#健全な型システムにはいくつかの利点があります
コンパイル時に型関連のバグを明らかにする。
健全な型システムは、コードが型について明確になることを強制するため、実行時に見つけにくい可能性のある型関連のバグがコンパイル時に明らかになります。より読みやすいコード。
値が実際に指定された型を持っていることを信頼できるため、コードが読みやすくなります。健全なDartでは、型は嘘をつくことはできません。より保守しやすいコード。
健全な型システムを使用すると、コードの一部を変更すると、型システムは破損した他のコードの一部について警告できます。より優れた先行(AOT)コンパイル。
AOTコンパイルは型なしでも可能ですが、生成されるコードの効率ははるかに低くなります。
静的解析をパスするためのヒント
#静的型のほとんどのルールは理解しやすいです。あまり明らかではないルールを以下に示します。
- メソッドをオーバーライドするときは、健全な戻り値の型を使用する。
- メソッドをオーバーライドするときは、健全なパラメーターの型を使用する。
- dynamicリストを型付きリストとして使用しない。
次の型階層を使用する例とともに、これらのルールを詳しく見ていきましょう。

メソッドをオーバーライドするときは、健全な戻り値の型を使用する
#サブクラスのメソッドの戻り値の型は、スーパークラスのメソッドの戻り値の型と同じ型またはサブタイプである必要があります。`Animal`クラスのgetterメソッドを考えてみましょう。
class Animal {
void chase(Animal a) { ... }
Animal get parent => ...
}
`parent` getterメソッドは`Animal`を返します。`HoneyBadger`サブクラスでは、getterの戻り値の型を`HoneyBadger`(または`Animal`の他のサブタイプ)に置き換えることができますが、無関係の型は許可されていません。
class HoneyBadger extends Animal {
@override
void chase(Animal a) { ... }
@override
HoneyBadger get parent => ...
}
class HoneyBadger extends Animal {
@override
void chase(Animal a) { ... }
@override
Root get parent => ...
}
メソッドをオーバーライドするときは、健全なパラメーターの型を使用する
#オーバーライドされたメソッドのパラメーターは、スーパークラスの対応するパラメーターと同じ型またはスーパータイプである必要があります。元のパラメーターのサブタイプで型を置き換えることにより、パラメーターの型を「厳密」にしないでください。
`Animal`クラスの`chase(Animal)`メソッドを考えてみましょう。
class Animal {
void chase(Animal a) { ... }
Animal get parent => ...
}
`chase()`メソッドは`Animal`を受け取ります。`HoneyBadger`は何でも追いかけます。`chase()`メソッドをオーバーライドして、何でも(`Object`)を受け取ることができるのはOKです。
class HoneyBadger extends Animal {
@override
void chase(Object a) { ... }
@override
Animal get parent => ...
}
次のコードでは、`chase()`メソッドのパラメーターが`Animal`から`Animal`のサブクラスである`Mouse`に厳密化されています。
class Mouse extends Animal { ... }
class Cat extends Animal {
@override
void chase(Mouse a) { ... }
}
このコードは、猫を定義してワニを追いかけることができるようになるため、型安全ではありません。
Animal a = Cat();
a.chase(Alligator()); // Not type safe or feline safe.
dynamicリストを型付きリストとして使用しない
#`dynamic`リストは、さまざまな種類のものをリストに含めたい場合に適しています。ただし、`dynamic`リストを型付きリストとして使用することはできません。
このルールは、ジェネリック型のインスタンスにも適用されます。
次のコードでは、`Dog`の`dynamic`リストを作成し、それを`Cat`型のリストに割り当てますが、これは静的解析中にエラーを生成します。
void main() {
List<Cat> foo = <dynamic>[Dog()]; // Error
List<dynamic> bar = <dynamic>[Dog(), Cat()]; // OK
}
ランタイムチェック
#ランタイムチェックは、コンパイル時に検出できない型安全性の問題に対処します。
たとえば、次のコードは、犬のリストを猫のリストにキャストするとエラーになるため、実行時に例外をスローします。
void main() {
List<Animal> animals = <Dog>[Dog()];
List<Cat> cats = animals as List<Cat>;
}
型推論
#アナライザーは、フィールド、メソッド、ローカル変数、およびほとんどのジェネリック型引数の型を推論できます。アナライザーが特定の型を推論するのに十分な情報を持っていない場合は、dynamic
型を使用します。
以下は、ジェネリックスを使用した型推論の例です。この例では、arguments
という名前の変数は、文字列キーとさまざまな型の値のペアを保持するマップを保持します。
変数の型を明示的に指定する場合、次のように記述できます。
Map<String, dynamic> arguments = {'argA': 'hello', 'argB': 42};
あるいは、var
またはfinal
を使用して、Dartに型を推論させることもできます。
var arguments = {'argA': 'hello', 'argB': 42}; // Map<String, Object>
マップリテラルは、そのエントリから型を推論し、次に変数はマップリテラルの型から型を推論します。このマップでは、キーは両方とも文字列ですが、値は異なる型(String
とint
。上限はObject
)を持ちます。したがって、マップリテラルはMap<String, Object>
型を持ち、arguments
変数も同様です。
フィールドとメソッドの推論
#型が指定されておらず、スーパークラスのフィールドまたはメソッドをオーバーライドするフィールドまたはメソッドは、スーパークラスのメソッドまたはフィールドの型を継承します。
宣言された型も継承された型も持たないが、初期値で宣言されたフィールドは、初期値に基づいて推論された型を取得します。
静的フィールドの推論
#静的フィールドと変数は、初期化子から型を推論します。推論がサイクルを検出した場合(つまり、変数の型を推論することがその変数の型を知ることに依存する場合)、推論は失敗することに注意してください。
ローカル変数の推論
#ローカル変数の型は、もしあれば初期化子から推論されます。後続の代入は考慮されません。これは、推論された型が過度に正確である可能性があることを意味します。その場合は、型アノテーションを追加できます。
var x = 3; // x is inferred as an int.
x = 4.0;
num y = 3; // A num can be double or int.
y = 4.0;
型引数の推論
#コンストラクター呼び出しおよびジェネリックメソッド呼び出しへの型引数は、発生コンテキストからの下方情報と、コンストラクターまたはジェネリックメソッドへの引数からの上方情報の組み合わせに基づいて推論されます。推論が期待どおりに機能しない場合は、いつでも型引数を明示的に指定できます。
// Inferred as if you wrote <int>[].
List<int> listOfInt = [];
// Inferred as if you wrote <double>[3.0].
var listOfDouble = [3.0];
// Inferred as Iterable<int>.
var ints = listOfDouble.map((x) => x.toInt());
最後の例では、x
は下方情報を使用してdouble
として推論されます。クロージャの戻り値の型は、上方情報を使用してint
として推論されます。Dartは、この戻り値の型を、map()
メソッドの型引数<int>
を推論するときに上方情報として使用します。
型の置換
#メソッドをオーバーライドするときは、ある型(古いメソッド内)のものを、新しい型(新しいメソッド内)を持つ可能性のあるもので置き換えています。同様に、関数に引数を渡すとき、ある型(宣言された型を持つパラメータ)を持つものを、別の型(実際の引数)を持つもので置き換えています。ある型を持つものを、サブタイプまたはスーパータイプを持つもので置き換えることができるのはいつですか?
型を置換するときは、コンシューマーとプロデューサーという観点で考えると役立ちます。コンシューマーは型を吸収し、プロデューサーは型を生成します。
コンシューマーの型をスーパータイプで、プロデューサーの型をサブタイプで置き換えることができます。
単純な型代入とジェネリック型を使用した代入の例を見てみましょう。
単純な型代入
#オブジェクトをオブジェクトに代入するとき、いつ型を別の型に置き換えることができますか?答えは、オブジェクトがコンシューマーかプロデューサーかによって異なります。
次の型階層を考えてみましょう。

Cat c
がコンシューマーであり、Cat()
がプロデューサーである次の単純な代入を考えてみましょう。
Cat c = Cat();
消費する位置では、特定の型(Cat
)を消費するものを、あらゆるもの(Animal
)を消費するものに置き換えても安全です。そのため、Cat c
をAnimal c
に置き換えることは許可されます。なぜなら、Animal
はCat
のスーパータイプだからです。
Animal c = Cat();
しかし、Cat c
をMaineCoon c
に置き換えることは、型安全性を破ります。なぜなら、スーパークラスは、Lion
など、異なる動作を持つCat型を提供する可能性があるからです。
MaineCoon c = Cat();
生成する位置では、ある型(Cat
)を生成するものを、より具体的な型(MaineCoon
)で置き換えることは安全です。したがって、次は許可されます。
Cat c = MaineCoon();
ジェネリック型代入
#ジェネリック型でもルールは同じですか?はい。動物のリストの階層を考えてみましょう。Cat
のList
はAnimal
のList
のサブタイプであり、MaineCoon
のList
のスーパータイプです。

次の例では、List<MaineCoon>
がList<Cat>
のサブタイプであるため、MaineCoon
リストをmyCats
に代入できます。
List<MaineCoon> myMaineCoons = ...
List<Cat> myCats = myMaineCoons;
反対方向はどうですか?Animal
リストをList<Cat>
に代入できますか?
List<Animal> myAnimals = ...
List<Cat> myCats = myAnimals;
この代入は、Animal
などの非dynamic
型からの暗黙的なダウンキャストを作成するため、静的解析に合格しません。
このタイプのコードを静的解析に通すには、明示的なキャストを使用できます。
List<Animal> myAnimals = ...
List<Cat> myCats = myAnimals as List<Cat>;
ただし、明示的なキャストは、キャストされるリストの実際の型(myAnimals
)によっては、ランタイムで失敗する可能性があります。
メソッド
#メソッドをオーバーライドするときも、プロデューサーとコンシューマーのルールが適用されます。例えば

コンシューマー(chase(Animal)
メソッドなど)の場合、パラメータの型をスーパータイプで置き換えることができます。プロデューサー(parent
ゲッターメソッドなど)の場合、戻り値の型をサブタイプで置き換えることができます。
詳細については、メソッドをオーバーライドするときは適切な戻り値の型を使用するとメソッドをオーバーライドするときは適切なパラメーター型を使用するを参照してください。
その他のリソース
#次のリソースには、健全なDartに関する詳細情報が記載されています。
- 一般的な型問題の修正 - 健全なDartコードを作成するときに発生する可能性のあるエラーと、その修正方法。
- 型プロモーションの失敗の修正 - 型プロモーションエラーを理解し、修正する方法を学習します。
- 健全なnull安全 - 健全なnull安全でコードを作成する方法を学習します。
- 静的分析のカスタマイズ - 分析オプションファイルを使用して、アナライザーとリンターを設定およびカスタマイズする方法。
特に明記されていない限り、このサイトのドキュメントはDart 3.5.3を反映しています。ページは2024-02-21に最終更新されました。 ソースを表示または問題を報告。