2015-04-16 16 views
6

val ve def'in özelliğimi karıştırırken bazı başlatma garipliklerini görüyorum. Durum aşağıdaki örnekle özetlenebilir.Bir öznitelikte bir soyut değere bağlı olarak bir null alma

Soyut bir alan sağlayan bir özellik var, onu çocuk sınıflarında uygulanması gereken fruit diyelim. beklendiği gibi

scala> class FruitTreeDescriptor(fruit: String) { 
    | def describe = s"This tree has loads of ${fruit}s" 
    | } 
defined class FruitTreeDescriptor 

scala> trait FruitTree { 
    | def fruit: String 
    | val descriptor = new FruitTreeDescriptor(fruit) 
    | } 
defined trait FruitTree 

bir def ile fruit basan, işlerin: Aynı zamanda bir val bu alanı kullanır ...

scala> object AppleTree extends FruitTree { 
    | def fruit = "apple" 
    | } 
defined object AppleTree 

scala> AppleTree.descriptor.describe 
res1: String = This tree has loads of apples 

Ancak ben val kullanarak fruit geçersiz kılarsanız,

scala> object BananaTree extends FruitTree { 
    | val fruit = "banana" 
    | } 
defined object BananaTree 

scala> BananaTree.descriptor.describe 
res2: String = This tree has loads of nulls 

Neler oluyor burda?

cevap

3

, noktada aradığınız:

val descriptor = new FruitTreeDescriptor(fruit) 

BananaTree için yapıcı henüz yayınlanma şansı verilmiş değil.Bu, val olmasına rağmen fruit'un hala null değeri anlamına gelir. Bu özel durumda, ne kadar Neyse ki (

class A {       
    val x = a 
    val a = "String" 
} 

scala> new A().x 
res1: String = null 

:

Bu basit bir örnek ile gösterilebilir vals olmayan açıklayıcı başlatma iyi bilinen çıtası, bir subcase olan Derleyici, bir şey olan bir dosyayı algılayacak ve bir uyarı sunacaktır.)

Sorundan kaçınmak için, lazy val numaralı fruit bildirimini zorlayacak.

2

Sorun, başlatma sırasıdır. val fruit = ..., val descriptor = ...'dan sonra başlatılıyor, dolayısıyla descriptor başlatıldığında, fruit hala null'dur. Bunu fruit a lazy val yaparak düzeltebilirsiniz, çünkü ilk erişimde başlatılacaktır.

1

descriptor alanınız fruit alanından daha erken başlatılarak, sınıftan önceki bir özellik intialize edilir. null, başlatmadan önce bir alanın değeridir - bu yüzden onu alırsınız. def durumunda, yalnızca bir alana erişmek yerine bir yöntem çağrısıdır, bu yüzden her şey yolundadır (yöntem kodu birkaç kez çağrılabilir - burada başlatma yok). Bakınız, http://docs.scala-lang.org/tutorials/FAQ/initialization-order.html

Neden def bu kadar farklı? Bunun nedeni, def'un birkaç kez, ancak val - sadece bir kez çağrılmasıdır (böylece ilk ve tek bir çağrı aslında filelin başlatılmasıdır).

Böyle bir soruna tipik çözüm - bunun yerine lazy val kullanarak, gerçekten ihtiyacınız olduğunda arayacaktır. Bir çözüm daha early intializers. Neler ait

Başka, daha basit bir örnek:

scala> class A {val a = b; val b = 5} 
<console>:7: warning: Reference to uninitialized value b 
     class A {val a = b; val b = 5} 
         ^
defined class A 

scala> (new A).a 
res2: Int = 0 //null 

daha genel konuşmak, teorik olarak scala (alan diğer alan gerektirir) alanları arasında bağımlılık grafiği analiz ve son düğümlerden başlatma başlatabilir. Fakat pratikte her modül ayrı olarak derlenir ve derleyici bu bağımlılıkları bile bilmeyebilir (Java'yı çağıran Scala'yı çağıran Java bile olabilir), bu yüzden sıralı başlatma işlemini gerçekleştirir.

Yani, bu yüzden, hatta basit döngüler bulamadık:

scala> class A {val a: Int = b; val b: Int = a} 
<console>:7: warning: Reference to uninitialized value b 
     class A {val a: Int = b; val b: Int = a} 
          ^
defined class A 

scala> (new A).a 
res4: Int = 0 

scala> class A {lazy val a: Int = b; lazy val b: Int = a} 
defined class A 

scala> (new A).a 
java.lang.StackOverflowError 

Aslında böyle döngü (bir modül içinde) teorik olarak ayrı yapı tespit edilebilir, ama çok yardımcı olmaz oldukça açık olduğu gibi. Basit bir ifadeyle

İlgili konular