2011-09-20 30 views
193

Tôi nhận thấy rằng Scala cung cấp lazy vals. Nhưng tôi không hiểu họ làm gì.Một val lười biếng làm gì?

scala> val x = 15 
x: Int = 15 

scala> lazy val y = 13 
y: Int = <lazy> 

scala> x 
res0: Int = 15 

scala> y 
res1: Int = 13 

Các REPL cho thấy y là một lazy val, nhưng làm thế nào là nó khác so với bình thường val?

Trả lời

267

Sự khác biệt giữa chúng là val được thực hiện khi được xác định trong khi lazy val được thực hiện khi được truy cập lần đầu tiên.

scala> val x = { println("x"); 15 } 
x 
x: Int = 15 

scala> lazy val y = { println("y"); 13 } 
y: Int = <lazy> 

scala> x 
res2: Int = 15 

scala> y 
y 
res3: Int = 13 

scala> y 
res4: Int = 13 

Ngược lại với một phương pháp (được định nghĩa với def) một lazy val được thực hiện một lần và sau đó không bao giờ trở lại. Điều này có thể hữu ích khi một hoạt động mất nhiều thời gian để hoàn thành và khi nó không chắc chắn nếu nó được sử dụng sau này.

scala> class X { val x = { Thread.sleep(2000); 15 } } 
defined class X 

scala> class Y { lazy val y = { Thread.sleep(2000); 13 } } 
defined class Y 

scala> new X 
res5: X = [email protected] // we have to wait two seconds to the result 

scala> new Y 
res6: Y = [email protected] // this appears immediately 

Ở đây, khi các giá trị xy không bao giờ được sử dụng, chỉ x nguồn lực lãng phí không cần thiết. Nếu chúng ta giả sử rằng y không có tác dụng phụ và chúng ta không biết tần suất nó được truy cập (không bao giờ, một lần, hàng nghìn lần) thì vô ích khi khai báo nó là def vì chúng ta không muốn thực thi nó nhiều lần.

Nếu bạn muốn biết cách lazy vals được triển khai, hãy xem số question này.

+55

Là một bổ sung: @ViktorKlang đăng trên Twitter: ["Ít được biết đến thực tế Scala: nếu khởi tạo một val lười biếng ném một exce ption, nó sẽ cố gắng reinitialize val tại truy cập tiếp theo. "] (https://twitter.com/#!/viktorklang/status/104483846002704384) –

51

Tính năng này không chỉ giúp trì hoãn tính toán đắt tiền, mà còn hữu ích để xây dựng các cấu trúc phụ thuộc lẫn nhau. Ví dụ. điều này dẫn đến một stack overflow:

trait Foo { val foo: Foo } 
case class Fee extends Foo { val foo = Faa() } 
case class Faa extends Foo { val foo = Fee() } 

println(Fee().foo) 
//StackOverflowException 

Nhưng với Vals lười biếng nó hoạt động tốt

trait Foo { val foo: Foo } 
case class Fee extends Foo { lazy val foo = Faa() } 
case class Faa extends Foo { lazy val foo = Fee() } 

println(Fee().foo) 
//Faa() 
+0

Nhưng nó sẽ dẫn đến cùng một StackOverflowException nếu phương pháp toString của bạn kết quả đầu ra" foo " thuộc tính. Ví dụ tốt đẹp của "lười biếng" anyway !!! –

19

Cũng lazy là hữu ích mà không phụ thuộc theo chu kỳ, như trong đoạn mã sau:

abstract class X { 
    val x: String 
    println ("x is "+x.length) 
} 

object Y extends X { val x = "Hello" } 
Y 

Truy cập Y bây giờ sẽ ném ngoại lệ con trỏ null, bởi vì x chưa được khởi tạo. Sau đây, tuy nhiên, hoạt động tốt:

abstract class X { 
    val x: String 
    println ("x is "+x.length) 
} 

object Y extends X { lazy val x = "Hello" } 
Y 

EDIT: sau đây cũng sẽ làm việc:

object Y extends { val x = "Hello" } with X 

này được gọi là "initializer sớm". Xem this SO question để biết thêm chi tiết.

+11

Bạn có thể làm rõ lý do tại sao khai báo Y không ngay lập tức khởi tạo biến "x" trong ví dụ đầu tiên trước khi gọi hàm khởi tạo gốc không? – Ashoat

+2

Bởi vì hàm tạo siêu lớp là hàm đầu tiên được gọi ngầm. –

+0

@Ashoat Vui lòng xem [liên kết này] (https://github.com/scala/scala.github.com/blob/master/tutorials/FAQ/initialization-order.md) để giải thích lý do tại sao nó không được khởi tạo. – Jus12

21

Giá trị lười biếng dễ hiểu nhất là "số memoized".

Giống như một def, một val lười biếng không được đánh giá cho đến khi nó được gọi. Nhưng kết quả được lưu để các lời gọi tiếp theo trả về giá trị đã lưu. Kết quả ghi nhớ chiếm không gian trong cấu trúc dữ liệu của bạn, như một val.

Như những người khác đã đề cập, các trường hợp sử dụng cho một val lười biếng là trì hoãn tính toán đắt tiền cho đến khi chúng cần thiết và lưu trữ kết quả của chúng, và để giải quyết phụ thuộc vòng tròn nhất định giữa các giá trị.

Thực tế, các chiến dịch lười biếng được triển khai nhiều hơn hoặc ít hơn như các lỗi đã ghi nhớ. Bạn có thể đọc về các chi tiết thực hiện của họ vào đây:

http://docs.scala-lang.org/sips/pending/improved-lazy-val-initialization.html

23

Tôi hiểu rằng câu trả lời được đưa ra nhưng tôi đã viết một ví dụ đơn giản để làm cho nó dễ hiểu cho người mới bắt đầu như tôi:

var x = { println("x"); 15 } 
lazy val y = { println("y"); x+1 } 
println("-----") 
x = 17 
println("y is: " + y) 

Output mã trên là:

x 
----- 
y 
y is: 18 

Vì nó có thể được nhìn thấy, x được in khi nó được khởi tạo, nhưng y không được in khi nó ini tialized theo cùng một cách (tôi đã lấy x như var cố ý ở đây - để giải thích khi y được khởi tạo). Tiếp theo khi y được gọi, nó được khởi tạo cũng như giá trị của 'x' cuối cùng được xem xét chứ không phải giá trị cũ.

Hy vọng điều này sẽ hữu ích.

0
scala> lazy val lazyEight = { 
    | println("I am lazy !") 
    | 8 
    | } 
lazyEight: Int = <lazy> 

scala> lazyEight 
I am lazy ! 
res1: Int = 8 
  • Tất cả Vals được khởi tạo trong quá trình thi đối tượng
  • Sử dụng từ khóa lười biếng để trì hoãn việc khởi tạo cho đến khi sử dụng đầu tiên
  • Attention: Vals Lazy không chính thức và do đó có thể hiển thị hiệu suất hạn chế
Các vấn đề liên quan