【Flutter/Dart】TやS/E/Kとは?Generics(総称型)基礎
よくMapやSetに<T>や<S>がつく場合があります。これらはGenerics(総称型)といい予め格納する値の型を宣言(予約)しておくものです。
このページでは、Generics(総称型)の基礎について調べまとめてみました。
目次
参考
- 【参考】公式サイト
- Generics(総称型)とは
- 利用目的
- 特定の型を記載せず総称型を仮置きし、後で決定する
- メリット
- 重複したコードを無くせる
- 違う型の変数に代入すると、コンパイルエラーで教えてくれる
- 利用目的
- 実行環境
- DartPadやAndroid Studio等で実行
Genericsとは
基本的な配列の型であるListのAPIドキュメンテーションを見ると、そこにはその型は実際はList<E>であることが判る。<…>記述はListは総称(或いはパラメタ化された)型、即ち正式の型パラメタたちを持っている型であることを示す。総称型の殆どの型パラメタは慣行的に E, T, S, K, 及び Vといった1文字の名前を持っている。
https://www.cresc.co.jp/tech/java/Google_Dart2/language/generics/generics.html
Genericsの効果
Genericsを使うメリットには
- 簡単に安全なコードを書ける
- 重複したコードを減らすことができる
です。
サンプル
サンプル①
void main(){ var names = Map<String>();//GenericsにString型を指定 names.addAll(['Seth', 'Kathy', 'Lars']);//値を格納 names.add(42); // int型が追加されようとしているためエラー }
解説
このサンプルではエラーが起きます。MapクラスにジェネリクスでStringを指定したのにint型を追加しようとしているためです。
サンプル②
class User<String> { final String Name;//型指定 User(this.Name); } void main() { final user = User('Tanaka');//String型に文字列を代入 print(user.Name); }
解説
クラス・変数をString型に指定し、コンストラクタを使い生成したuserクラスにTanakaというStringのプロパティを追加しています。
スタッフブログ
STAFFBLOG
アプリ開発日誌
2021.09.30
Flutter開発のためのDart入門(7)クラス②
前回の投稿に続き、今回もDartについて解説していきます。
引き続きクラスについて解説します。抽象クラス
抽象クラスとは継承されることを前提として、処理内容を記述しないメソッド(抽象メソッド)を含むクラスです。
抽象クラス単体ではインスタンス化することはできません。反対にインスタンス化できるクラスは具象クラスと呼びます。
抽象クラスを定義する際はabstract
キーワードを指定します。abstract class 抽象クラス名 { }抽象メソッド
抽象クラスには抽象メソッドを定義できます。
抽象メソッドには処理内容を記述できません。
メソッド名()
の後ろの{処理内容}
の代わりにセミコロン;
を記述します。abstract class Shape { double getArea(); // 抽象メソッド }継承
既存のクラスを元に新たなクラスを定義することを継承と呼びます。
また、継承において、元となるクラスをスーパークラス、新たに定義されるクラスをサブクラスと呼びます。
サブクラスを定義する際には、extends
キーワードを使用します。次のコードは先程の抽象クラスを継承した例です。
abstract class Shape { // 抽象クラス double getArea(); // 抽象メソッド } // 抽象クラスを継承 class Rectangle extends Shape { double width = 0; double height = 0; Rectangle(this.width, this.height); // 抽象メソッドを実装 double getArea() { return this.width * this.height; } } void main() { var rect = Rectangle(3, 4); print('四角形の面積は${rect.getArea()}㎠'); }実行結果
四角形の面積は12㎠※Dartでは総てがオブジェクトです。Javaのプリミティブ型(intやbooleanなどのデータ型)のようなものは存在しません。すべてのオブジェクトは Objectクラスを継承しています。
オーバーライド
オーバーライドとは、スーパークラスで定義しているメソッドを、同じ名前でサブクラスで再定義することです。
スーパークラスで定義したメソッドと目的は同じであるが、処理が異なるメソッドを定義する場合に使用します。@override
アノテーションを使用して、意図的にメソッドをオーバーライドしていることを示すことができます。class SmartTelevision extends Television { @override void turnOn() {...} // ··· }暗黙的インターフェイス
インターフェースとは
インターフェースとは簡単に言うと、公開されたクラスの取り決めのことです。
ほとんどのインターフェイスは処理内容を含まない抽象クラスから作成されます。
DartにはJavaのように明示的にインターフェイスを宣言するinterface
キーワードはありません。インターフェースを実装する場合、
extends
ではなくimplements
キーワードを使用します。// 抽象クラスA abstract class クラスA { void someMethod(); } // クラスAインターフェースの実装 class クラスB implements クラスA { void someMethod() { 処理内容 } }※JavaやKotlinの
interface
、Swiftのprotocol
にあたります。
暗黙的インターフェース
前述の通り、Dartには明示的なインターフェースはありません。
代わりに、任意のクラスをインターフェースとして使用できます。
これはすべてのクラスにあらかじめ暗黙的にインターフェースが定義されているためです。次のコードでは
Impostor
クラスはPerson
クラスを実装(implements
)しています。
Impostor
クラスはPerson
クラスのインターフェースをすべて実装する必要があります。// 暗黙的インターフェイスPersonにはgreet()メソッドが含まれています class Person { final String _name; Person(this._name); String greet(String who) => 'Hello, $who. I am $_name.'; } // Personインターフェースの実装 class Impostor implements Person { String get _name => ''; String greet(String who) => 'Hi $who. Do you know who I am?'; } String greetBob(Person person) => person.greet('Bob'); void main() { print(greetBob(Person('Kathy'))); print(greetBob(Impostor())); }実行結果
Hello, Bob. I am Kathy. Hi Bob. Do you know who I am?列挙型
列挙型とはいくつかの定数をひとまとまりにして扱えるようにしたもので、プログラマが任意に定義できます。
enum
キーワードを使用して列挙型を宣言します。次のコードはswich文で列挙型を使ったコードです。
※列挙型のすべての値を条件に含めないと警告が出ます。
enum Color { red, green, blue } void main() { var aColor = Color.blue; switch (aColor) { case Color.red: print('Red as roses!'); break; case Color.green: print('Green as grass!'); break; default: print(aColor); } }実行結果
Color.blueクラス変数
クラス変数やクラスメソッドは、クラス全体で扱うデータを格納するために使用します。
クラス変数、クラスメソッドはそれぞれstatic変数、staticメソッドとも呼ばれます。
呼び出す際には、クラス名.static変数名
やクラス名.staticメソッド名()
で呼び出します。
実装する際はstatic
キーワードを指定します。class Queue { static const initialCapacity = 16; // ··· } void main() { assert(Queue.initialCapacity == 16); }クラスメソッド
クラスメソッドはインスタンス上では動作しないので、
this
にアクセスすることはできません。しかし、クラス変数にはアクセスできます。次の例では、クラス上で直接スタティックメソッドを呼び出しています。
import 'dart:math'; class Point { double x, y; Point(this.x, this.y); static double distanceBetween(Point a, Point b) { var dx = a.x - b.x; var dy = a.y - b.y; return sqrt(dx * dx + dy * dy); } } void main() { var a = Point(2, 2); var b = Point(4, 4); var distance = Point.distanceBetween(a, b); assert(2.8 < distance && distance < 2.9); print(distance); }実行結果
2.8284271247461903ジェネリクス(総称型)
以前、List型の解説でList<String>のように<>内に型を指定していました。
ジェネリクス型でクラス定義をおこなうことにより、クラス内で型を事前に決めるのではなく、クラス利用時に決定できます。
次のコードはmain()
関数内で指定したString型の値とint型の値を同じクラス定義であつかっています。class GenericMember<T> { final List<T> _memberList = []; void push(T item) => _memberList.add(item); T pop() => _memberList.removeLast(); get memberList => _memberList; } void main() { final memberA = GenericMember<String>(); memberA.push("Ken"); memberA.push("Bob"); //memberA.push(1); print(memberA.memberList); final memberB = GenericMember<int>(); memberB.push(1); memberB.push(2); memberB.push(3); print(memberB.memberList); memberB.pop(); print(memberB.memberList); }実行結果
[Ken, Bob] [1, 2, 3] [1, 2]クラス名に続く<>はダイヤモンド演算子と呼ばれており、
T
のことを型パラメータといいます。<T>の他に<E>や<K, V>などがあり、これらは汎用的な型、つまり、仮の型を使用することを表すために使用します。
型パラメータの名前は任意でかまいませんが、通常は意味のある大文字1文字が使用されます。
※TはType、EはElement、KはKey、VはValueの頭文字を意味していますジェネリクス型を使用することによりシンプルで安全なコードが実現できます。
https://eda-inc.jp/post-5191/
参考