■ はじめに
https://dk521123.hatenablog.com/entry/2023/03/16/012034
で YAMLファイルを扱った。ここで作成した 「例2:独自クラスにマッピングする」「例3:別YAML&リファクタリング」 を、どのクラスでも使えるようにしたい。 そこで、その第一歩として、 今回は、Scala の Generics(ジェネリック/総称型)について学び 最後に曲がりなりにも上記の実装を実現する。
目次
【1】ジェネリック 【2】構文 1)関数 2)クラス 【3】ジェネリックの制限方法 1)境界 2)変位アノテーション 【4】サンプル 例1:ジェネリッククラス 例2:YAMLファイルを汎用的に独自クラスをマッピングする
【1】ジェネリック
* ジェネリック(Generic) のことを、Scala では 型パラメータ(Type Parameter or Parameterized types)ともいう
【2】構文
1)関数
def 関数名[型パラメータ, ...](引数):戻り値 = {
関数本体
}
2)クラス
class クラス名[A](クラス引数) {
(フィールド定義|メソッド定義)*
}
【3】ジェネリックの制限方法
1)境界
境界種類 | 記法 | 指定可能なクラス |
---|---|---|
境界 | [T] | T そのものしか指定できない |
上限境界 | [T<: クラス名] | Tには指定されたクラス又はそのサブクラスを指定可能 |
下限境界 | [T>: クラス名] | Tには指定されたクラス又はそのスーパークラスを指定可能 |
可視境界 | [T<% クラス名] | Tには指定されたクラス又はTに暗黙の型変換が可能なクラスを指定可能 |
2)変位アノテーション
class Foo[+A] // 共変クラス (covariance) class Bar[-A] // 反変クラス(contravariance) class Baz[A] // 非変クラス (nonvariant) // この「+/-」を「変位アノテーション (Variance Annotations)」という
変位種類 | 記法 | 指定可能なクラス | 引数使用可否 | 戻り値使用可否 |
---|---|---|---|---|
非変 | [T] | T に限られる | YES | YES |
共変 | [+T] | T とそのサブクラスに限られる | NO | YES |
反変 | [-T] | T とそのスーパークラスに限られる | YES | NO |
* Java の配列は、共変 * Scala の配列は、非変
【3】サンプル
例1:ジェネリッククラス
https://docs.scala-lang.org/ja/tour/generic-classes.html
class Stack[T] { private var elements: List[T] = Nil def push(value: T): Unit = this.elements = value :: this.elements def peek: T = this.elements.head def pop(): T = { val currentTop = peek this.elements = this.elements.tail currentTop } }
例2:YAMLファイルを汎用的に独自クラスをマッピングする
https://dk521123.hatenablog.com/entry/2023/03/16/012034
の「例3:別YAML&リファクタリング」をジェネリックで実装。 結構、苦労した、、、 なお、FileのCloseにUsingを使っているが、 AWS Glue の Scalaバージョンが 2.12なので、 代わりにTry-finallyで実装。
Main.scala
import org.yaml.snakeyaml.Yaml import org.yaml.snakeyaml.constructor.Constructor import scala.beans.BeanProperty import java.io.FileInputStream import scala.reflect._ object Main extends App { val inputPath = raw"C:\xxxx\process_info.yaml" val processInfo = readYaml[ProcessInfo](inputPath) println(processInfo) def readYaml[T: ClassTag](inputPath: String): T = { val reader = new FileInputStream(inputPath) try { // e.g. "class ProcessInfo" val targetClass = classTag[T].runtimeClass.toString() // e.g. "class ProcessInfo" -> "ProcessInfo" val className = targetClass.replaceFirst("class ", "") val inputYaml = new Yaml(new Constructor(className)) return inputYaml.load(reader).asInstanceOf[T] } finally { reader.close() } } } class ProcessInfo { @BeanProperty var processName = "" @BeanProperty var schedule = "" @BeanProperty var emailListToNotify = new java.util.ArrayList[String]() @BeanProperty var subProcesses = new java.util.HashMap[String, java.util.HashMap[String, Any]]() override def toString: String = s"processName : $processName , schedule: $schedule, emailListToNotify : $emailListToNotify , subProcesses : $subProcesses " }
process_info.yaml
processName: processA schedule: 10:00-12:00 emailListToNotify: - sample@gmail - sample@yahoo.com subProcesses: sub-processA1: inputPath: s3://your-bucket/sub-processA1/input/ outputPath: s3://your-bucket/sub-processA1/output/ sub-processA2: inputPath: s3://your-bucket/sub-processA2/input/ outputPath: s3://your-bucket/sub-processA2/output/ sub-processA3: inputPath: s3://your-bucket/sub-processA3/input/ outputPath: s3://your-bucket/sub-processA3/output/
参考文献
https://atmarkit.itmedia.co.jp/ait/articles/1208/09/news128.html
関連記事
Scala ~ 環境構築編 ~
https://dk521123.hatenablog.com/entry/2023/03/10/193805
Scala ~ 入門編 ~
https://dk521123.hatenablog.com/entry/2023/03/12/184331
Scala ~ YAML ~
https://dk521123.hatenablog.com/entry/2023/03/16/012034
Scala ~ ファイルハンドリング ~
https://dk521123.hatenablog.com/entry/2023/01/03/000000
Scala ~ asInstanceOf / isInstanceOf ~
https://dk521123.hatenablog.com/entry/2024/07/26/225713