0Pricing
Scala for Backend Engineering & Functional Programming · レッスン

ジェネリクスと型パラメーター

型安全性を保ちながら再利用可能なコンポーネントを作成するため、ジェネリッククラスとメソッドを記述する方法を理解します。

「ジェネリクスと型パラメーター」はCoddyKit上の無料Scala for Backend Engineering & Functional Programmingレッスンです。 これはレッスン1/3です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはScala for Backend Engineering & Functional Programming学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Scala for Backend Engineering & Functional Programmingコースには全3レッスンが含まれています。

ジェネリクスとは

StringやInt、カスタムオブジェクトなど、あらゆる型の項目を格納できるコンテナを作る必要があるとします。ジェネリクスがなければ、型ごとに別々のコンテナを作成してコードが重複するか、Anyのような非常に広い型を使うことになります。

ジェネリクスを使うと、コンパイル時の型安全性を保ちながら、さまざまなデータ型で動作する柔軟で再利用可能なコードを記述できます。後から異なる素材を組み合わせられる設計図のようなものです。

`Any`型の落とし穴

型のプレースホルダーとしてAnyを使うと、問題につながることがあります。何でも格納できる一方で、具体的な型情報が失われます。そのため、項目を元の型にキャストし直す必要が生じやすく、キャストが正しくない場合はエラーの原因になったり、実行時にクラッシュしたりします。

次の例を実行し、どのような問題が起こり得るか考えてみてください。

class ItemHolder(val item: Any)

object Main {
  def main(args: Array[String]): Unit = {
    val stringHolder = new ItemHolder("Hello Scala")
    val numberHolder = new ItemHolder(123)

    // We need to cast, and it's unsafe if we get it wrong
    val retrievedString = stringHolder.item.asInstanceOf[String]
    println(s"Retrieved String: $retrievedString")

    // If we uncomment the line below, it compiles but will crash at runtime!
    // val wrongType = numberHolder.item.asInstanceOf[String]
    // println(s"Wrong Type: $wrongType")
  }
}

型パラメーターの導入

ジェネリクスは、型パラメーターを導入することでAny型の落とし穴を解決します。型パラメーターは、クラスのインスタンスを作成するときやメソッドを呼び出すときに指定する実際の型のプレースホルダーです。

Scalaでは、クラス名またはメソッド名の後に角かっこ([])を付けて型パラメーターを定義します。通常、T、A、Bなどの大文字1文字を使います。たとえば、class Box[T]は、型パラメーターTを1つ受け取るジェネリッククラスBoxを宣言します。

ジェネリックなBoxを作る

任意の型の内容を格納できる、Boxというシンプルなジェネリッククラスを作ってみましょう。クラス定義全体で、内容の型を表すためにTが使われていることに注目してください。

これにより、型ごとに別々のクラスを作成しなくても、文字列、整数、その他の任意の型用のボックスを作成できます。

class Box[T](val content: T) {
  def get: T = content
  override def toString: String = s"Box($content)"
}

object Main {
  def main(args: Array[String]): Unit = {
    val stringBox = new Box("Scala is fun!")
    val intBox = new Box(42)
    val booleanBox = new Box(true)

    println(stringBox)
    println(intBox)
    println(booleanBox)
  }
}

ジェネリクスによる型安全性

ジェネリクスの最大の利点は、コンパイル時の型安全性です。Box[String]を作成すると、コンパイラーはその中に文字列だけを格納できることを認識します。整数を入れようとしたり、整数として取り出そうとしたりすると、コンパイラーがすぐにエラーを検出します。

これにより、Anyを使うような型安全性の低い方法で発生しがちな、多くの一般的な実行時エラーを防げます。

class Box[T](val content: T) {
  def get: T = content
}

object Main {
  def main(args: Array[String]): Unit = {
    val myStringBox: Box[String] = new Box("CoddyKit")
    val myIntBox: Box[Int] = new Box(100)

    // This works, type is String
    val s: String = myStringBox.get
    println(s"String from box: $s")

    // This works, type is Int
    val i: Int = myIntBox.get
    println(s"Int from box: $i")

    // The compiler prevents this line from compiling:
    // val wrong: String = myIntBox.get // Type mismatch error!
  }
}

メソッドにもジェネリクスを使えます

クラスと同様に、個々のメソッドもジェネリックにできます。ジェネリックメソッドは、そのメソッドに固有の型パラメーターを受け取れるため、クラス全体をジェネリックにしなくても、異なる型に対して処理を実行できます。

これは、さまざまな型に適用できる特定の操作が必要で、クラス自体はジェネリックなコンテナではない場合に便利です。

柔軟な`printPair`メソッド

ジェネリックメソッドprintPairの例を見てみましょう。このメソッドはAとBという2つの型パラメーターを受け取り、任意の2つの型のペアを出力できます。型パラメーターがメソッド名の直後に宣言されていることに注目してください。

これにより、さまざまなデータの組み合わせに対して、メソッドを柔軟に再利用できます。

object Util {
  def printPair[A, B](first: A, second: B): Unit = {
    println(s"Pair: ($first, $second)")
    println(s"Type of first: ${first.getClass.getName}")
    println(s"Type of second: ${second.getClass.getName}")
  }
}

object Main {
  def main(args: Array[String]): Unit = {
    Util.printPair("Hello", 123)
    Util.printPair(true, List(1, 2, 3))
    Util.printPair(4.5, 'C')
  }
}

賢い型推論

Scalaの強力な機能の1つが型推論です。ジェネリッククラスやジェネリックメソッドを使うとき、型パラメーターを明示的に指定する必要がない場合がよくあります。Scalaのコンパイラーは、指定された引数に基づいて型パラメーターを推論できます。

これにより、型安全性を損なわずにコードを簡潔にし、読みやすくできます。

class Container[T](val item: T) {
  def getItem: T = item
}

object Main {
  def main(args: Array[String]): Unit = {
    // Scala infers T as String
    val stringContainer = new Container("Inferred String")
    println(s"Content: ${stringContainer.getItem}, Type: ${stringContainer.getItem.getClass.getName}")

    // Scala infers T as Double
    val doubleContainer = new Container(3.14)
    println(s"Content: ${doubleContainer.getItem}, Type: ${doubleContainer.getItem.getClass.getName}")

    // You can also specify explicitly, but often not needed
    val explicitIntContainer: Container[Int] = new Container(500)
    println(s"Content: ${explicitIntContainer.getItem}, Type: ${explicitIntContainer.getItem.getClass.getName}")
  }
}

境界によるジェネリック型の制限

ジェネリッククラスやジェネリックメソッドを、特定の機能を持つ型だけで動作させたい場合があります。たとえば、数値を加算するメソッドは、数値型だけを受け取るべきです。

型境界を使うと、型パラメーターとして使用できる型を制限できます。上限境界(<:)は、型パラメーターが指定された型のサブタイプでなければならないことを意味します。たとえば、[T <: Animal]は、TがAnimalまたはAnimalのサブタイプでなければならないことを意味します。

class Animal { def speak(): String = "..." }
class Dog extends Animal { override def speak(): String = "Woof!" }
class Cat extends Animal { override def speak(): String = "Meow!" }

// This box can only hold types that are Animal or its subtypes
class AnimalShelter[T <: Animal](val animal: T) {
  def getAnimalSound: String = animal.speak()
}

object Main {
  def main(args: Array[String]): Unit = {
    val dogShelter = new AnimalShelter(new Dog())
    println(s"Dog says: ${dogShelter.getAnimalSound}")

    val catShelter = new AnimalShelter(new Cat())
    println(s"Cat says: ${catShelter.getAnimalSound}")

    // This would NOT compile because Car is not an Animal:
    // class Car
    // val carShelter = new AnimalShelter(new Car()) 
  }
}

ジェネリッククラスのチャレンジ

ジェネリクスについての理解度を確認してみましょう。

次の文を注意深く読み、Scalaのジェネリッククラスとジェネリックメソッドについて正しいものをすべて選択してください。

ジェネリクス:重要なポイント

このレッスンでは、柔軟で型安全なコードを記述するための強力な機能である、Scalaのジェネリクスについて学びました。

  • ジェネリクスを使うと、クラスやメソッドを異なる型に対応させられます。
  • 実際の型のプレースホルダーとして型パラメーター(例:[T])を使います。
  • ジェネリクスはコンパイル時の型安全性を提供し、実行時エラーを防ぎます。
  • Scalaの型推論により、ジェネリックコードを簡潔に記述できることがよくあります。
  • 型境界(例:<:)を使うと、型パラメーターを特定のサブタイプに制限できます。

ジェネリクスを使うことで、さまざまなデータ型に対応できる、堅牢で再利用可能なコンポーネントを記述できます。これにより、Scalaコードの表現力が高まり、エラーも起こりにくくなります。

よくある質問

「ジェネリクスと型パラメーター」レッスンは無料ですか?

はい。「ジェネリクスと型パラメーター」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Scala for Backend Engineering & Functional Programmingコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Scala for Backend Engineering & Functional Programmingコースには全3レッスンが含まれています。

「ジェネリクスと型パラメーター」で何を学びますか?

型安全性を保ちながら再利用可能なコンポーネントを作成するため、ジェネリッククラスとメソッドを記述する方法を理解します。 ブラウザで直接実行するハンズオンコードでScala for Backend Engineering & Functional Programmingを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Scala for Backend Engineering & Functional Programmingを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのScala for Backend Engineering & Functional Programmingは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン1/3です。

「ジェネリクスと型パラメーター」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このScala for Backend Engineering & Functional Programmingレッスンでコードを書いて実行できますか?

はい。すべてのScala for Backend Engineering & Functional Programmingレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. ジェネリクスと型パラメーター
  2. 変性:共変と反変
  3. 型クラスと暗黙の仕組み
← Scala for Backend Engineering & Functional Programmingに戻る