回答:
他の多くの人が言ったように、val
缶に割り当てられたオブジェクトは置き換えることができず、缶に割り当てられたオブジェクトは置き換えられませんvar
。ただし、このオブジェクトは内部状態を変更できます。例えば:
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) // Doesn't work, because I can't replace the object created on the line above with this new one.
x.value = new A(6) // Doesn't work, because I can't replace the object assigned to B.value for a new one.
x.value.value = 6 // Works, because A.value can receive a new object.
}
}
したがって、に割り当てられたオブジェクトを変更できない場合でも、x
そのオブジェクトの状態を変更できます。ただし、その根本にはがありましたvar
。
現在、不変性は多くの理由から良いことです。まず、オブジェクトが内部状態を変更しない場合でも、コードの他の部分がそれを変更しているかどうかを心配する必要はありません。例えば:
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
、排他的、かつ唯一の不変のデータ構造(つまり、回避アレイ内のすべての使用scala.collection.mutable
など、)、あなたは、これは発生しませんので安心することができます。つまり、リフレクショントリックを実行するコード、おそらくフレームワークさえなければ、リフレクションは「不変」の値を変更する可能性があります。
それが理由の1つですが、それには別の理由があります。を使用var
するとvar
、複数の目的で同じものを再利用したくなることがあります。これにはいくつかの問題があります:
簡単に言えば、使用する方val
が安全で、コードが読みやすくなります。
そうすれば、別の方向に進むことができます。それval
がもっと良いのなら、なぜvar
全然持っているのですか?まあ、いくつかの言語はその道をたどりましたが、可変性がパフォーマンスを大幅に改善する状況があります。
たとえば、不変をとりQueue
ます。あなたenqueue
またはその中のいずれかdequeue
である場合、新しいQueue
オブジェクトを取得します。では、その中のすべてのアイテムをどのように処理しますか?
例を挙げて説明します。数字のキューがあり、それらから数字を作成したいとします。たとえば、2、1、3の順番のキューがある場合、213の数字を取得します。まず、次のように解決します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
とvar
同時に!例えば:
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
。また、を呼び出す前にキューのコピーを作成する必要があるかどうかを心配する必要はありません。当然、他の目的で変数を再利用することは避け、この関数の外のコードはそれらを認識しないため、明示的にそうしない限り、変数の値が1行から次の行に変化することを心配する必要はありません。
Scalaは、プログラマーがそれを最良のソリューションであると判断した場合に、プログラマーにそれを実行させることを選択しました。他の言語は、そのようなコードを困難にすることを選択しました。Scala(および広範囲の可変性を備えた任意の言語)が支払う代償は、コンパイラーがコードを最適化する際に、そうでない場合ほどの余裕がないことです。これに対するJavaの答えは、実行時プロファイルに基づいてコードを最適化することです。私たちはそれぞれの長所と短所についてどんどん進むことができました。
個人的には、現時点ではScalaは適切なバランスをとっていると思います。完璧ではありません。ClojureとHaskellの両方には、Scalaで採用されていない非常に興味深い概念があると思いますが、Scalaにも独自の強みがあります。今後の予定を確認します。
q
。そのオブジェクトへの参照のコピー(ヒープではなくスタック上)を作成します。パフォーマンスに関しては、あなたが言っている「それ」についてもっと明確にする必要があります。
qr
は不変のキューなので、式qr.dequeue
が呼び出されるたびにが作成されますnew Queue
(< github.com/scala/scala/blob/2.13.x/src/library/scala/collection/…を参照)。
val
最終的な、つまり設定できません。final
Javaで考えてください。
val
変数は不変ですが、変数が参照するオブジェクトはそうである必要はありません。Stefanが投稿したリンクによると、「ここでは、名前の参照を別の配列を指すように変更することはできませんが、配列自体は変更できます。つまり、配列の内容/要素は変更できます。」つまりfinal
、Javaでの動作のようなものです。
+=
として定義された可変ハッシュマップを呼び出すことができますval
final
違いは、aにvar
は再割り当てval
できるのに対し、aには再割り当てできないことです。変更可能性、または実際に割り当てられているものは何でも、副次的な問題です。
import collection.immutable
import collection.mutable
var m = immutable.Set("London", "Paris")
m = immutable.Set("New York") //Reassignment - I have change the "value" at m.
一方:
val n = immutable.Set("London", "Paris")
n = immutable.Set("New York") //Will not compile as n is a val.
それゆえ:
val n = mutable.Set("London", "Paris")
n = mutable.Set("New York") //Will not compile, even though the type of n is mutable.
データ構造を構築していて、そのフィールドがすべてval
sの場合、その状態は変更できないため、そのデータ構造は不変です。
C ++の観点から考えると、
val x: T
非定数データへの定数ポインタに類似しています
T* const x;
ながら
var x: T
非定数データへの非定数ポインタに類似しています
T* x;
有利val
オーバーvar
と、コードベースの不変性が高まり、コードベースの正確性、並行性、理解しやすさが向上します。
非定数データへの定数ポインターを持つことの意味を理解するには、次のScalaスニペットを検討してください。
val m = scala.collection.mutable.Map(1 -> "picard")
m // res0: scala.collection.mutable.Map[Int,String] = HashMap(1 -> picard)
ここで「ポインタ」 val m
は定数なので、別のものを指すように再割り当てすることはできません
m = n // error: reassignment to val
ただし、そのm
ように指す非定数データ自体を実際に変更できます
m.put(2, "worf")
m // res1: scala.collection.mutable.Map[Int,String] = HashMap(1 -> picard, 2 -> worf)
「valは不変を意味し、varは変更可能を意味します。」
言い換えると、「valは値を意味し、varは変数を意味します」。
コンピューティングで非常に重要な違い(これらの2つの概念はプログラミングの本質を明確に定義しているため)であり、OOはほぼ完全にぼやけてきました。OOでは、「すべてがオブジェクト」。そして、その結果として、最近の多くのプログラマーは、「オブジェクト指向の方法を考える」ことだけに洗脳されているため、理解/認識/認識しない傾向があります。多くの場合、あらゆる場所と同じように変数/可変オブジェクトが使用されますが、値/不変オブジェクトの方が優れている場合があります。
名前と同じくらい簡単です。
varは変化する可能性があることを意味します
valは不変を意味します
Val-値は型付き記憶定数です。いったん作成されると、その値を再度割り当てることはできません。新しい値はキーワードvalで定義できます。
例えば。val x:Int = 5
scalaは割り当てられた値から推測できるため、ここではタイプはオプションです。
Var-変数は型付きのストレージユニットであり、メモリスペースが予約されている限り、値を再度割り当てることができます。
例えば。var x:Int = 5
両方のストレージユニットに格納されたデータは、不要になるとJVMによって自動的に割り当て解除されます。
安定性のため、スカラーでは変数よりも値が推奨されます。これは、特に並行およびマルチスレッドコードでコードにもたらされます。
var qr = q
のコピーを作成しq
ますか?