Một val lười làm gì?


247

Tôi nhận thấy rằng Scala cung cấp lazy vals. Nhưng tôi không có được những gì họ làm.

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 ylà một lazy val, nhưng làm thế nào là nó khác so với bình thường val?

Câu trả lời:


335

Sự khác biệt giữa chúng là, a valđược thực thi khi nó được định nghĩa trong khi a lazy valđược thực thi khi nó đượ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 thức (được định nghĩa bằng def) a lazy valđược thực thi một lần và sau đó không bao giờ lặp lại. Điều này có thể hữu ích khi một thao tác mất nhiều thời gian để hoàn thành và khi không chắc chắn liệu nó có được sử dụng sau này hay không.

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 = X@262505b7 // we have to wait two seconds to the result

scala> new Y
res6: Y = Y@1555bd22 // this appears immediately

Ở đây, khi các giá trị xykhông bao giờ được sử dụng, chỉ xlãng phí tài nguyên một cách không cần thiết. Nếu chúng ta cho rằng ykhông có tác dụng phụ và chúng ta không biết tần suất truy cập (không bao giờ, một lần, hàng nghìn lần) thì việc tuyên bố điều đó là vô ích defvì chúng ta không muốn thực hiện nó nhiều lần.

Nếu bạn muốn biết làm thế nào lazy valsđược thực hiện, xem câu hỏi này .



@PeterSchmitz Và tôi thấy điều này thật kinh khủng. So sánh với Lazy<T>trong .NET
Pavel Voronin

61

Tính năng này không chỉ giúp trì hoãn các 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 hoặc theo chu kỳ. Ví dụ, điều này dẫn đến tràn ngăn xếp:

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 thì 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()

Nhưng nó sẽ dẫn đến cùng một StackOverflowException nếu phương thức toString của bạn xuất ra thuộc tính "foo". Ví dụ hay về "lười biếng" nào !!!
Fuad Efendi

39

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 để dễ hiểu cho những 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)

Đầu ra của mã trên là:

x
-----
y
y is: 18

Như có thể thấy, x được in khi khởi tạo, nhưng y không được in khi nó được khởi tạo theo cùng một cách (tôi đã lấy x là 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 nhưng không phải là giá trị cũ.

Hi vọng điêu nay co ich.


35

Một val lười biếng được hiểu một cách dễ dàng nhất là một " defoized (no-arg) def".

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 lại để các lần 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 các 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 các phụ thuộc vòng tròn nhất định giữa các giá trị.

Vals lười trong thực tế được thực hiện ít nhiều như defs ghi nhớ. Bạn có thể đọc về các chi tiết thực hiện của họ ở đây:

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


1
có lẽ đúng hơn là một "def memized mà mất 0 đối số".
Andrey Tyukin

19

Cũng lazyhữ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 Ybây giờ sẽ ném ngoại lệ con trỏ null, vì xchưa được khởi tạo. Tuy nhiên, sau đây 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ẽ hoạt động:

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

Điều này được gọi là "khởi tạo sớm". Xem câu hỏi SO này để biết thêm chi tiết.


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

2
Bởi vì hàm tạo của siêu lớp là cái đầu tiên được gọi ngầm.
Stevo Slavić

@Ashoat Vui lòng xem liên kết này để được giải thích lý do tại sao nó không được khởi tạo.
Jus12

4

Trình diễn lazy- như được định nghĩa ở trên - thực thi khi được xác định so với thực thi khi được truy cập: (sử dụng 2.12.7 scala shell)

// compiler says this is ok when it is lazy
scala> lazy val t: Int = t 
t: Int = <lazy>
//however when executed, t recursively calls itself, and causes a StackOverflowError
scala> t             
java.lang.StackOverflowError
...

// when the t is initialized to itself un-lazily, the compiler warns you of the recursive call
scala> val t: Int = t
<console>:12: warning: value t does nothing other than call itself recursively
   val t: Int = t

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

scala> lazyEight
I am lazy !
res1: Int = 8
  • Tất cả các vals được khởi tạo trong quá trình xây dựng đối tượng
  • Sử dụng từ khóa lười biếng để trì hoãn khởi tạo cho đến khi sử dụng lần đầu tiên
  • Chú ý : Vals lười không phải là cuối cùng và do đó có thể cho thấy nhược điểm về hiệu suất
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.