1. 概要

Scalaは、他の多くのコンピューター言語と同様に、型キャストまたは型強制をサポートしています。 このチュートリアルでは、これらのメカニズムを見て、Scalaでより慣用的なメカニズムを学びます。

Scalaでの型キャストは、型消去のために危険にさらされていることに注意してください。 その結果、キャストを正しく型変換する方法がわからない場合、微妙なバグが発生する可能性があります。

2. Scalaの型キャストメカニズム

Scalaは、オブジェクトの宣言されたタイプを別のタイプに変換する3つの主な方法を提供します。

  1. Byte Int Char Floatなどの組み込み型の値型キャスト
  2. asInstanceOf[T]メソッドによる型キャスト
  3. matchステートメントを使用した効果型キャストへのパターンマッチング

2.1. 値型キャスト

値型間の変換は次のように定義されます。

Byte —> Short —> Int —> Long —> Float —> Double
                  ^
                  |
                 Char

矢印は、矢印の左側にある特定の値タイプを右側に昇格できることを示しています。 たとえば、ByteShortにプロモートできます。 ただし、その逆は当てはまりません。 Scalaでは、反対方向に割り当てることはできません。

val byte: Byte = 0xF
val shortInt: Short = byte // OK
val byte2: Byte = shortInt // Will not compile

値型キャストには特別な構文やメソッドを使用しないことに注意してください。

2.2. asInstanceOf[T]を介して型キャスト

asInstanceOf [T] メソッドを使用して、既存のオブジェクトを別のタイプに強制することができます。 さらに、Scalaはコンパニオンメソッド isInstanceOf [T] をサポートしており、これを組み合わせて使用できます。

これらのメソッドの動作を確認するために、いくつかのクラス定義を考えてみましょう。

class T1
class T2 extends T1
class T3

次に、これらのクラスを使用して、 asInstanceOf[T]を介した型キャストがどのように機能するかを見てみましょう。

val t2 = new T2
val t3 = new T3
val t1: T1 = t2.asInstanceOf[T1]

assert(t2.isInstanceOf[T1] == true)
assert(t3.isInstance[T1] == false)
val anotherT1 = t3.asInstanceOf[T1]  // Run-time error

これはJavaに似ています。 ただし、次のセクションでは、Scalaのmatchステートメントを使用してこれを改善する方法を学習します。

2.3. パターンマッチングによる型キャスト

前のセクションで示したように、 isInstanceOf[T]およびasInstanceOf[T]を使用できます。 ただし、オブジェクトタイプに基づいて適度に複雑なロジックを使用している場合でも、保守が困難な一連のif-elseステートメントになってしまう可能性があります。

幸い、Scalaのパターンマッチングはこの課題を解決します。 RSSを介してBaeldungの現在の記事に関する情報を取得する関数retrieveBaeldungRSSArticles()について考えてみます。 この関数は、ネットワーキングとXML解析の両方を含む、さまざまな方法で失敗する可能性があります。

これを解決するには、メソッド呼び出しを Try でラップし、SuccessおよびFailure応答にパターンマッチングを実装します。

Try(retrieveBaeldungRSSArticles()) match {
  case Success(lines) if lines.isEmpty =>
    // No content was found
  case Success(lines) =>
    // Can process a successful response
  case Failure(e: MalformedURLException) =>
    // Error w/the URL we used to connect
  case Failure(e: UnknownHostException) =>
    // Failed to find specified host
  case Failure(e: IOException) =>
    // Generic IO/network error handling here.
  case Failure(t) =>
    // A non-IOException occurred.
}

この実装には、次のような多くの利点があります。

  1. アプリケーションロジックがより明確になりました。 成功または失敗のケースを簡単に特定できることに注目してください。
  2. Scalaはオブジェクトタイプをアンラップし、意図を単純化します。
  3. 成功と失敗のデフォルトのケースは、私たちが簡単に特定できます。

Scalaはこれらすべてを簡単にするので、型キャストについて考える必要はほとんどありません。

3. 型キャストはいつ必要ですか?

では、Scalaで型キャストが必要になる時期はありますか? リストは驚くほど短いです:

  • 前のセクションで示したエラー処理
  • 変更できないレガシーコードとの統合
  • 私たちが書いているフレームワーク

最後のケースは慎重に検討する必要があります。 Scalaの機能機能を活用し、関数をパラメーターとして受け取る関数を作成する必要があります。 このようにして、フレームワークのユーザーは、不必要に新しいクラスを派生させるのではなく、アプリケーションコードの記述に集中できます

レガシーコードとの統合は、特にJava統合にも適用されます。 古いJavaフレームワークの多くは、不透明なオブジェクトを返します。 それらをScalaで使用したい場合は、強く型付けされたオブジェクトにキャストする必要があります。

4. 型消去

型消去は、知っておくべき重要な概念です。 これは、JVMがジェネリック型をサポートする方法の結果です。 ご存知のように、ジェネリック型を使用すると、クラスまたはトレイトをパラメーター化できます。 ジェネリックスの最も一般的な使用法は、Scalaのコレクションクラスでの使用です。次に例を示します。

val counts: List[Int] = List.empty[Int]
val teamScores: Map[String, Int] = Map[String, Int]("Team A" -> 10, "Team B" -> 5, "Team C" -> 13)

ここでは、これらのコレクションが指定されたタイプのオブジェクトのみを受け入れることができることを示しました。 ただし、これはコンパイル時の機能です。 実行時に、両方のコレクションは他のリストまたはマップと区別できなくなります。

この概念を説明するために、すべてのアサーションが真であるため、おそらく少し驚くべきいくつかの有効な例を見てみましょう。

val l1 = List(1, 2, 3)
val l2 = List("a", "b", "c")
assert(l1.isInstanceOf[List[Int]], "l1 is a List[Int]")
assert(l1.isInstanceOf[List[String]], "l1 is a List[String]")
assert(l2.isInstanceOf[List[Int]], "l1 is a List[Int]")
assert(l2.isInstanceOf[List[String]], "l1 is a List[String]")

ここで重要なポイントは、Scalaの型システムには制限があるということです。

5. 結論

この記事では、Scalaでの型キャストの基本的な使用法を示しました。 また、Scalaのmatchステートメントが型キャストの流動的な管理をどのように可能にするかについても調べました。

最後に、Scalaの型システムの微妙さは高度なトピックであることに気づきました。 このため、キャストを入力するときは注意が必要です。

いつものように、完全なソースコードはGitHubにあります。