2016-02-26 14 views
10

Ho un problema intermittente nel codice Scala in cui sto lavorando con i valori di una mappa immutabile con chiavi stringa. Ecco il codice di base, tra cui la registrazione di debug ho aggiunto:Scala Nessuna istanza non == Nessuna

val compStruct = subsq.comps get (ident) 
    compStruct match { 
    ... 
    case None => 
     logger.info(s"Found None, of type ${compStruct.getClass.getName}, at position $position (ident $ident)") 
     ... 
    case x => 
     logger.info(s"Illegal structure of type ${x.getClass.getName} at position $position (ident $ident) - x == None is ${x == None}, x.getClass == None.getClass is ${x.getClass == None.getClass}, x.getClass.getName == None.getClass.getName (${None.getClass.getName}) is ${x.getClass.getName == None.getClass.getName}") 
     ... 
    } 

il problema è che il caso X è talvolta presa quando il valore è in realtà un None, come dimostra la (sterilizzata) output di debug:

INFO ...: Found None, of type scala.None$, at position 3000 (ident XX) 
    INFO ...: Illegal structure of type scala.None$ at position 3200 (ident XX) - x == None is false, x.getClass == None.getClass is true, x.getClass.getName == None.getClass.getName (scala.None$) is true 

(La prima linea è quello che mi aspetto di accadere e in effetti avviene normalmente, il resto è il caso di errore)

Quindi se il mio registro è da credere (e non ho incasinato la mia espressione in qualche modo) Ho un caso in cui la mappa restituisce x, dove x è un'istanza di classe scala.None $ (lo stesso classe scala.None $ come visto dal codice compilato) ma non corrisponde al caso Nessuno e x == Nessuno è falso.

Un problema di caricamento in classe sarebbe la causa ovvia, ma x.class == None.class sembra precluderlo.

Aggiunto: Come ho suggerito nei commenti, posso riprodurre le istanze di Nessuno che non corrispondono con il seguente codice:

object Test { 
    def main(args: Array[String]): Unit = { 
    val none1 = None 
    val clas = this.getClass.getClassLoader.loadClass("scala.None$") 
    val constr = clas.getDeclaredConstructors()(0) 
    constr.setAccessible(true) 
    val none2 = constr.newInstance() 
    println(s"none1 == none2 is ${none1 == none2}") 
    println(s"none1 == None is ${none1 == None}") 
    println(s"none2 == None is ${none2 == None}") 
    } 
} 

che dà:

none1 == none2 is false 
none1 == None is false 
none2 == None is true 

non credo che ha qualcosa a che fare con ciò che sta accadendo nell'applicazione, però.

Aggiunto: Ho modificato il file di classe None $ effettivo per stampare un messaggio quando il costruttore esegue e genera un'eccezione se il valore $ None $ .MODULE non è nullo quando viene chiamato il costruttore, e anche spostato lo store in il valore statico di MODULE $ al blocco di costruzione statico (il codice originale aveva questo store nel costruttore, che a mio avviso è tecnicamente una violazione delle regole JVM poiché l'oggetto non è considerato inizializzato fino a quando non viene restituito il costruttore).

Ciò blocca la chiamata di riflessione al costruttore (sopra il codice di esempio) che ha duplicato i sintomi del problema, ma non modifica nulla nell'applicazione effettiva. Il valore di None $ .MODULE $ cambia da un'esecuzione del codice a quella successiva, anche se la classe rimane la stessa (stesso System.identityHashCode), tuttavia il costruttore viene chiamato solo una volta.

+0

Forse mostrare la dichiarazione del tipo di subsq.comps. Inoltre stai serializzando qualcosa in gioco qui da un'altra macchina virtuale o caricando l'intero subsq in qualche modo (rete/disco?). Prova anche a mostrare la chiave hash dei tuoi oggetti debug e confronta in questo modo: scala.None.hashCode, x .hashCode forse non corrispondono? – LaloInDublin

+0

I tipi non sono davvero rilevanti qui in alcun modo che possa vedere, solo il None. None $ è una classe singleton in Scala, con un costruttore privato chiamato solo da un blocco di inizializzazione statico, Il costruttore che imposta il valore di.MODULE $ finale statico sull'istanza della classe (non sono sicuro del motivo per cui questo viene eseguito nel costruttore - approccio interessante). –

+1

Nessuno $ non ridefinisce equals() o hashCode(), quindi sta utilizzando le implementazioni dell'oggetto di base. Quindi se puoi in qualche modo ottenere una seconda istanza di None $ non dovrebbe essere == Nessuna. Ciò corrisponde ai miei risultati, ma non spiega come potrebbe essere creata un'altra istanza di None $. Nessuno $ fornisce il metodo readResolve(), restituendo il valore statico $ MODULE, quindi la deserializzazione non dovrebbe creare un'istanza diversa. –

risposta

1

Per quanto riguarda il test: None è un oggetto in Scala. Quando si definisce val none1 = None, si assegna a none1 questo oggetto, che dovrebbe essere un singleton.

Utilizzando la riflessione, si ignora il costruttore privato e si crea una nuova istanza di classe None. L'operatore == restituirà solo true se i due puntatori puntano allo stesso oggetto. È possibile verificare l'indirizzo di memoria di questi oggetti utilizzando System.identityHashCode(none1) e confrontarlo.

Inoltre, se si tenta di eseguire la corrispondenza con none1 nell'oggetto Test, si verificherà un errore di corrispondenza, poiché la seconda istanza di Nessuno non corrisponde a Nessuno o a x.

Sono stato in grado di riprodurre il tuo errore.Con l'esecuzione di questo codice:

val a = Map("a" -> "b", "b" -> None) 
a.get("b") match { 
    case None => print("None") 
    case x => print("X") 
} // Prints X 
a.get("c") match { 
    case None => print("None") 
    case x => print("X") 
} // Prints None 

So che questo non è così spiega il motivo per cui la stampa X, ma almeno si sa quando ...

Perché il vostro HashMap ha valori None, è un HashMap [String , java.io.Serializable] piuttosto che HashMap [String, String]. E la chiamata per ottenere restituirà un java.io.Serializable piuttosto che una stringa.

Per risolvere il tuo problema e farlo per abbinare quando è None, si può fare:

case x if(x.isInstanceOf[None$]) => 
+0

Ciò significa che aggiungere un'annotazione di tipo di 'HashMap [String, String]' a 'a' o' compStruct' dell'OP dovrebbe far sì che il compilatore indichi il problema, giusto? Suppongo che la scelta di 'java.io.Serializable' come tipo comune per i valori della mappa non fosse intesa dal programmatore. –

0

Si precisa che per il modo in cui un None viene elaborato nel contesto del codice avviene tramite un Option[A] dove per non specificando il caso per la tua seconda condizione, significa che stai permettendo a None di far parte del secondo caso.

Che cosa si dovrebbe fare è processo il map.get in questo modo, se si sta cercando di ottenere su come ottenere il caso, che non è un None

val compStruct = subsqs.comp.get(ident) 
compStruct match { 
    case None => ... 
    case x: Some(_) => ... 
} 
Problemi correlati