ความแตกต่างระหว่างคำจำกัดความ var และ val ใน Scala คืออะไร?


301

อะไรคือความแตกต่างระหว่าง a varและvalคำจำกัดความใน Scala และทำไมภาษาจึงต้องการทั้งสอง ทำไมคุณถึงเลือกvalมากกว่าvarและในทางกลับกัน?

คำตอบ:


331

อย่างที่คนอื่น ๆ พูดไปแล้ววัตถุที่มอบหมายให้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ฯลฯ ) คุณสามารถมั่นใจได้ว่าสิ่งนี้จะไม่เกิดขึ้น นั่นคือถ้าไม่มีรหัสบางอย่างบางทีแม้แต่กรอบการทำเทคนิคการสะท้อน - การสะท้อนสามารถเปลี่ยนค่า "ไม่เปลี่ยนรูป" โชคไม่ดี

นั่นเป็นเหตุผลหนึ่ง แต่ก็มีอีกเหตุผลหนึ่ง เมื่อคุณใช้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รหัสนี้ยังคงมีประสิทธิภาพไม่จำเป็นต้องเรียกซ้ำและคุณไม่จำเป็นต้องกังวลว่าคุณจะต้องทำสำเนาของคิวของคุณหรือไม่ก่อนที่จะเรียก โดยธรรมชาติแล้วฉันหลีกเลี่ยงการใช้ตัวแปรซ้ำเพื่อวัตถุประสงค์อื่นและไม่มีโค้ดใดนอกฟังก์ชั่นนี้ที่เห็นพวกเขาดังนั้นฉันไม่ต้องกังวลเกี่ยวกับการเปลี่ยนแปลงค่าของพวกเขาจากบรรทัดหนึ่งไปเป็นบรรทัดถัดไป - ยกเว้นเมื่อฉันทำอย่างชัดเจน

สกาล่าเลือกที่จะให้โปรแกรมเมอร์ทำเช่นนั้นหากโปรแกรมเมอร์เห็นว่าเป็นทางออกที่ดีที่สุด ภาษาอื่น ๆ ได้เลือกที่จะทำให้รหัสนั้นยาก ราคาสกาล่า (และภาษาใด ๆ ที่มีความไม่แน่นอนอย่างแพร่หลาย) จ่ายคือคอมไพเลอร์ไม่มีวิธีการเพิ่มประสิทธิภาพโค้ดมากเท่าที่ควร คำตอบของจาวาคือการปรับรหัสให้เหมาะสมตามโปรไฟล์ของเวลาทำงาน เราสามารถดำเนินต่อไปเรื่อย ๆ เกี่ยวกับข้อดีข้อเสียของแต่ละด้าน

โดยส่วนตัวแล้วฉันคิดว่าสกาล่าจะทำให้เกิดสมดุลในตอนนี้ มันไม่สมบูรณ์แบบเลยทีเดียว ฉันคิดว่าทั้งClojureและHaskellมีแนวคิดที่น่าสนใจที่ไม่ได้รับการรับรองจาก Scala แต่ Scala มีจุดแข็งของตัวเองเช่นกัน เราจะเห็นสิ่งที่เกิดขึ้นในอนาคต


สายไปนิดหน่อย แต่ ... var qr = qทำสำเนาqไหม?

5
@davips qมันไม่ได้ทำสำเนาของวัตถุอ้างอิงโดย มันทำสำเนา - บนสแต็กไม่ใช่ฮีป - ของการอ้างอิงไปยังวัตถุนั้น สำหรับประสิทธิภาพคุณจะต้องชัดเจนมากขึ้นเกี่ยวกับสิ่งที่ "พูด" ที่คุณพูด
Daniel C. Sobral

ตกลงด้วยความช่วยเหลือของคุณและข้อมูลบางส่วน ( (x::xs).drop(1)ตรงxsไม่ใช่ลิงก์ "คัดลอก" ของxs) จากที่นี่ฉันเข้าใจ TNX!

"รหัสนี้ยังคงมีประสิทธิภาพ" - ใช่ไหม เนื่องจากqrเป็นคิวที่ไม่เปลี่ยนรูปทุกครั้งที่นิพจน์qr.dequeueถูกเรียกมันจะทำให้new Queue(ดู < github.com/scala/scala/blob/2.13.x/src/library/scala/collection/ ...... )
โอเว่น

@wen ใช่ แต่สังเกตว่ามันเป็นวัตถุตื้น รหัสยังคงเป็น O (n) ไม่ว่าจะไม่แน่นอนถ้าคุณคัดลอกคิวหรือไม่เปลี่ยนรูป
Daniel C. Sobral

61

valเป็นขั้นสุดท้ายนั่นคือไม่สามารถตั้งค่าได้ คิดfinalในภาษาจาวา


3
แต่ถ้าฉันเข้าใจอย่างถูกต้อง (ไม่ใช่ผู้เชี่ยวชาญ Scala) val ตัวแปรจะไม่เปลี่ยนรูป แต่วัตถุที่พวกเขาอ้างอิงไม่จำเป็นต้องเป็น อ้างอิงจากลิงค์ Stefan ที่โพสต์: "ที่นี่การอ้างอิงชื่อไม่สามารถเปลี่ยนเป็นชี้ไปที่ Array อื่นได้ แต่อาเรย์นั้นสามารถแก้ไขได้เองในคำอื่น ๆ เนื้อหา / องค์ประกอบของอาเรย์สามารถแก้ไขได้" ดังนั้นจึงเป็นเหมือนวิธีการfinalทำงานใน Java
Daniel Pryden

3
ทำไมฉันโพสต์มันเป็นอย่างแน่นอน ฉันสามารถโทร+=ใน HashMap mutabled กำหนดให้เป็นvalเพียงแค่ปรับผมเชื่อว่าว่าวิธีfinalการทำงานใน java
แจ็คสันเดวิส

ตอบฉันคิดว่าประเภทสกาล่าในตัวสามารถทำได้ดีกว่าการอนุญาตให้มอบหมายอีกครั้ง ฉันต้องตรวจสอบข้อเท็จจริง
Stefan Kendall

ฉันสับสนประเภทลำดับที่ไม่เปลี่ยนรูปของสกาล่าด้วยความคิดทั่วไป การเขียนโปรแกรมที่ใช้งานได้ทำให้ฉันหันไปรอบ ๆ
Stefan Kendall

ฉันเพิ่มและลบตัวละครหุ่นในคำตอบของคุณเพื่อที่ฉันจะได้ให้ upvote
Stefan Kendall


20

ความแตกต่างคือvarสามารถกำหนดใหม่ได้ในขณะที่valไม่สามารถ ความไม่แน่นอนหรืออื่น ๆ ที่ได้รับมอบหมายเป็นปัญหาด้าน:

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.

หากคุณกำลังสร้างโครงสร้างข้อมูลและเขตข้อมูลทั้งหมดเป็นvals ดังนั้นโครงสร้างข้อมูลนั้นจึงไม่เปลี่ยนรูปเนื่องจากสถานะไม่สามารถเปลี่ยนแปลงได้


1
นั่นเป็นเรื่องจริงถ้าคลาสของฟิลด์เหล่านั้นไม่เปลี่ยนรูปเช่นกัน
Daniel C. Sobral

ใช่ - ฉันจะใส่มันเข้าไป แต่ฉันคิดว่ามันอาจจะไกลไปหน่อย! นอกจากนี้ยังเป็นจุดที่พิสูจน์ได้ฉันจะพูด; จากมุมมองหนึ่ง (แม้ว่าไม่ใช่ฟังก์ชันที่ใช้งานได้) สถานะของมันจะไม่เปลี่ยนแปลงแม้ว่าสถานะของมันจะเป็นเช่นไร
oxbow_lakes

ทำไมมันยังคงยากที่จะสร้างวัตถุที่ไม่เปลี่ยนรูปแบบในภาษา JVM? นอกจากนี้ทำไม Scala ไม่ทำให้วัตถุไม่เปลี่ยนรูปโดยค่าเริ่มต้น
Derek Mahar

19

val หมายถึงไม่เปลี่ยนรูปและ varหมายถึงไม่แน่นอน

การสนทนาแบบเต็ม


1
นี่ไม่เป็นความจริงเลย บทความที่เชื่อมโยงให้อาร์เรย์ที่ไม่แน่นอนและเรียกมันไม่เปลี่ยนรูป ไม่มีแหล่งร้ายแรง
Klaus Schulz

ไม่จริงแน่นอน ลองใช้ val b = Array [Int] (1,2,3) b (0) = 4 println (b.mkString (""))) println ("")
Guillaume

13

คิดในแง่ของ C ++

val x: T

คล้ายกับตัวชี้คงที่กับข้อมูลที่ไม่คงที่

T* const x;

ในขณะที่

var x: T 

คล้ายกับตัวชี้แบบไม่คงที่กับข้อมูลที่ไม่คงที่

T* x;

นิยมvalมากกว่าvarการเพิ่มขึ้นของการไม่เปลี่ยนรูปของ codebase ที่สามารถอำนวยความสะดวกในความถูกต้องของการทำงานพร้อมกันและทำความเข้าใจได้

เพื่อทำความเข้าใจความหมายของการมีตัวชี้ค่าคงที่ไปยังข้อมูลที่ไม่คงที่ให้พิจารณาข้อมูลโค้ด 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)

1
ฉันคิดว่า Scala ไม่ได้ใช้ข้อสรุปที่ไม่สามารถเปลี่ยนแปลงได้สุดท้าย: ตัวชี้คงที่และข้อมูลคงที่ สกาล่าพลาดโอกาสที่จะทำให้วัตถุไม่เปลี่ยนรูปโดยค่าเริ่มต้น ดังนั้นสกาล่าไม่ได้มีค่าความคิดเช่นเดียวกับ Haskell
Derek Mahar

@DerekMahar คุณถูกต้อง แต่วัตถุสามารถแสดงตัวเองว่าไม่เปลี่ยนรูปได้อย่างสมบูรณ์แบบในขณะที่ยังคงใช้ความไม่แน่นอนในการนำไปใช้เช่นเหตุผลด้านประสิทธิภาพ คอมไพเลอร์จะแยกความแตกต่างระหว่างความแปรปรวนจริงกับความแปรปรวนภายในเท่านั้นได้อย่างไร
francoisr

8

"val หมายถึงไม่เปลี่ยนแปลงและ var แปลว่าไม่แน่นอน"

ในการถอดความ "val หมายถึงค่าและ var หมายถึงตัวแปร"

ความแตกต่างที่เกิดขึ้นมีความสำคัญอย่างยิ่งในการคำนวณ (เพราะทั้งสองแนวคิดกำหนดสาระสำคัญของการเขียนโปรแกรมทั้งหมดเกี่ยวกับ) และ OO มีการจัดการที่จะเบลอเกือบสมบูรณ์เพราะใน OO ความจริงเพียงอย่างเดียวคือ วัตถุ". และนั่นก็คือผู้เขียนโปรแกรมจำนวนมากในทุกวันนี้มักจะไม่เข้าใจ / ซาบซึ้ง / รับรู้เพราะพวกเขาถูกล้างสมองให้เป็น บ่อยครั้งที่นำไปสู่ตัวแปร / วัตถุที่เปลี่ยนแปลงได้ซึ่งถูกนำมาใช้เหมือนทุกหนทุกแห่งเมื่อคุณค่า / วัตถุที่เปลี่ยนแปลงไม่ได้อาจจะ / ดีกว่า


นี่คือเหตุผลที่ฉันชอบ Haskell มากกว่า Java ตัวอย่างเช่น
Derek Mahar

6

val หมายถึงไม่เปลี่ยนรูปและ var หมายถึงไม่แน่นอน

คุณสามารถคิดได้ว่าvalเป็นfinalโลกแห่งการเขียนโปรแกรมภาษาจาวาหรือ c ++ ภาษาconstหลักโลก。


1

Valหมายถึงขั้นสุดท้ายไม่สามารถกำหนดใหม่ได้

ในขณะที่Varสามารถมีพระราชเสาวนีย์ในภายหลัง


1
คำตอบนี้ต่างจาก 12 คำตอบที่ส่งไปแล้วอย่างไร
jwvh

0

มันง่ายเหมือนชื่อ

var หมายความว่าสามารถเปลี่ยนแปลงได้

วาลหมายถึงคงที่


0

Val - ค่าเป็นค่าคงที่ของการจัดเก็บข้อมูลที่พิมพ์ เมื่อสร้างมูลค่าแล้วจะไม่สามารถกำหนดใหม่ได้ ค่าใหม่สามารถกำหนดได้ด้วยคีย์เวิร์ด val

เช่น. val x: Int = 5

ประเภทนี้เป็นทางเลือกเนื่องจากสกาล่าสามารถอนุมานได้จากค่าที่กำหนด

ตัวแปร - คือหน่วยเก็บข้อมูลที่พิมพ์ซึ่งสามารถกำหนดค่าได้อีกครั้งตราบใดที่พื้นที่หน่วยความจำถูกสงวนไว้

เช่น. var x: Int = 5

ข้อมูลที่เก็บไว้ในทั้งสองหน่วยเก็บข้อมูลจะถูกยกเลิกการจัดสรรโดย JVM โดยอัตโนมัติเมื่อไม่ต้องการใช้อีกต่อไป

ในค่าสกาล่าเป็นที่ต้องการมากกว่าตัวแปรเนื่องจากความมั่นคงเหล่านี้นำมาสู่รหัสโดยเฉพาะอย่างยิ่งในรหัสพร้อมกันและแบบมัลติเธรด


0

แม้ว่าหลายคนจะตอบความแตกต่างระหว่างValและvarแล้ว แต่สิ่งหนึ่งที่สังเกตได้คือval นั้นไม่เหมือนกับคำค้นหาสุดท้าย

เราสามารถเปลี่ยนค่าของ val โดยใช้การเรียกซ้ำ แต่เราไม่สามารถเปลี่ยนค่าสุดท้ายได้ สุดท้ายคือค่าคงที่มากกว่า Val

def factorial(num: Int): Int = {
 if(num == 0) 1
 else factorial(num - 1) * num
}

พารามิเตอร์ของเมธอดจะเป็นค่าเริ่มต้นและทุกค่าการโทรจะถูกเปลี่ยน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.