1. 概要

不変のオブジェクトとデータ構造は、Scalaの第一級市民です。 これは、分散システムでのミスを防ぎ、スレッドセーフなデータを提供するためです。 ただし、本当に必要な場合は、可変オブジェクトを使用することもできます。

このチュートリアルでは、Scalaの不変オブジェクトと可変オブジェクトについて説明します。 それらを実装する方法といくつかのベストプラクティスを見ていきます。

2. 不変および可変の値の割り当て

Scalaでは、値が不変変数に割り当てられている場合、その値を再割り当てすることはできません。 試してみると、コンパイルエラーが発生します。 一方、可変変数に値を再割り当てできます。

val を使用して不変の値を定義することにより、これを試してみましょう。

val pi = 3.14

pi、の値を変更しようとすると、コンパイルエラーが発生します。

pi = 4

したがって、変更可能な値が必要な場合は、varを使用して宣言された変数を使用する必要があります。

var myWeight = 60
assert(myWeight == 60)

これで、myWeightに割り当てられた値を変更できます。

myWeight = 65
assert(myWeight == 65)

3. クラスの可変性

変数への値の割り当てに加えて、 valとvarを使用して、クラスのパブリックプロパティを定義できます。

不変値と可変値の両方を含むクラスを定義しましょう。

class ImmutabilityCar(color: String, val wheels: Int, var engine: String) {
  def call(): Unit = {
    println(s"This is a car with $color paint, $wheels wheels, and $engine engine")
  }
}

ImmutabilityCar クラスには、次の3つのプロパティカテゴリがあることがわかります。

  • プライベートカラー
  • パブリック不変ホイール
  • パブリック可変エンジン

オブジェクトを作成して各プロパティの制限を調べ、それを使って何ができるかを見てみましょう。

val myCar = new ImmutabilityCar("blue", 4, "diesel")
myCar.call()

それらは不変であるため、 colorおよびwheelsの値を変更することはできず、次のようなコンパイルエラーが発生します。

myCar.color = "green"
myCar.wheels = 5

engine の値は変更可能なプロパティであるため、変更できるのは次のとおりです。

myCar.engine = "electric"
assert(myCar.engine == "electric")
myCar.call()

これは以下を出力します:

This is a car with blue paint, 4 wheels, and electric engine

4. コレクションの可変性

Scalaコレクションライブラリは、不変および可変のコレクションを提供します。 いくつかの例を試してみましょう。

4.1. 不変のコレクション

不変のコレクションタイプは、パッケージscala.collection.immutablesで定義されています。 ただし、 scala パッケージにはエイリアスがあるため、追加のインポートなしですぐに使用できます。

不変のコレクションSeqを変数に割り当てましょう。

val pets = Seq("Cat", "Dog")

不変のコレクションは、それらに変更を加える演算子を提供しているように見えます。 これらの演算子は、変更が適用された新しいコレクションを返すため、不変性を維持します。 元のコレクションは変更されません。

val myPets = pets :+ "Hamster"
val notPets = pets ++ List("Giraffe", "Elephant") 
val yourPets = pets.updated(0, "Mice")

assert(pets == Seq("Cat", "Dog"))
assert(myPets == Seq("Cat", "Dog", "Hamster"))
assert(notPets == Seq("Cat", "Dog", "Giraffe", "Elephant"))
assert(yourPets == Seq("Mice", "Dog"))

ここでは、 pets は変更されておらず、 myPets notPets 、およびyourPetsに新しいコレクションが割り当てられています。

4.2. 可変コレクション

可変コレクションを定義するには、scala.collection.mutableからインポートする必要があります。

可変コレクションArrayBufferを使用してみましょう。

import scala.collection.mutable.ArrayBuffer

val breakfasts = ArrayBuffer("Sandwich", "Salad")

可変コレクションから要素を追加、更新、または削除できます。

コレクションに要素を追加することから始めましょう:

breakfasts += "Bagels"
assert(breakfasts == ArrayBuffer("Sandwich", "Salad", "Bagels"))

breakfasts ++= Seq("PB & J", "Pancake")
assert(breakfasts == ArrayBuffer("Sandwich", "Salad", "Bagels", "PB & J", "Pancake"))

これにより、の内容が変更されます朝食 、追加ベーグル、PB& J、およびパンケーキ

朝食の要素を更新することもできます。

breakfasts.update(2, "Steak")
assert(breakfasts == ArrayBuffer("Sandwich", "Salad", "Steak", "PB & J", "Pancake"))

インデックス番号2の要素をSteakに変更します。

朝食の要素を削除することもできます。

breakfasts -= "PB & J"
assert(breakfasts == ArrayBuffer("Sandwich", "Salad", "Steak", "Pancake"))

breakfasts -= "Fried rice"
assert(breakfasts == ArrayBuffer("Sandwich", "Salad", "Steak", "Pancake"))

-= を使用して、コレクションから要素が存在する場合はそれを削除できることに注意してください。

Array などの一部の可変コレクションでは、要素を追加または削除できません。 コレクション内の要素のみを更新できます。

val lunches = Array("Pasta", "Rice", "Hamburger")
lunches.update(0, "Noodles")
assert(lunches sameElements Array("Noodles", "Rice", "Hamburger"))

5. 可変性のベストプラクティス

関数型プログラミングには、参照透過性と呼ばれる概念があります。

プログラムの動作を変更せずにその値に置き換えることができる場合、式は参照透過性です。 これは、不変オブジェクトを使用することで実現できます。 そして、これが不変オブジェクトがScalaの第一級市民である理由です。

不変オブジェクトを使用すると、並行性を安全に使用できます。 これは、変更できない場合でも、複数のスレッドが同じデータに同時にアクセスする危険がないためです。

ただし、不変のオブジェクトとコレクションを使用すると、パフォーマンスの問題が発生する可能性があります。 これまで見てきたように、要素を追加するためにコレクションの新しいコピーを作成することは、既存のコレクションを変更するよりも効率的ではありません。

したがって、必要な場合は、代わりに可変オブジェクトを使用できます。

6. 結論

この記事では、Scalaでの可変性について説明しました。

まず、Scalaの不変変数と可変変数、およびそれがクラスプロパティにどのようにマッピングされるかを調べました。 次に、不変および可変のコレクションとその使用方法を調べました。

最後に、Scalaでの可変性のベストプラクティスについて説明しました。

いつものように、この記事のサンプルコードは、GitHubから入手できます。