2015-01-26 10 views
7

Mam dziwny błąd. Jeden z obiektów sprawy (tylko jeden) dostaje null jako wartość i oczywiście powoduje wyjątki wskaźnika Null w dalszej części życia. Ale dlaczego?Obiekt sprawy zostaje zainicjalizowany na wartość null - jak to możliwe?

Próbowałem różnych rzeczy (zmieniając nazwy obiektów, zmieniając wahanie, nawet przepisując tę ​​samą linię dwukrotnie, otrzymując jeden z nich do kompilacji ładnie, a inny daje null). Sklonowałem repozytorium, aby upewnić się, że nie jest to zabawny efekt przechodniów. Zarówno Scala 2.11.4, jak i 2.11.5 powodują to samo.

Kod jest raczej pokrętny i odcięcie go od kontekstu nie ma większego sensu. Ale tu jest, na wypadek, gdyby dzwoniło na kogoś innego.

sealed trait A {} 

sealed trait B extends A {} 

sealed trait C { val n: Int } 
object C { 
    val C1: C = new C { val n = 1 } 
    val C2: C = new C { val n = 2 } 
    val C3: C = new C { val n = 3 } 
    val C4a: C = new C { val n = 4 } 
    val C4b: C = new C { val n = 5 } 
} 

class D (depends: Seq[C], f: (Seq[Double]) => Double) { 
    // ...methods removed 
} 

sealed trait E { val e: Int = 1 } 

object D { 
    import C._ 

    private def f(v: Seq[Double]): Double = 0 

    private def list(acc: Boolean) = List(
    C1, C2, C3, 
    if (acc) C4a else C4b 
) 

    case object D1 extends D(list(true), f) with E // <-- this gets to be 'null'!! 
    case object D2 extends D(list(false), f) 

    val keys: Seq[D] = List(D1, D2) 

    println(s"D1: $D1") 
    println(s"D2: $D2") 

    assert(D1 != null) // <-- caught here 
    assert(D2 != null) 
} 

Próbowałem zachować jak najwięcej szczegółów w kodzie, na ile to możliwe, na wypadek, gdyby w jakiś sposób były ważne.

Co to może być?

Oznaczałem to jako "heisenbug", ale kiedy kompiluję rzeczy, jest to zgodne (tzn. Kolejne testy zawsze dawały taki sam efekt).

EDYCJA: Pierwotnie powiedziałem, że to się stało tylko z sbt test (i nie sbt run), ale to nie jest prawda. Oba zostały w równym stopniu dotknięte.

EDYCJA: Wydaje się, że @dk14 złożył to jako Scala issue 9115. Dzięki!

+2

Ten kod nie może zostać wykonany. Czy można utworzyć przykład wykonywalny, który powoduje nieoczekiwane zachowanie? – sschaef

+0

Próbowałem tego, ale zredukowana próbka działała zgodnie z przeznaczeniem (tj. Bez wartości null). Większy projekt, który ma problem, jest zastrzeżony, więc nie mogę ujawnić więcej. – akauppi

Odpowiedz

7

Nie mam pojęcia, co dokładnie się dzieje, ale podejrzewam, że istnieje jakiś rodzaj zlecenia budowy. mogę odtworzyć problem 100% czasu, jeśli mogę wkleić kod i zrobić

scala> D.D1 
D1: null 
D2: D2 
java.lang.AssertionError: assertion failed 
    at scala.Predef$.assert(Predef.scala:151) 
    ... 47 elided 

Także jeśli mogę prosić o D2 pierwszy, to nie zbyt, więc to nie jest związane z E.

scala> D.D2 
D1: D1 
D2: null 
java.lang.AssertionError: assertion failed 
    at scala.Predef$.assert(Predef.scala:151) 
    ... 47 elided 

Ale jeśli mogę prosić o D pierwsze, działa w 100% czasu:

scala> D 
D1: D1 
D2: D2 
res1: D.type = [email protected] 

scala> D.D1 
res2: D.D1.type = D1 

uda mi się destylować problem w krótszym przykład kodu:

class Bug(val f: Int) {} 

object Bug { 
    private def f(n: Int): Int = n 

    case object Bug1 extends Bug(f(1)) 
    case object Bug2 extends Bug(f(2)) 

    val values: List[Bug] = List(Bug1, Bug2) 

    println(s"1: $Bug1") 
    println(s"2: $Bug2") 
} 

Pytanie o Bug.Bug1 zakończy się niepowodzeniem. Jednak jeśli usunę linie println - wszystko działa. Lub "wykonaj" najpierw Bug. Jednak mając values jest w porządku.

Domyślam się, że niektóre blokady inicjalizacyjne przebiegają nieprawidłowo, gdy pytają bezpośrednio o Bug.Bug1 lub Bug.Bug2.

+0

Niesamowita robota! W mojej oryginalnej próbce rozwiązałem problem, wykonując "klucze", "lazy val", i usuwając testy w obiekcie nadrzędnym. Wygląda na to, że obiekt wewnątrz obiektu może mieć wartość zerową przejściowo i trzeba uważać, aby go nie zakotwiczyć, przechowując wartość zbyt wcześnie. Dzięki!! – akauppi

+0

Oznaczono jako oficjalną odpowiedź, ponieważ pomogło mi rozwiązać problem pod moim adresem! ** łuki ** – akauppi

1

To nie działa tylko wtedy, gdy bezpośredni dostęp D.D1 lub D.D2 przed samą D obiektu, ponieważ nie jest inicjowany tutaj, więc case object wartości są nadal null „s. Wydaje mi się, że to błąd (powinien zainicjować D przed wywołaniem D.D1). Obejście: wystarczy zadzwonić pod numer D (aby uruchomić inicjalizację) gdzieś przed D.D1.Zobacz, https://stackoverflow.com/a/26072435/1809978 - leniwy inicjowanie jest w rzeczywistości funkcją, ale nie w przypadku, który znalazłeś. Uproszczony przykład:

object D { 
    def aaa = 1 //that’s the reason 
    class Z (depends: Any) 
    case object D1 extends Z(aaa) // <-- this gets to be 'null'!! 
    case object D2 extends Z(aaa) 
    println(D1) 
    println(D2) 
} 

Wyniki:

defined object D 
scala> D.D1 
null 
D2 
res32: D.D1.type = D1 

Po ponownym DEFINICJA D:

defined object D 
scala> D.D2 
D1 
null 
res34: D.D2.type = D2 

Tak scala forgots zainicjować żądaną pod-obiekt (jeśli dotyczy to niektórych innym członek obiektu wewnątrz definicji sub-obiektu) przed uruchomieniem załączania inicjalizacji obiektu. Inicjuje ten pod-obiekt po (gdy zdefiniowano aaa). Myślę, że pierwotną intencją było zainicjowanie wszystkich polecanych członków przed żądanym członkiem, ale nadal wydaje się, że to błąd, ponieważ zmienia kolejność intializacji, w zależności od tego, w jaki sposób D został użyty w kodzie.

+0

Czy widzisz to jako błąd Scali? Spodziewałem się, że obiekty zamknięte zostaną najpierw zainicjalizowane, ale wygląda na to, że nie powinny być dostępne z poziomu konstruktora obiektu zewnętrznego (chyba że dostęp jest leniwy). – akauppi

+0

w tym przypadku jest to błąd leniwego inicjowania obiektu scala - zobacz moją aktualizację – dk14

Powiązane problemy