Sự khác biệt giữa một định nghĩa var và val trong Scala là gì?


301

Sự khác biệt giữa a varvalđịnh nghĩa trong Scala là gì và tại sao ngôn ngữ cần cả hai? Tại sao bạn chọn một valhơn một varvà ngược lại?

Câu trả lời:


331

Như rất nhiều người khác đã nói, đối tượng được gán cho valkhông thể được thay thế và đối tượng được gán cho một hộp var. Tuy nhiên, đối tượng cho biết có thể có trạng thái nội bộ của nó được sửa đổi. Ví dụ:

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.
  }
}

Vì vậy, mặc dù chúng tôi không thể thay đổi đối tượng được gán x, chúng tôi có thể thay đổi trạng thái của đối tượng đó. Tại gốc của nó, tuy nhiên, đã có mộtvar .

Bây giờ, bất biến là một điều tốt vì nhiều lý do. Đầu tiên, nếu một đối tượng không thay đổi trạng thái bên trong, bạn không phải lo lắng nếu một phần khác trong mã của bạn thay đổi nó. Ví dụ:

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")

Điều này trở nên đặc biệt quan trọng với các hệ thống đa luồng. Trong một hệ thống đa luồng, điều sau đây có thể xảy ra:

x = new B(1)
f(x)
if (x.value.value == 1) {
  print(x.value.value) // Can be different than 1!
}

Nếu bạn sử dụng valriêng và chỉ sử dụng các cấu trúc dữ liệu không thay đổi (nghĩa là tránh các mảng, mọi thứ trong scala.collection.mutable, v.v.), bạn có thể yên tâm điều này sẽ không xảy ra. Đó là, trừ khi có một số mã, thậm chí có thể là một khung, thực hiện các thủ thuật phản chiếu - sự phản chiếu có thể thay đổi các giá trị "bất biến", thật không may.

Đó là một lý do, nhưng có một lý do khác cho nó. Khi bạn sử dụng var, bạn có thể bị cám dỗ sử dụng lại cho cùng một varmục đích. Điều này có một số vấn đề:

  • Mọi người sẽ khó đọc mã hơn để biết giá trị của biến trong một phần nhất định của mã là gì.
  • Bạn có thể quên khởi tạo lại biến trong một số đường dẫn mã và cuối cùng chuyển các giá trị sai xuống dòng trong mã.

Nói một cách đơn giản, sử dụng valsẽ an toàn hơn và dẫn đến mã dễ đọc hơn.

Chúng ta có thể, sau đó, đi theo hướng khác. Nếu valđiều đó là tốt hơn, tại sao có vartất cả? Chà, một số ngôn ngữ đã đi theo con đường đó, nhưng có nhiều tình huống trong đó tính đột biến giúp cải thiện hiệu suất, rất nhiều.

Ví dụ, lấy một bất biến Queue. Khi bạn enqueuehoặc dequeuenhững thứ trong đó, bạn sẽ có một cái mớiQueue đối tượng . Sau đó, bạn sẽ xử lý tất cả các mục trong đó như thế nào?

Tôi sẽ trải qua điều đó với một ví dụ. Giả sử bạn có một hàng chữ số và bạn muốn soạn một số trong số chúng. Ví dụ: nếu tôi có một hàng đợi với 2, 1, 3, theo thứ tự đó, tôi muốn lấy lại số 213. Trước tiên, hãy giải quyết nó bằng mutable.Queue:

def toNum(q: scala.collection.mutable.Queue[Int]) = {
  var num = 0
  while (!q.isEmpty) {
    num *= 10
    num += q.dequeue
  }
  num
}

Mã này nhanh và dễ hiểu. Hạn chế chính của nó là hàng đợi được thông qua đã được sửa đổi toNum, vì vậy bạn phải tạo một bản sao của nó trước đó. Đó là kiểu quản lý đối tượng mà tính bất biến giúp bạn thoát khỏi.

Bây giờ, hãy chuyển đổi nó thành 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)
}

Bởi vì tôi không thể sử dụng lại một số biến để theo dõi num , như trong ví dụ trước, tôi cần phải sử dụng đệ quy. Trong trường hợp này, nó là một đệ quy đuôi, có hiệu suất khá tốt. Nhưng điều đó không phải lúc nào cũng đúng: đôi khi không có giải pháp đệ quy đuôi tốt (dễ đọc, đơn giản).

Tuy nhiên, lưu ý rằng tôi có thể viết lại mã đó để sử dụng cùng lúc immutable.Queuevà một var! Ví dụ:

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
}

Mã này vẫn hiệu quả, không yêu cầu đệ quy và bạn không cần lo lắng liệu bạn có phải tạo một bản sao của hàng đợi hay không trước khi gọi toNum. Đương nhiên, tôi tránh sử dụng lại các biến cho các mục đích khác và không có mã nào ngoài hàm này nhìn thấy chúng, vì vậy tôi không cần phải lo lắng về việc giá trị của chúng thay đổi từ dòng này sang dòng tiếp theo - ngoại trừ khi tôi rõ ràng làm như vậy.

Scala đã chọn để cho lập trình viên làm điều đó, nếu lập trình viên coi đó là giải pháp tốt nhất. Các ngôn ngữ khác đã chọn để làm cho mã như vậy khó khăn. Cái giá mà Scala (và bất kỳ ngôn ngữ nào có khả năng biến đổi rộng rãi) phải trả là trình biên dịch không mất nhiều thời gian trong việc tối ưu hóa mã như có thể. Câu trả lời của Java cho điều đó là tối ưu hóa mã dựa trên cấu hình thời gian chạy. Chúng ta có thể tiếp tục và về những ưu và nhược điểm cho mỗi bên.

Cá nhân, tôi nghĩ bây giờ Scala đạt được sự cân bằng phù hợp. Nó không hoàn hảo, cho đến nay. Tôi nghĩ rằng cả ClojureHaskell đều có những khái niệm rất thú vị không được Scala chấp nhận, nhưng Scala cũng có những thế mạnh riêng. Chúng ta sẽ thấy những gì sẽ xảy ra trong tương lai.


Hơi muộn một chút, nhưng ... Có var qr = qtạo ra một bản sao qkhông?

5
@davips Nó không tạo một bản sao của đối tượng được tham chiếu bởi q. Nó tạo một bản sao - trên ngăn xếp, không phải là đống - của tham chiếu đến đối tượng đó. Về hiệu suất, bạn sẽ phải nói rõ hơn về "nó" mà bạn đang nói đến.
Daniel C. Sobral

Ok, với sự giúp đỡ của bạn và một số thông tin ( (x::xs).drop(1)là chính xác xs, không phải là một "bản sao" của xs) từ đây liên kết tôi có thể hiểu được. tnx!

"Mã này vẫn hiệu quả" - phải không? Vì qrlà một hàng đợi không thay đổi, nên mỗi khi biểu thức qr.dequeueđược gọi, nó sẽ tạo ra một new Queue(xem < github.com/scala/scala/blob/2.13.x/src/l Library / scala / film / sắt ).
Owen

@Owen Có, nhưng lưu ý rằng đó là một đối tượng nông. Mã vẫn là O (n) cho dù có thể thay đổi, nếu bạn sao chép hàng đợi hoặc không thay đổi.
Daniel C. Sobral

61

vallà cuối cùng, nghĩa là không thể được thiết lập. Hãy suy nghĩ finaltrong java.


3
Nhưng nếu tôi hiểu chính xác (không phải là chuyên gia Scala), val các biến là bất biến, nhưng các đối tượng mà chúng tham chiếu không phải là. Theo liên kết Stefan đã đăng: "Ở đây không thể thay đổi tham chiếu tên để trỏ đến một Mảng khác, nhưng bản thân mảng có thể được sửa đổi. Nói cách khác, nội dung / thành phần của mảng có thể được sửa đổi." Vì vậy, nó giống như cách làm finalviệc trong Java.
Daniel Pryden

3
Chính xác là tại sao tôi đăng nó như vậy. Tôi có thể gọi +=một hashmap bị biến đổi được định nghĩa là valổn - tôi tin chính xác nó finalhoạt động như thế nào trong java
Jackson Davis

Ack, tôi nghĩ rằng các loại scala tích hợp có thể làm tốt hơn là chỉ cho phép gán lại. Tôi cần kiểm tra thực tế.
Stefan Kendall

Tôi đã nhầm lẫn các loại Sequence bất biến của scala với khái niệm chung. Lập trình chức năng có tất cả tôi quay lại.
Stefan Kendall

Tôi đã thêm và xóa một ký tự giả trong câu trả lời của bạn để tôi có thể cung cấp cho bạn upvote.
Stefan Kendall

55

Nói một cách đơn giản:

var = var iable

val = v khả thi + vây al


20

Sự khác biệt là a varcó thể được gán lại trong khi valkhông thể. Khả năng biến đổi, hay nói cách khác là bất cứ điều gì thực sự được chỉ định, là một vấn đề phụ:

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.

Trong khi:

val n = immutable.Set("London", "Paris")
n = immutable.Set("New York") //Will not compile as n is a val.

Và do đó:

val n = mutable.Set("London", "Paris")
n = mutable.Set("New York") //Will not compile, even though the type of n is mutable.

Nếu bạn đang xây dựng một cấu trúc dữ liệu và tất cả các trường của nó là vals, thì cấu trúc dữ liệu đó là bất biến, vì trạng thái của nó không thể thay đổi.


1
Điều đó chỉ đúng nếu các lớp của các trường đó là bất biến.
Daniel C. Sobral

Vâng - tôi sẽ đặt nó vào nhưng tôi nghĩ nó có thể là một bước quá xa! Đó cũng là một điểm có thể tranh cãi mà tôi muốn nói; từ một quan điểm (mặc dù không phải là một chức năng) trạng thái của nó không thay đổi ngay cả khi trạng thái của nó.
oxbow_lakes

Tại sao vẫn còn quá khó để tạo một đối tượng bất biến trong ngôn ngữ JVM? Hơn nữa, tại sao Scala không làm cho các đối tượng bất biến theo mặc định?
Derek Mahar

19

valcó nghĩa là bất biến và varcó nghĩa là đột biến.

Thảo luận đầy đủ.


1
Đây chỉ là không đúng sự thật. Bài viết được liên kết đưa ra một mảng có thể thay đổi và gọi nó là bất biến. Không có nguồn nghiêm trọng.
Klaus Schulz

Thật sự không đúng. Hãy thử val b = Array [Int] (1,2,3) b (0) = 4 println (b.mkString ("")) println ("")
Guillaume

13

Suy nghĩ về C ++,

val x: T

tương tự như con trỏ không đổi với dữ liệu không đổi

T* const x;

trong khi

var x: T 

tương tự con trỏ không liên tục đến dữ liệu không liên tục

T* x;

Thích valhơnvar làm tăng tính bất biến của codebase mà có thể tạo điều kiện cho tính đúng đắn của nó, đồng thời và dễ hiểu.

Để hiểu ý nghĩa của việc có một con trỏ liên tục đến dữ liệu không liên tục, hãy xem xét đoạn trích Scala sau:

val m = scala.collection.mutable.Map(1 -> "picard")
m // res0: scala.collection.mutable.Map[Int,String] = HashMap(1 -> picard)

Ở đây "con trỏ" val m là hằng số vì vậy chúng ta không thể gán lại nó để trỏ đến một cái gì đó khác như vậy

m = n // error: reassignment to val

tuy nhiên chúng ta thực sự có thể thay đổi chính dữ liệu không cố định mà mchỉ ra như vậy

m.put(2, "worf")
m // res1: scala.collection.mutable.Map[Int,String] = HashMap(1 -> picard, 2 -> worf)

1
Tôi nghĩ Scala đã không đưa ra kết luận cuối cùng: con trỏ không đổi và dữ liệu không đổi. Scala đã bỏ lỡ một cơ hội để làm cho các đối tượng bất biến theo mặc định. Do đó, Scala không có cùng quan niệm về giá trị như Haskell.
Derek Mahar

@DerekMahar bạn đúng, nhưng một đối tượng có thể thể hiện chính nó là hoàn toàn bất biến, trong khi vẫn sử dụng khả năng biến đổi trong quá trình thực hiện, ví dụ như vì lý do hiệu suất. Làm thế nào trình biên dịch có thể phân tách giữa khả năng biến đổi thực sự và khả năng biến đổi chỉ nội bộ?
francoisr

8

"val có nghĩa là bất biến và var có nghĩa là đột biến."

Để diễn giải, "val có nghĩa là giá trị và var có nghĩa là biến".

Một sự khác biệt cực kỳ quan trọng trong điện toán (bởi vì hai khái niệm này xác định chính bản chất của việc lập trình là gì) và OO đã làm mờ gần như hoàn toàn, bởi vì trong OO, tiên đề duy nhất là "mọi thứ đều là một vật". Và đó là kết quả, rất nhiều lập trình viên ngày nay có xu hướng không hiểu / đánh giá cao / nhận ra, bởi vì họ đã bị tẩy não để "nghĩ theo cách OO" độc quyền. Thường dẫn đến các đối tượng biến / biến đổi được sử dụng như mọi nơi , khi các đối tượng giá trị / bất biến có thể / thường sẽ tốt hơn.


Đây là lý do mà tôi thích Haskell hơn Java chẳng hạn.
Derek Mahar

6

val có nghĩa là bất biến và var có nghĩa là đột biến

bạn có thể nghĩ valnhư ngôn ngữ lập trình java finalchính thế giới hoặc c ++ ngôn ngữ constchính trên thế giới.


1

Valcó nghĩa là cuối cùng của nó , không thể được chỉ định lại

Trong khi đó, Varcó thể được chỉ định lại sau .


1
Câu trả lời này khác với 12 câu trả lời đã được gửi như thế nào?
jwvh

0

Nó đơn giản như tên của nó.

var có nghĩa là nó có thể thay đổi

val có nghĩa là bất biến


0

Val - giá trị là hằng số lưu trữ gõ. Sau khi tạo giá trị của nó không thể được gán lại. một giá trị mới có thể được xác định với val từ khóa.

ví dụ. val x: Int = 5

Ở đây loại là tùy chọn vì scala có thể suy ra nó từ giá trị được gán.

Biến - biến là các đơn vị lưu trữ được nhập có thể được gán lại giá trị miễn là không gian bộ nhớ được dành riêng.

ví dụ. var x: Int = 5

Dữ liệu được lưu trữ trong cả hai đơn vị lưu trữ được JVM phân bổ tự động một khi chúng không còn cần thiết nữa.

Trong các giá trị scala được ưu tiên hơn các biến do tính ổn định, các giá trị này mang đến mã đặc biệt là mã đồng thời và đa luồng.


0

Mặc dù nhiều người đã trả lời sự khác biệt giữa Valvar . Nhưng một điểm cần chú ý là val không chính xác như từ khóa cuối cùng .

Chúng ta có thể thay đổi giá trị của val bằng cách sử dụng đệ quy nhưng chúng ta không bao giờ có thể thay đổi giá trị cuối cùng. Chung kết là không đổi hơn Val.

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

Các tham số phương thức theo val mặc định và tại mỗi giá trị cuộc gọi đang được thay đổi.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.