2014-09-01 5 views
6

Ho pensato che sarebbe stato relativamente facile, ma ahimè, sembra che non lo sia.Inizializzazione Double Brace Tipo Confusion

Attualmente sto scrivendo unità di test per una struttura Facciata, come nel mio progetto utilizzando Java EE 6.
per i test che uso Junit 4.11, con Eclipse Keplero come IDE.

Da quello che posso vedere, sembra che ci sia qualcosa di "sbagliato" con il doppio di inizializzazione brace, ma non sono abbastanza esperto per mettere il dito su perché non funziona come penso che dovrebbe.

per arrivare al punto, sto usando classe seguente per fare conversioni in un luogo centralizzato:

package com.example-company.util.converters; 

import java.util.HashMap; 
import java.util.Map; 

import com.example-company.model.Location; 
import com.example-company.model.Right; 

public final class ModelConverters { 

    private static final Map<Class<?>, ModelConverter<?, String>> modelConverterBacking = new HashMap<Class<?>, ModelConverter<?, String>>(); 
    static { 
     modelConverterBacking.put(Right.class, new RightConverter()); 
     modelConverterBacking.put(Location.class, new LocationConverter()); 
    }; 

    public static <T> String convert(final T input) 
      throws IllegalStateException { 
     @SuppressWarnings("unchecked") 
     ModelConverter<T, String> modelConverter = (ModelConverter<T, String>) modelConverterBacking 
       .get(input.getClass()); 
     if (modelConverter == null) { 
      throw new IllegalStateException("No mapping found for " 
        + input.getClass()); 
     } 
     return modelConverter.convertToView(input); 
    } 
} 

Per quanto riguarda questo va è per lo più a giocare con i generici e una mappa statica. Ora ho deciso che dovrei scrivere qualche unit test per questo. La seguente classe è un po 'abbreviata, tutti i casi di test che non riproducono il problema sono stati rimossi.

Fin qui tutto bene, non dovrebbe accadere nulla, almeno non quello che ho ottenuto ora. E questo è: una fantasia IllegalStateException con il seguente stacktrace:

java.lang.IllegalStateException: No mapping found for class com.example-company.test.unit.util.converters.ModelConvertersFacadeTests$1 
    at com.example-company.util.converters.ModelConverters.convert(ModelConverters.java:23) 
    at com.example-company.test.unit.util.converters.ModelConvertersFacadeTests.test_MappingForLocationExists(ModelConvertersFacadeTests.java:24) 
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 
    at java.lang.reflect.Method.invoke(Method.java:597) 
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) 
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) 
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) 
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) 
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271) 
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70) 
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) 
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) 
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) 
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) 
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) 
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) 
    at org.junit.runners.ParentRunner.run(ParentRunner.java:309) 
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50) 
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38) 
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467) 
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683) 
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390) 
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197) 

prima cosa che ho fatto, è stato eseguito di nuovo, e quindi impostando un punto di interruzione per controllare cosa succede all'interno del ModelConverters#convert()

quello che ho ottenuto un po 'flabberghasted me:

Debug Perspective: Expressions

sembra che input.getClass() ri gira ModelConvertersFacadeTests. Ma perché non restituisce com.example-company.model.Location?

Full Debug Perspective, names censored

+0

[In generale, dovresti diffidare dall'inizializzazione della doppia parentesi] (http://stackoverflow.com/q/924285/521799) –

risposta

13

Sembra che input.getClass() restituisce ModelConvertersFacadeTests

Questo non è vero. Il tuo stacktrace dice che questa è la classe:

com.example-company.test.unit.util.converters.ModelConvertersFacadeTests $ 1

Annotare il $1 alla fine. Ciò significa che la tua classe è un anonimo (non ha un nome proprio) della classe interiore.

Il this$0 che vediamo nel tuo screenshot è solo un riferimento alla classe esterna.

Ogni volta che fai new SomeClass() { ... } stai creando una classe interiore anonima.

L'inizializzazione di doppia parentesi non ha nulla a che fare con questo.Ogni volta che si utilizza l'inizializzazione doppia coppia, si crea anche una classe interna anonima.


Solving, cercando in mappa in modo diverso

tuo Map ha una mappatura per Right.class e Location.class, ma non dispone di una mappatura per sottoclassi di queste due classi.

static { 
    modelConverterBacking.put(Right.class, new RightConverter()); 
    modelConverterBacking.put(Location.class, new LocationConverter()); 
}; 

cosa si potrebbe do (non dicendo che è l'approccio migliore), è quello di un ciclo tra le chiavi della vostra mappa e verifica:

mapKey.isAssignableFrom(input.getClass()) 

Quando ciò restituisce vero, si sa che o hai una classe di mapKey o ne hai una sottoclasse.

Anziché eseguire il ciclo attraverso i tasti della mappa, è anche possibile scorrere le superclasse e le interfacce implementate dell'oggetto che si passa e fare una ricerca modelConverterBacking.get per ognuna di esse. L'effetto sarà lo stesso.


Risolvere senza l'utilizzo di una classe interna anonima

Il codice attuale è:

final Location stub = new Location() { 
    { 
     setLocationName(""); 
    } 
}; 

Se invece si farebbe:

final Location stub = new Location(); 
stub.setLocationName(""); 

Allora non si crea alcun classe interiore anonima e quindi non avrebbe questo problema.

Tuttavia, anche se semplicemente fare questo:

final Location stub = new Location() {}; 
stub.setLocationName(""); 

Poi si ha una classe interna anonima, che causerà problemi per voi.


E 'molto importante di non mischiare le due classi ModelConvertersFacadeTests$1 e ModelConvertersFacadeTests.