Menu
Coddy logo textTech

ジェネリクスの制約

CoddyのDartジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 67/110。

時には、ジェネリック型に特定の機能を持たせる必要があります。例えば、項目の比較や特定のプロパティへのアクセスを行いたい場合、制限のない T ではそれらの機能が存在することを保証できません。ジェネリック制約を使用すると、型パラメータを特定の型またはそのサブクラスに制限できます。

extends キーワードを使用して、型パラメータを制約します:

class NumberBox<T extends num> {
  T value;
  
  NumberBox(this.value);
  
  T doubled() => (value * 2) as T;
}

void main() {
  var intBox = NumberBox<int>(5);
  var doubleBox = NumberBox<double>(3.5);
  
  print(intBox.doubled());    // 10
  print(doubleBox.doubled()); // 7.0
  
  // var stringBox = NumberBox<String>('hi');  // エラー!
}

T extends num であるため、コンパイラは value が乗算のような数値演算をサポートしていることを認識します。この制約がない場合、value * 2 の呼び出しは失敗します。

制約は、独自のクラス階層でも動作します:

abstract class Animal {
  String get name;
}

class Dog extends Animal {
  String get name => 'Dog';
}

class Shelter<T extends Animal> {
  List<T> animals = [];
  
  void add(T animal) => animals.add(animal);
  
  void printNames() {
    for (var animal in animals) {
      print(animal.name);  // 安全: T によって 'name' が存在することが保証されます
    }
  }
}

ジェネリック制約は、ジェネリクスの柔軟性と、その型でどのような操作が利用可能かを知っているという安全性を組み合わせたものです。

challenge icon

チャレンジ

簡単

数値型のみで動作する統計計算機を作成しましょう!数値のみで使用できるように制約を設けたジェネリッククラスを作成し、格納された値に対して安全に数学的演算を行えるようにします。

コードを2つのファイルに整理します:

  • stats.dart: 数値のリストを管理するジェネリッククラス Stats<T extends num> を作成します:
    • 数値を格納するための values という名前の List<T>
    • 提供された値でリストを初期化するコンストラクタ
    • すべての値の合計を num として返すメソッド sum()
    • 最小値を T として返すメソッド min()
    • 最大値を T として返すメソッド max()
    • 統計情報をフォーマットして表示するメソッド printStats()
  • main.dart: statsファイルをインポートし、異なる数値型で制約付きジェネリッククラスを実演します:
    • [10, 25, 5, 30, 15] を持つ Stats<int> を作成し、printStats() を呼び出します
    • 空行を1つ出力します
    • [3.5, 1.2, 4.8, 2.1] を持つ Stats<double> を作成し、printStats() を呼び出します

printStats() メソッドは、合計、最小値、最大値を示す3行を出力する必要があります。T extends num となっているため、値に対して比較演算子や算術演算を安全に使用できます。これは、制約のないジェネリック型では不可能なことです。

期待される出力:

Sum: 85
Min: 5
Max: 30

Sum: 11.6
Min: 1.2
Max: 4.8

チートシート

ジェネリック制約は、extendsキーワードを使用して、型パラメータを特定の型またはそのサブクラスに制限します。

class NumberBox<T extends num> {
  T value;
  
  NumberBox(this.value);
  
  T doubled() => (value * 2) as T;
}

T extends numを指定することで、コンパイラはvalueが乗算などの数値演算をサポートしていることを認識します。

制約はカスタムクラスの階層構造でも機能します:

abstract class Animal {
  String get name;
}

class Shelter<T extends Animal> {
  List<T> animals = [];
  
  void printNames() {
    for (var animal in animals) {
      print(animal.name);  // 安全:Tは'name'が存在することを保証します
    }
  }
}

ジェネリック制約は、型の安全性と利用可能な操作を保証しながら、柔軟性を提供します。

自分で試してみよう

import 'stats.dart';

void main() {
  // TODO: 値 [10, 25, 5, 30, 15] を持つ Stats<int> を作成し、
  // printStats() を呼び出す
  
  // TODO: 空行を出力する
  
  // TODO: 値 [3.5, 1.2, 4.8, 2.1] を持つ Stats<double> を作成し、
  // printStats() を呼び出す
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン