4-2.Dartのジェネリクスの基本

1. ジェネリクスとは

ジェネリクスは、クラスや関数を特定のデータ型に依存しないように設計するための仕組みです。ジェネリクスを使用することで、コードの再利用性が向上し、型安全性が確保されます。Dartでは、ジェネリクスを使ってリスト、マップ、セットなどのコレクションやカスタムクラスを定義できます。

2. ジェネリクスの基本構文

ジェネリクスは、クラスや関数の定義時に角括弧(<>)を使用して宣言します。

リストのジェネリクス

Dart
void main() {
  List<int> intList = [1, 2, 3, 4, 5];
  List<String> stringList = ['one', 'two', 'three'];

  print(intList); // [1, 2, 3, 4, 5]
  print(stringList); // [one, two, three]
}

3. ジェネリッククラス

ジェネリッククラスを使用することで、さまざまなデータ型に対応したクラスを作成できます。

ジェネリッククラスの定義

Dart
class Box<T> {
  T content;

  Box(this.content);

  void display() {
    print('Content: $content');
  }
}

void main() {
  Box<int> intBox = Box(123);
  intBox.display(); // Content: 123

  Box<String> strBox = Box('Hello');
  strBox.display(); // Content: Hello
}

4. ジェネリック関数

ジェネリック関数を使用することで、さまざまなデータ型に対応した関数を作成できます。

ジェネリック関数の定義

Dart
T add<T extends num>(T a, T b) {
  return a + b;
}

void main() {
  int intSum = add(2, 3);
  print('Sum of integers: $intSum'); // Sum of integers: 5

  double doubleSum = add(2.5, 3.5);
  print('Sum of doubles: $doubleSum'); // Sum of doubles: 6.0
}

5. 制約付きジェネリクス

ジェネリクスに制約を付けることで、特定の型に限定することができます。制約付きジェネリクスを使用することで、型安全性を高めることができます。

制約付きジェネリクスの定義

Dart
class Pair<T extends num> {
  T first;
  T second;

  Pair(this.first, this.second);

  T sum() {
    return first + second;
  }
}

void main() {
  Pair<int> intPair = Pair(3, 4);
  print('Sum of intPair: ${intPair.sum()}'); // Sum of intPair: 7

  Pair<double> doublePair = Pair(3.5, 4.5);
  print('Sum of doublePair: ${doublePair.sum()}'); // Sum of doublePair: 8.0

  // Pair<String> stringPair = Pair('hello', 'world'); // エラー: Stringはnumのサブタイプではない
}

6. ジェネリクスの利点

ジェネリクスを使用することで、以下の利点があります。

  1. 型安全性の向上: コンパイル時に型チェックが行われるため、ランタイムエラーが減少します。
  2. コードの再利用性: 同じコードを異なるデータ型で再利用できるため、重複コードが減少します。
  3. 可読性の向上: 明示的に型を指定することで、コードの意図が明確になります。

7. 例題

例題1: ジェネリッククラスの使用

Dart
class Storage<T> {
  T value;

  Storage(this.value);

  void updateValue(T newValue) {
    value = newValue;
  }

  T getValue() {
    return value;
  }
}

void main() {
  Storage<int> intStorage = Storage(10);
  print('Initial int value: ${intStorage.getValue()}'); // Initial int value: 10
  intStorage.updateValue(20);
  print('Updated int value: ${intStorage.getValue()}'); // Updated int value: 20

  Storage<String> stringStorage = Storage('Dart');
  print('Initial string value: ${stringStorage.getValue()}'); // Initial string value: Dart
  stringStorage.updateValue('Flutter');
  print('Updated string value: ${stringStorage.getValue()}'); // Updated string value: Flutter
}

例題2: ジェネリック関数の使用

Dart
T findMax<T extends Comparable>(T a, T b) {
  return a.compareTo(b) > 0 ? a : b;
}

void main() {
  int maxInt = findMax(3, 5);
  print('Max int: $maxInt'); // Max int: 5

  double maxDouble = findMax(3.7, 2.5);
  print('Max double: $maxDouble'); // Max double: 3.7

  String maxString = findMax('apple', 'banana');
  print('Max string: $maxString'); // Max string: banana
}

例題3: 制約付きジェネリクスの使用

Dart
class Rectangle<T extends num> {
  T width;
  T height;

  Rectangle(this.width, this.height);

  T area() {
    return width * height;
  }
}

void main() {
  Rectangle<int> intRectangle = Rectangle(5, 10);
  print('Area of intRectangle: ${intRectangle.area()}'); // Area of intRectangle: 50

  Rectangle<double> doubleRectangle = Rectangle(5.5, 10.5);
  print('Area of doubleRectangle: ${doubleRectangle.area()}'); // Area of doubleRectangle: 57.75
}

↓次回内容:

5-1.Dartの非同期プログラミング: Futureとasync/await

1. 非同期プログラミングとは
非同期プログラミングは、時間のかかる操作(例えば、ネットワークリクエストやファイルの読み書き)を実行している間に他の処理をブロックせずに続行するための手法です。これにより、アプリケーションのパフォーマンスが向上し、応答性が良くなります。
2. Futureとは
Futureは、非同期操作の結果を表すオブジェクトです。将来の時点で値が提供されるか、エラーが発生する可能性があります。Futureは、現在は値が利用できないが、将来的には利用可能になることを示します。

コメントを残す