Sự khác biệt giữa a var
và val
đị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 val
hơn một var
và ngược lại?
Sự khác biệt giữa a var
và val
đị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 val
hơn một var
và ngược lại?
Câu trả lời:
Như rất nhiều người khác đã nói, đối tượng được gán cho val
khô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 val
riê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 var
mục đích. Điều này có một số vấn đề:
Nói một cách đơn giản, sử dụng val
sẽ 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ó var
tấ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 enqueue
hoặc dequeue
nhữ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.Queue
và 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ả Clojure và Haskell đề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.
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.
(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!
qr
là 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 ).
val
là cuối cùng, nghĩa là không thể được thiết lập. Hãy suy nghĩ final
trong java.
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 final
việc trong Java.
+=
một hashmap bị biến đổi được định nghĩa là val
ổn - tôi tin chính xác nó final
hoạt động như thế nào trong java
Nói một cách đơn giản:
var = var iable
val = v khả thi + vây al
Sự khác biệt là a var
có thể được gán lại trong khi val
khô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à val
s, 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.
val
có nghĩa là bất biến và var
có nghĩa là đột biến.
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 val
hơ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à m
chỉ ra như vậy
m.put(2, "worf")
m // res1: scala.collection.mutable.Map[Int,String] = HashMap(1 -> picard, 2 -> worf)
"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.
val có nghĩa là bất biến và var có nghĩa là đột biến
bạn có thể nghĩ val
như ngôn ngữ lập trình java final
chính thế giới hoặc c ++ ngôn ngữ const
chính trên thế giới.
Val
có nghĩa là cuối cùng của nó , không thể được chỉ định lại
Trong khi đó, Var
có thể được chỉ định lại sau .
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
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.
Mặc dù nhiều người đã trả lời sự khác biệt giữa Val và var . 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.
var qr = q
tạo ra một bản saoq
không?