2009-11-24 57 views

Trả lời

284

Như nhiều người khác đã nói, đối tượng được gán cho val không thể thay thế được và đối tượng được gán cho var có thể. Tuy nhiên, đối tượng nói có thể có trạng thái nội bộ đượ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 ta không thể thay đổi đối tượng giao cho x, chúng ta có thể thay đổi trạng thái của đối tượng đó. Tuy nhiên, tại gốc của nó, có một số var.

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 số phần khác của mã của bạn đang 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 hệ thống đa luồng.Trong một hệ thống đa luồng, 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 độc quyền, và chỉ sử dụng cấu trúc dữ liệu không thay đổi (nghĩa là, tránh mảng, tất cả mọi thứ trong scala.collection.mutable, vv), bạn có thể yên tâm won này không xảy ra. Đó là, trừ khi có một số mã, có lẽ ngay cả một khuôn khổ, làm thủ đoạn phản chiếu - sự phản ánh 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 cùng một số var cho nhiều mục đích. Điều này có một số vấn đề:

  • Sẽ khó khăn hơn cho những người đọc mã để biết giá trị của biến trong một phần nhất định của mã.
  • 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ưới dòng trong mã.

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

Chúng tôi có thể, sau đó, đi theo một hướng khác. Nếu val thì tốt hơn, tại sao lại có var? Vâng, một số ngôn ngữ đã sử dụng tuyến đường đó, nhưng có những tình huống mà tính năng đột biến cải thiện hiệu suất, rất nhiều.

Ví dụ: lấy Queue bất biến. Khi bạn enqueue hoặc dequeue thứ trong đó, bạn sẽ nhận được đối tượng Queue mới. Làm thế nào sau đó, bạn sẽ đi về chế biến tất cả các mục trong nó?

Tôi sẽ xem xét ví dụ đó. Giả sử bạn có hàng đợi các chữ số và bạn muốn soạn một số trong số đó. 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. Hãy đầu tiên giải quyết nó với một 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 được nhanh chóng và dễ dàng để hiểu không. Hạn chế chính của nó là hàng đợi được chuyển đổi được sửa đổi bởi toNum, vì vậy bạn phải tạo một bản sao của nó trước. Đó là loại quản lý đối tượng mà sự bất biến làm cho bạn thoát khỏi.

Bây giờ, chúng ta hãy bí mật nó vào một 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ể tái sử dụng một số biến để theo dõi các num tôi, như trong ví dụ trước, tôi cần phải nghỉ mát để đệ 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 đó không phải lúc nào cũng như vậy: đôi khi không có giải pháp đệ quy đuôi tốt (dễ đọc, đơn giản).

Lưu ý, tuy nhiên, tôi có thể viết lại mã đó để sử dụng immutable.Queuevar cùng một lúc! 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 còn hiệu quả, không đòi hỏi đệ quy, và bạn không cần phải lo lắng cho dù bạn phải thực hiện một bản sao của hàng đợi của bạn 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ã bên ngoài chức năng này nhìn thấy chúng, vì vậy tôi không cần phải lo lắng về giá trị của chúng thay đổi từ dòng này sang dòng khác - ngoại trừ khi tôi 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ã khó như vậy. Giá Scala (và bất kỳ ngôn ngữ nào có khả năng biến đổi phổ biến rộng rãi) trả tiền là trình biên dịch không có nhiều thời gian để tối ưu hóa mã như nó có thể khác. Câu trả lời của Java 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à khuyết điểm cho mỗi bên.

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

+0

Một chút trễ, nhưng ... Có 'var qr = q' tạo một bản sao của' q'? –

+0

Làm thế nào nó ảnh hưởng đến hiệu suất (thời gian cpu)? –

+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ó thực hiện một bản sao - trên ngăn xếp, không phải là đống - của _reference_ đối tượng đó. Đối với hiệu suất, bạn sẽ phải rõ ràng hơn về những gì "nó" bạn đang nói đến. –

19

val có nghĩa là không thay đổi và var có nghĩa là có thể thay đổi.

Full discussion.

+0

Điều này không đúng. Bài viết được liên kết cung cấp 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. –

+0

Không thực sự đúng. Hãy thử val b = Array [Int] (1,2,3) b (0) = 4 println (b.mkString ("")) println ("") –

50

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

+3

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

+3

Chính xác lý do tại sao tôi đăng nó như là. Tôi có thể gọi '+ =' trên một băm băm tắt được định nghĩa là 'val' tốt thôi - tôi tin chính xác cách' cuối cùng' hoạt động trong java –

+0

Ack, tôi nghĩ rằng các kiểu scala tích hợp có thể làm tốt hơn chỉ đơn giản là cho phép bài tập. Tôi cần kiểm tra sự thật. –

19

Sự khác biệt là có thể gán lại var trong khi không thể phân bổ val. Các đột biến, hoặc bất cứ điều gì là thực sự được giao, 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 đó:

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

Và vì thế:

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, do đó cấu trúc dữ liệu đó là không thay đổi được, 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 đó không thay đổi. –

+0

Vâng - tôi sẽ đặt nó vào nhưng tôi nghĩ nó có thể là một bước quá xa! Nó cũng là một điểm đáng tranh cãi tôi sẽ 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 trạng thái của nó. –

+0

Tại sao nó vẫn còn khó khăn như vậy để tạo ra một đối tượng bất biến trong một 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? –

8

"val nghĩa là bất biến và var có nghĩa là có thể thay đổi".

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

Sự phân biệt xảy ra cực kỳ quan trọng trong tính toán (vì hai khái niệm này xác định bản chất của lập trình), và OO đã quản lý mờ gần như hoàn toàn, vì trong OO, tiên đề duy nhất là "tất cả mọi thứ là một đối tượng". 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 thành "suy nghĩ theo cách OO" độc quyền. Thường dẫn đến các đối tượng biến/có thể thay đổi được sử dụng như ở mọi nơi, khi các đối tượng giá trị/không thay đổi có thể/thường sẽ tốt hơn.

+0

Đây là lý do mà tôi thích Haskell hơn Java, ví dụ. –

6

val có nghĩa là bất biến và var nghĩa có thể thay đổi

bạn có thể nghĩ val như ngôn ngữ lập trình java final thế giới hoặc c ngôn ngữ chính ++ const thế giới chính

37

Trong thuật ngữ đơn giản:.

var = var iable

val = v ariable + vây al

11

Suy nghĩ về C++,

val x: T 

là tương tự như con trỏ hằng số liệu không liên tục

T* const x; 

trong khi

var x: T 

là tương tự như con trỏ không thường xuyên để dữ liệu không liên tục

T* x; 

ưu val qua var 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.

+0

Tôi nghĩ rằng Scala đã không mất bất biến để kết luận cuối cùng của nó: con trỏ liên tục và dữ liệu liên tục. 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 khái niệm về giá trị như Haskell. –

1

Nó đơn giản như tên gọi.

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

val có nghĩa là bất di bất dịch

2

Một val cũng tương tự như một biến thức trong Java. Khi được khởi tạo, không thể gán lại val.

A var, ngược lại, tương tự như biến không phải cuối cùng trong Java. A var có thể được gán lại trong suốt cuộc đời của nó.

0

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

ví dụ: val x: Int = 5

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

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

ví dụ: var x: Int = 5

Dữ liệu được lưu trữ trong cả hai đơn vị lưu trữ sẽ tự động được phân bổ bởi JVM khi 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, chúng mang đến mã đặc biệt trong mã đồng thời và đa luồng.

Các vấn đề liên quan