1. 概要

このチュートリアルでは、Scalaの2つのコアOOP要素への取り組みに焦点を当てます。 クラスとオブジェクト。

掘り下げる前に、クラスをカバーすることから始めます暗黙のクラスと内部クラス。 次に、見てみましょう Scalaオブジェクト。

結論として、Scalaのオブジェクトとクラスの違いについても学びます。

2. クラス

クラスは、オブジェクトを作成するための青写真です。 クラスを定義すると、クラスから新しいオブジェクト(インスタンス)を作成できます。

class キーワードの後に、そのクラスに付ける名前を付けてクラスを定義します。

Scala REPL を使用して、ターミナルで単純なクラスを作成する方法を見てみましょう。

scala> class Vehicle
defined class Vehicle

scala> var car = new Vehicle
car: Vehicle = Vehicle@7c1447b5

scala>

私たちが今持っているのは、引数のないコンストラクターを持つVehicleクラスです。

Scalaでは、クラスコンストラクターは一般的にJavaよりもはるかに簡潔で読みやすくなっています。

この言語は、primaryauxiliaryの2つのタイプをサポートしています。

2.1. プライマリコンストラクタ

デフォルトでは、すべてのScalaクラスにプライマリコンストラクターがあります。 プライマリコンストラクターは、コンストラクターパラメーター、クラス本体で呼び出されるメソッド、およびクラス本体で実行されるステートメントで構成されます。

Abcというクラスを定義しましょう。そのコンストラクターはStringIntを取ります。

class Abc(var a: String = "A", var b: Int) {
  println("Hello world from Abc")
}

今回は、Scalaが提供する引数なしのコンストラクターの代わりに、このクラスにはクラス名の横にリストすることで定義した2つの引数のコンストラクターがあります。上部のパラメーターと本体のステートメントは次のようになります。コンストラクターをアップします。

また、パラメータの1つである a、にデフォルトを指定していることにも注意してください。 「A」

Javaでは、これは少し長くなります。 オブジェクトを初期化するコンストラクターとして、特別なメソッドAbcを作成する必要があります。

Abc のインスタンスが作成されると、printlnステートメントからの出力が表示されます。

scala> val abc = new Abc(b=3)
Hello world from Abc
abc: Abc = Abc@70f68288

これは、クラス本体のすべてのステートメントと式がコンストラクターの一部であるためです。

2.2. 補助コンストラクター

補助(またはセカンダリ)コンストラクターを定義するには、thisという名前のメソッドを定義します。

val constA = "A"
val constB = 4

class Abc(var a: String, var b: Int) {
  def this(a: String) {
    this(a, constB)
    this.a = a
  }

  def this(b: Int) {
    this(constA, b)
    this.b = b
  }

  def this() {
    this(constA, constB)
  }
}

これらの補助コンストラクターを使用して、いくつかの異なる方法でクラスインスタンスを作成できます。

new Abc("Some string")
new Abc(1)
new Abc()

補助コンストラクターを定義する際に留意すべき2つのルールがあります。

    • 各コンストラクターには一意の署名が必要です。 パラメータセットは、他のコンストラクタとは異なる必要があります
    • 各コンストラクターは、初期コンストラクターの1つまたは基本クラスコンストラクターを呼び出す必要があります

2.3. クラスインスタンス

クラスはもっと次のように考えることができますテンプレートオブジェクトを作成するため。 クラスインスタンスは、クラスをテンプレートとして使用して作成された実際のオブジェクトです。 すでに見てきたように、キーワードを使用します新着クラスのインスタンスを作成します。

さまざまな種類の車両を製造するために使用できるVehicleテンプレートがあるとします。 Vehicle テンプレートは、実際の車両を表していないクラスを表しています。 Vehicleテンプレートを使用してcarを作成すると、carVehicleのインスタンスになります。

最後の例では、carというVehicleクラスのインスタンスを作成しました。 ほとんどの場合、クラスとインスタンスは、Vehicleおよびcarの例ほど単純ではありません。

いくつかの新しい興味深い機能を追加して、車両の例を作成してみましょう。 まず、Carという新しいクラスを作成します。

class Car (val manufacturer: String, brand: String, var model: String) {
  var speed: Double = 0;
  var gear: Any = 0;
  var isOn: Boolean = false;

  def start(keyType: String): Unit = {
    println(s"Car started using the $keyType")
  }

  def selectGear(gearNumber: Any): Unit = {
    gear = gearNumber
    println(s"Gear has been changed to $gearNumber")
  }

  def accelerate(rate: Double, seconds: Double): Unit = {
    speed += rate * seconds
    println(s"Car accelerates at $rate per second for $seconds seconds.")
  }

  def brake(rate: Double, seconds: Double): Unit = {
      speed -= rate * seconds
      println(s"Car slows down at $rate per second for $seconds seconds.")
  }

  def stop(): Unit = {
    speed = 0;
    gear = 0;
    isOn = false;
    println("Car has stopped.")
  }
}

Car クラスを使用すると、必要な数のインスタンスを作成できます。 コードをターミナルにロードしましょう:

scala> :load path/to/my/scala/File.scala
args: Array[String] = Array()
Loading path/to/my/scala/File.scala
defined class Car

CarクラスからオブジェクトfamilyCar を作成します。

scala> var familyCar = new Car("Toyota", "SUV", "RAV4")
familyCar: Car = Car@2d5b549b

scala>

familyCar 変数は、 Car クラスのインスタンスであり、Carのすべての属性を備えています。

これで、 familyCar variable を試してみることができます。

scala> familyCar.start("remote")
Car started using the remote

scala> familyCar.speed
res0: Double = 0.0

scala> familyCar.accelerate(2, 5)
Car accelerates at 2.0 per second for 5.0 seconds.

scala> familyCar.speed
res1: Double = 10.0

scala> familyCar.brake(1, 3)
Car slows down at 1.0 per second for 3.0 seconds.

scala> familyCar.speed
res2: Double = 7.0

2.4. クラスの拡張

クラスを拡張すると、最初のクラスからすべてのプロパティを継承する新しいクラスを作成できるようになります。 extendsキーワードを使用してクラスを拡張します。

Car クラスを拡張して、Toyotaという新しいクラスを作成しましょう。

class Toyota(transmission: String, brand: String, model: String) extends Car("Toyota", brand, model) { 
  override def start(keyType: String): Unit = { 
    if (isOn) {
      println(s"Car is already on.") 
      return
    } 
    if (transmission == "automatic") { 
      println(s"Car started using the $keyType") 
    } else { 
      println(s"Please ensure you're holding down the clutch.") 
      println(s"Car started using the $keyType") 
    } 
    isOn = true  
  } 
}

いずれかのメソッドで異なる動作を提供する場合は、オーバーライドしてカスタム動作を定義できます。 Toyota クラスでわかるように、startメソッドをオーバーライドしました。

次に、ToyotaクラスとCarクラスの共通点と、変更点を見てみましょう。

scala> val prado = new Toyota("Manual", "SUV", "Prado")
prado: Toyota = Toyota@4b4ff495

scala> prado.start("key")
Please ensure you're holding down the clutch.
Car started.

scala> prado.accelerate(5, 2)
Car accelerates at 5.0 per second for 2.0 seconds.

scala> prado.speed
res0: Double = 10.0

scala>

これで、クラッチを握ることを思い出させるstartメソッドができました。

他のメソッドをオーバーライドしていないため、残りのメソッドはCarの場合と同じように機能します。

2.5. 暗黙のクラス

暗黙のクラス(Scala 2.10で導入)は、既存のオブジェクトに新しい機能を追加する方法を提供します。 これは、特にソースオブジェクトを変更するオプションがない場合に便利です

implicitキーワードを使用して暗黙のクラスを定義します。 たとえば、Stringクラスにメソッドを追加する暗黙のクラスを作成しましょう。

object Prediction {
  implicit class AgeFromName(name: String) {
    val r = new scala.util.Random
    def predictAge(): Int = 10 + r. nextInt(90)
  }
}

この例では、10から100までのランダムな整数を返すpredictAgeメソッドを使用してimplicitクラスAgeFromNameを作成しました。 objectキーワードについては心配しないでください。これについては次のセクションで詳しく説明します。

スコープ内に暗黙のクラスがある場合は、任意の文字列でpredictAgeメソッドを呼び出すことができます。

scala> import Prediction._
import Prediction._

scala> "Faith".predictAge()
res0: Any = 89

scala> "Faith".predictAge()
res1: Any = 74

暗黙のクラスの作成にはいくつかの制限があることに注意してください。

1. これらは、別のトレイト/クラス/オブジェクト内で定義する必要があります。  AgeFromName の例では、オブジェクトに配置しました。

2. コンストラクターで暗黙的な引数を1つだけ取ることができます。 我々はできる:

implicit class AgeFromName(name: String)
implicit class AgeFromName(name: String)(implicit val a: String, val b: Int)

だがしかし:

implicit class AgeFromName(name: String, val a: Int)

3. それらは一意である必要があります。 つまり、暗黙のクラスと同じ名前のメソッド、メンバー、またはオブジェクトがスコープ内に存在しない可能性があります。

4. 暗黙のクラスをcaseクラスにすることはできません。

2.6. インナークラス

Scalaは、クラスを別のクラス内にネストする機能を提供します。 Scala 内部クラスは、外部オブジェクトにバインドされています。

簡単な例を見てみましょう。

class PlayList {
  var songs: List[Song] = Nil
  def addSong(song: Song): Unit = {
    songs = song :: songs
  }
  class Song(title: String, artist: String)
}

class DemoPlayList {
  val funk = new PlayList
  val jazz = new PlayList
  val song1 = new funk.Song("We celebrate", "Laboriel")
  val song2 = new jazz.Song("Amazing grace", "Victor Wooten")
}

これで、DemoPlayListファンクおよびジャズプレイリストに適切な曲を追加できます。

scala> val demo = new DemoPlayList
demo: DemoPlayList = DemoPlayList@4ab2e70c

scala> demo.funk.addSong(demo.song1)

scala> demo.jazz.addSong(demo.song2)

scala> demo.funk.songs
res0: List[demo.funk.Song] = List(PlayList$Song@6c3b477b)

scala> demo.jazz.songs
res1: List[demo.jazz.Song] = List(PlayList$Song@d963c85)

scala>

適切なプレイリストに適切な曲を追加したため、すべてが期待どおりに機能しました。

DemoPlayList をもう一度見ると、song1song2がそれぞれfunkjazzのメンバーであることがわかります。 song1song2Songのインスタンスですが、は同じプレイリストインスタンスに属していないため、タイプが異なります。

これが意味するのは、song1jazzに追加できないということです。

scala> demo.jazz.addSong(demo.song1)
                              ^
       error: type mismatch;
        found   : demo.funk.Song
        required: demo.jazz.Song

scala>

同じことがsong2にも当てはまり、ファンクに追加することはできません。

scala> demo.funk.addSong(demo.song2)
                              ^
       error: type mismatch;
        found   : demo.jazz.Song
        required: demo.funk.Song

scala>

この動作は、Scala内部クラスが外部オブジェクトにバインドされているためです。

3. オブジェクト

以前のcarの例を思い出すと、 car オブジェクトであり、Vehicleが実行できるすべてのことを実行できます。 クラスはテンプレートを提供しますが、オブジェクトはテンプレートから作成するものです。

一般的にOOPで言えば、オブジェクトはクラスのインスタンスであると言うのが完璧です。 ただし、Scalaには、シングルトンオブジェクトを定義するときに使用できるobjectキーワードがあります

シングルトンとは、一度しかインスタンス化できないオブジェクトを意味します。 オブジェクトを作成するには、objectキーワードと識別子だけが必要です。

object SomeObject

オブジェクトはパラメータを取りませんが、通常のクラスと同じようにフィールド、メソッド、およびクラスを定義できます。

object Router {
  val baseUrl: String = "https://www.baeldung.com"
  
  case class Response(baseUrl: String, path: String, action: String)
  def get(path: String): Response = {
    println(s"This is a get method for ${path}")
    Response(baseUrl, path, "GET")
  }

  def post(path: String): Response = {
    println(s"This is a post method for ${path}")
    Response(baseUrl, path, "POST")
  }

  def patch(path: String): Response = {
    println(s"This is a patch method for ${path}")
    Response(baseUrl, path, "PATCH")
  }

  def put(path: String): Response = {
    println(s"This is a put method for ${path}")
    Response(baseUrl, path, "PUT")
  }

  def delete(path: String): Response = {
    println(s"This is a delete method for ${path}")
    Response(baseUrl, path, "DELETE")
  }
}

Router オブジェクトのメンバーをインポートすることで、それらのメンバーのいずれかを使用できます。 ルーターにすべてをインポートしましょう:

scala> import Router._
import Router._

scala> Response("some url", "some path", "GET")
res1: Router.Response.type = Response(some url,some path,GET)

scala> baseUrl
Here we go about Routing!
res2: String = https://www.baeldung.com

scala> get("/index")
This is a get method for /index
res4: Router.Response = Response(https://www.baeldung.com,/index,GET)

scala> put("/index")
This is a put method for /index
res5: Router.Response = Response(https://www.baeldung.com,/index,PUT)

scala> post("/scala-tutorials")
This is a post method for /scala-tutorials
res6: Router.Response = Response(https://www.baeldung.com,/scala-tutorials,POST)

scala>

baseUrl にアクセスすると、「ここでルーティングについて説明します!」 が印刷されました。 これは、オブジェクトを参照すると、オブジェクトが遅延してインスタンス化されるためです。 また、次に baseUrl にアクセスしたときに、メッセージが出力されないこともわかります。

Javaには、シングルトンオブジェクトに直接相当するものはありません。 すべてのScalaシングルトンオブジェクトに対して、コンパイラーはオブジェクトのJavaクラス(末尾にドル記号が追加されたもの)と、クラスの単一インスタンスを保持する MODULE$という名前の静的フィールドを作成します。 したがって、オブジェクトのインスタンスが1つだけであることを確認するために、Scalaは静的クラスホルダーを使用します。

3.1. コンパニオンオブジェクト

コンパニオンオブジェクトは、クラスと同じ名前で同じファイルにあるオブジェクトです。 逆に、クラスはオブジェクトのコンパニオンクラスです。 Router を変更し、それをコンパニオンオブジェクトとして利用するコンパニオンクラスを作成します。

object Router {
  //..
}

class Router(path: String) {
  import Router._
  def get(): Response = getAction(path)
  def post(): Response = postAction(path)
  def patch(): Response = patchAction(path)
  def put(): Response = putAction(path)
  def delete(): Response = deleteAction(path)
}

これがどのように機能するかを確認するために、Scalaコマンドライン past コマンドを使用して、ターミナルでコードを取得します。

scala> :paste path/to/my/scala/File.scala
Pasting file path/to/my/scala/File.scala...
defined object Router
defined class Router

scala> val indexRouter = new Router("/index")
indexRouter: Router = Router@29e61e82

scala> indexRouter.get()
Here we go about Routing!
This is a get method for /index
res0: Router.Response = Response(https://www.baeldung.com,/index,GET)

scala> indexRouter.post()
This is a post method for /index
res1: Router.Response = Response(https://www.baeldung.com,/index,POST)

scala> indexRouter.delete()
This is a delete method for /index
res2: Router.Response = Response(https://www.baeldung.com,/index,DELETE)

scala>

Routerオブジェクトのメソッドはprivateですが、Routerコンパニオンクラスはそれらにアクセスできます。

コンパニオンオブジェクトのもう1つの一般的な使用法は、ファクトリメソッドの作成です

アプリケーションにtest、int、staging、およびproduct環境の4つの環境がある一般的なユースケースを見てみましょう。 文字列から現在の環境を生成するファクトリが必要です。 また、状態を維持できるように、環境をシリアル化できるようにする必要があります。

これは、コンパニオンオブジェクトとクラスを使用して実現できます。

sealed class BaeldungEnvironment extends Serializable {val name: String = "int"}

object BaeldungEnvironment {
  case class ProductionEnvironment() extends BaeldungEnvironment {override val name: String = "production"}
  case class StagingEnvironment() extends BaeldungEnvironment {override val name: String = "staging"}
  case class IntEnvironment() extends BaeldungEnvironment {override val name: String = "int"}
  case class TestEnvironment() extends BaeldungEnvironment {override val name: String = "test"}

  def fromEnvString(env: String): Option[BaeldungEnvironment] = {
    env.toLowerCase match {
      case "int" => Some(IntEnvironment())
      case "staging" => Some(StagingEnvironment())
      case "production" => Some(ProductionEnvironment())
      case "test" => Some(TestEnvironment())
      case e => println(s"Unhandled BaeldungEnvironment String: $e")
        None
    }
  }
}

簡単な単体テストを使用して環境を検証できます。

@Test
def givenAppropriateString_whenFromEnvStringIsCalled_thenAppropriateEnvReturned(): Unit ={
  val test = BaeldungEnvironment.fromEnvString("test")
  val int = BaeldungEnvironment.fromEnvString("int")
  val stg = BaeldungEnvironment.fromEnvString("staging")
  val prod = BaeldungEnvironment.fromEnvString("production")

  assertEquals(test, Some(TestEnvironment()))
  assertEquals(int, Some(IntEnvironment()))
  assertEquals(stg, Some(StagingEnvironment()))
  assertEquals(prod, Some(ProductionEnvironment()))
}

4. Scalaクラスとオブジェクトの違い

Scala クラスオブジェクトについて理解が深まったので、いくつかの違いを見てみましょう。

  1. Definition :クラスは class キーワードで定義され、オブジェクトはobjectキーワードを使用してdefinedで定義されます。 また、クラスはパラメータを受け取ることができますが、オブジェクトはパラメータを受け取ることはできません。
  2. インスタンス化:通常のクラスをインスタンス化するには、newキーワードを使用します。 オブジェクトの場合、newキーワードは必要ありません
  3. シングルトンvsマルチインスタンス:クラスは無制限のインスタンスを持つことができますが、オブジェクトには1つのインスタンスが怠惰に作成されます。最初に参照します
  4. 継承:オブジェクトはシングルトンであるため、継承できません/拡張; そうすることで、オブジェクトのインスタンス以上のものが作成されます。一方、クラスは拡張できます

5. 結論

このチュートリアルでは、一連の簡単な例を使用してScalaクラスとオブジェクトを確認しました。 内部クラス、暗黙のクラス、コンパニオンオブジェクト、およびそれらのアプリケーションについて学びました。 次に、Scalaのクラスとオブジェクトの違いをいくつか理解して結論を出しました。

いつものように、記事の完全なソースコードは、GitHubから入手できます。