2014-09-01 146 views
6

我認爲這會比較容易,但唉,它似乎不是。雙括號初始化類型混淆

我目前正在使用Java EE 6在我的項目中爲Facade-like結構編寫單元測試。
對於測試,我使用Junit 4.11和Eclipse Kepler作爲IDE。

從我所看到的情況來看,似乎有雙重大括號初始化的「錯誤」,但我不夠知識足以將我的手指放在爲什麼它不工作,因爲我認爲它應該。

要切入正題,我使用下面的類進行轉換在集中的地方:

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); 
    } 
} 

至於這正好這主要是與泛型和靜態地圖打。現在我決定我應該爲此寫幾個單元測試。下面的課程稍微縮短了,所有不重現問題的測試用例都被刪除了。

package com.example-company.test.unit.util.converters; 

import static org.junit.Assert.assertEquals; 
import com.example-company.model.Location; 
import com.example-company.util.converters.ModelConverters; 

import org.junit.Test; 

public class ModelConvertersFacadeTests { 

    @Test 
    public void test_MappingForLocationExists() { 
     final Location stub = new Location() { 
      { 
       setLocationName(""); 
      } 
     }; 

     String actual = ModelConverters.convert(stub); 
     assertEquals("", actual); 
    } 
} 

到目前爲止好,真的沒有什麼事情發生,至少不是我現在得到的。那就是:一個花哨IllegalStateException與以下堆棧跟蹤:

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) 

我做的第一件事情,再次運行它,然後設置一個斷點檢查ModelConverters#convert()

裏面發生了什麼我得到了什麼略有flabberghasted我:

Debug Perspective: Expressions

似乎input.getClass()重輪到ModelConvertersFacadeTests。但爲什麼它不返回com.example-company.model.Location

Full Debug Perspective, names censored

+0

[你一般應該小心使用雙括號初始化](http://stackoverflow.com/q/924285/521799) – 2014-12-17 09:02:29

回答

13

似乎input.getClass()返回ModelConvertersFacadeTests

這是不正確的。您的堆棧跟蹤說,這是類:

com。示例,company.test.unit.util.converters.ModelConvertersFacadeTests $ 1

注意$1底。這意味着你的類是一個匿名的(它沒有自己的名字)內部類。

我們在截圖中看到的this$0只是參考到外部類。

每次你做new SomeClass() { ... }你正在創建一個匿名的內部類。

雙括號初始化本身與此無關。每次你使用雙括號初始化時,你也在創建一個匿名的內部類。


解決通過地圖查找不同

MapRight.classLocation.class的映射,但它不具備子類這兩類的映射。

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

什麼你可以 DO(不是說這是最好的方法),通過地圖的鑰匙,並且是循環檢查:

mapKey.isAssignableFrom(input.getClass()) 

當返回true,你知道,你要麼有一個mapKey的類,要麼你有它的一個子類。

除了循環遍歷映射鍵之外,還可以遍歷所傳遞對象的超類和已實現接口,併爲每個對象執行modelConverterBacking.get查找。效果將是相同的。


不使用匿名內部類解決

您當前的代碼是:

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

如果你不是會做:

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

那麼你沒有創建任何匿名的內部類,因此不會有這個問題。

但是,即使你只是這樣做:

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

然後你有一個匿名內部類,這將導致問題爲您服務。


這是非常重要不要混淆了兩類ModelConvertersFacadeTests$1ModelConvertersFacadeTests