Scalaにおけるvarとvalの違い

2977 ワード

この2つの違いを考えると、多くの人が最初に反応したのは、var修飾の変数は変更でき、val修飾の変数は変更できないことだ.でも本当にそうなの?実際、var修飾のオブジェクト参照は変更でき、val修飾は変更できませんが、オブジェクトの状態は変更できます.例:
class A(n: Int) {
  var value = n
}

class B(n: Int) {
  val value = new A(n)
}

object Test {
  def main(args: Array[String]) {
    val x = new B(5)
    x = new B(6) //   ,   x   val    ,      
    x.value = new A(6) //   ,   x.value   val    ,      
    x.value.value = 6 //   ,x.value.value  var    ,      
  }
}

変数の不変性には多くのメリットがあります.
1つは、1つのオブジェクトが内部の状態を変更したくない場合、不変性のため、プログラムの他の部分がオブジェクトの状態を変更する心配はありません.たとえば
x = new B(0)
f(x)
if (x.value.value == 0)
  println("f didn't do anything to x")
else
  println("f did something to x")

これは、マルチスレッド(マルチプロセス)のシステムにとって特に重要です.マルチスレッドシステムでは、次のことが起こります.
x = new B(1)
f(x)
if (x.value.value == 1) {
  print(x.value.value) // Can be different than 1!
}

valのみを使用し、可変のデータ構造のみを使用する場合(arrays、scala.collection.mutable内の何も使用しないなど)、安心して発生しません.フレームなどのコードがない限り、反射は可変の値を変えることができます.
2つ目は、varで修飾する場合、var修飾の変数を複数の場所で再利用する可能性があります.これにより、次の問題が発生します.
  • コードを読む人にとって、コードの決定部分で変数の値を知るのは難しい.
  • コードを使用する前にコードを初期化する可能性があります.これにより、エラーが発生します.

  • 従って、簡単に言えば、valの使用は、コードの可読性を安全かつ強化するものである.
    valにこんなに多くのメリットがある以上、なぜvarを使うのか(あるいは存在する).確かに、valのみが存在するプログラミング言語もありますが、可変性を使用するとプログラムの実行効率が大幅に向上する場合があります.
    たとえば、可変Queueの場合、キューに対してenqueueおよびdequeue操作を行うたびに、新しいQueueオブジェクトが得られます.では、すべてのアイテムをどのように処理しますか?
    次に例を用いて説明する.Intタイプのキューを想定し、キュー内のすべての数字を組み合わせます.例えば、キューの要素が1,2,3であれば、組み合わせられた数字は123である.次は第1の解決策です.mutableを採用しています.Queue
    def toNum(q: scala.collection.mutable.Queue[Int]) = {
      var num = 0
      while (!q.isEmpty) {
        num *= 10
        num += q.dequeue
      }
      num
    }
    

    上記のコードは読みやすく理解しやすいが、ソースデータが変更されるという主な問題があるため、toNumメソッドを呼び出す前にソースデータをコピーし、ソースデータの汚染を回避しなければならない.このとき,オブジェクトを不変性管理する方法である.
    次にimmutableを採用する.Queue :
    def toNum(q: scala.collection.immutable.Queue[Int]) = {
      def recurse(qr: scala.collection.immutable.Queue[Int], num: Int): Int = {
        if (qr.isEmpty)
          num
        else {
          val (digit, newQ) = qr.dequeue
          recurse(newQ, num * 10 + digit)
        }
      }
      recurse(q, 0)
    }
    

    numは、前の例のように値を再割り当てできないため、再帰を使用する必要があります.これはテール再帰で、性能がいいです.しかし、状況は必ずしもそうではありません.良い(読み取り可能で簡単な)テール再帰ソリューションがない場合があります.
    以下immutableを採用する.Queueとmutable.Queueコードの書き換え:
    def toNum(q: scala.collection.immutable.Queue[Int]) = {
      var qr = q
      var num = 0
      while (!qr.isEmpty) {
        val (digit, newQ) = qr.dequeue
        num *= 10
        num += digit
        qr = newQ
      }
      num
    }
    

    このコードは非常に有効で、再帰する必要はありません.toNumを呼び出す前にキューをコピーする必要があるかどうかを心配する必要はありません.もちろん、他の用途で変数を再利用することは避けました.この関数以外にコードが表示(修正)されていないので、コードの他の場所で値が発生することを心配する必要はありません.私が明確にしない限り.
    プログラマが何らかのソリューションが最良のソリューションだと思っている場合、Scalaはプログラマにそれを許可します.他の言語になるには、このような柔軟性はありません.Scala(および広範な可変性を有する言語)の代価は、コンパイラがコードの最適化に十分な柔軟性を持っていないことである.Javaの提供ソリューションは、実行時に基づいてコードを最適化することです.