2010-09-30 78 views
3

我正在使用Hamcrest 1.2庫編寫一些匹配器,但是我很難用Java通配符。當我嘗試編譯下面的代碼Java泛型和通配符:如何編譯此代碼?

public class GenericsTest { 

    public void doesNotCompile() { 
     Container<String> container = new Container<String>(); 

     // this is the desired assertion syntax 
     assertThat(container, hasSomethingWhich(is("foo"))); 
    } 

    // these two are a custom made class and matcher; they can be changed 

    public static class Container<T> { 
     public boolean hasSomethingMatching(Matcher<T> matcher) { 
      T something = null; // here is some application logic 
      return matcher.matches(something); 
     } 
    } 

    public static <T> Matcher<Container<T>> hasSomethingWhich(final Matcher<T> matcher) { 
     return new TypeSafeMatcher<Container<T>>() { 
      @Override 
      protected boolean matchesSafely(Container<T> container) { 
       return container.hasSomethingMatching(matcher); 
      } 
     }; 
    } 

    // the following signatures are from the Hamcrest 1.2 library; they cannot be changed 

    public static <T> void assertThat(T actual, Matcher<? super T> matcher) { 
    } 

    public static <T> Matcher<? super T> is(T value) { 
     return null; 
    } 

    public interface Matcher<T> { 
     boolean matches(Object item); 
    } 

    public static abstract class TypeSafeMatcher<T> implements Matcher<T> { 
     @SuppressWarnings({"unchecked"}) 
     @Override 
     public final boolean matches(Object item) { 
      return matchesSafely((T) item); 
     } 

     protected abstract boolean matchesSafely(T item); 
    } 
} 

它產生的編譯錯誤

$ javac GenericsTest.java 
GenericsTest.java:7: <T>assertThat(T,GenericsTest.Matcher<? super T>) in GenericsTest cannot be applied to (GenericsTest 
.Container<java.lang.String>,GenericsTest.Matcher<GenericsTest.Container<capture#928 of ? super java.lang.String>>) 
     assertThat(container, hasSomethingWhich(is("foo"))); 
     ^
1 error 

如何修改代碼,以便它可以編譯?我已經嘗試了Container容器類和hasSomethingWhich方法簽名中? super? extends的不同組合,但未能使其編譯(不使用顯式方法類型參數,但生成難看的代碼:GenericsTest.<String>hasSomethingWhich)。

此外,還可以使用其他方法創建簡潔和可讀的斷言語法。無論什麼語法,它都應接受一個Container和一個匹配器來匹配容器中的元素。

回答

3

is(T)匹配器返回一個匹配器,其簽名是Matcher<? super T>

所以,如果你解構線assertThat(container, hasSomethingWhich(is("foo")))你真正擁有的是:

Matcher<? super String> matcher = is("foo"); 
assertThat(container, hasSomethingWhich(matcher)); 

第二行有一個編譯錯誤,因爲你hasSomethingWhich方法的簽名需要Matcher<T>的參數。爲了配合hamcrest的is(T)的返回類型,你的簽名應改爲:

public static <T> Matcher<Container<T>> hasSomethingWhich(final Matcher<? super T> matcher) 

(不同的是改變從Matcher<T>的參數Matcher<? super T>

這將迫使你的hasSomethingWhich()簽名更改爲。還接受Matcher<? super T>像這樣:

public boolean hasSomethingMatching(Matcher<? super T> matcher) 

Here爲您發佈的原始代碼的完全修改版本來編譯小號爲我成功。

+0

修改後的版本在斷言類型轉換:'assertThat(容器,hasSomethingWhich((匹配器)是( 「富」))<超級字符串?>);' - 這太冗長合我的口味。我正在尋找能夠創建簡潔可讀的斷言API的東西。 – 2010-09-30 15:01:42

+0

@Esko,演員是不必要的,並且被我的IDE無意中添加了。刪除它並更新了要點。 – 2010-09-30 15:09:24

+0

我無法在你的要點中編譯代碼。你使用Hamcrest 1.1來編譯它嗎?使用原始帖子中的方法,這些方法取自Hamcrest 1.2。 1.2和1.1庫在使用'??時有所不同。超級「在匹配器中。 – 2010-09-30 15:18:19

1

馬特是hasSomethingMatching()/hasSomethingWhich()

權約<? super T>,使其工作:

Matcher<Container<String>> tmp = hasSomethingWhich(is("foo")); 
    assertThat(container, tmp); 

tmp變量是必要的,javac的只能推斷T ==字符串中的分配,而不是任意表達式。 (或者你可以在調用方法時明確地將T指定爲字符串)。

如果eclipse放寬推理規則,那是違反語言規範的。讓我們來看看爲什麼它不合適:

public static <T> Matcher<Container<T>> 
hasSomethingWhich(final Matcher<? super T> matcher) 

這種方法本質上是危險的。給定Match<Object>,它可以返回Matcher<Container<Foo>>,其中Foo可以是任何東西。除非調用者明確地提供了T,否則編譯器必須從上下文中推斷出T,否則無法知道什麼是。

語言規範定義在上面賦值語句的推理規則,因爲開發者的意圖是絕對清楚地表明T應當是完全String

的多個推理規則的倡導者必須提供的規則的確切設定他們想要的,證明規則是安全和強大的,並且可以被凡人理解。

1

我能夠創建幾個解決方法來實現所需的語法。

選項1

一個解決方法是創建用於assertThat方法的替代品,所以,它需要一個Container<T>作爲參數。當方法處於不同的類中時,替換斷言方法甚至應該能夠具有相同的名稱。

這需要奇怪的增加? super例如在返回類型hasSomethingWhich和類型參數hasSomethingMatching必須放寬。所以代碼變得很難理解。

public class GenericsTest { 

    public void doesNotCompile() { 
     Container<String> container = new Container<String>(); 

     // this is the desired assertion syntax 
     assertThat2(container, hasSomethingWhich(is("foo"))); 
    } 

    public static <T> void assertThat2(Container<T> events, Matcher<? super Container<T>> matcher) { 
     assertThat(events, matcher); 
    } 

    // these two are a custom made class and matcher; they can be changed 

    public static class Container<T> { 
     public boolean hasSomethingMatching(Matcher<?> matcher) { 
      T something = null; // here is some application logic 
      return matcher.matches(something); 
     } 
    } 

    public static <T> Matcher<Container<? super T>> hasSomethingWhich(final Matcher<? super T> matcher) { 
     return new TypeSafeMatcher<Container<? super T>>() { 
      @Override 
      protected boolean matchesSafely(Container<? super T> container) { 
       return container.hasSomethingMatching(matcher); 
      } 
     }; 
    } 

    // the following signatures are from the Hamcrest 1.2 library; they cannot be changed 

    public static <T> void assertThat(T actual, Matcher<? super T> matcher) { 
    } 

    public static <T> Matcher<? super T> is(T value) { 
     return null; 
    } 

    public interface Matcher<T> { 
     boolean matches(Object item); 
    } 

    public static abstract class TypeSafeMatcher<T> implements Matcher<T> { 
     @SuppressWarnings({"unchecked"}) 
     @Override 
     public final boolean matches(Object item) { 
      return matchesSafely((T) item); 
     } 

     protected abstract boolean matchesSafely(T item); 
    } 
} 

選項2

其他解決方案,這是更簡單的,是放棄的類型參數,只是使用<?>。無論如何,測試會在運行時找到類型不匹配的情況,因此編譯時類型的安全性幾乎沒有用處。

public class GenericsTest { 

    public void doesNotCompile() { 
     Container<String> container = new Container<String>(); 

     // this is the desired assertion syntax 
     assertThat(container, hasSomethingWhich(is("foo"))); 
    } 

    // these two are a custom made class and matcher; they can be changed 

    public static class Container<T> { 
     public boolean hasSomethingMatching(Matcher<?> matcher) { 
      T something = null; // here is some application logic 
      return matcher.matches(something); 
     } 
    } 

    public static Matcher<Container<?>> hasSomethingWhich(final Matcher<?> matcher) { 
     return new TypeSafeMatcher<Container<?>>() { 
      @Override 
      protected boolean matchesSafely(Container<?> container) { 
       return container.hasSomethingMatching(matcher); 
      } 
     }; 
    } 

    // the following signatures are from the Hamcrest 1.2 library; they cannot be changed 

    public static <T> void assertThat(T actual, Matcher<? super T> matcher) { 
    } 

    public static <T> Matcher<? super T> is(T value) { 
     return null; 
    } 

    public interface Matcher<T> { 
     boolean matches(Object item); 
    } 

    public static abstract class TypeSafeMatcher<T> implements Matcher<T> { 
     @SuppressWarnings({"unchecked"}) 
     @Override 
     public final boolean matches(Object item) { 
      return matchesSafely((T) item); 
     } 

     protected abstract boolean matchesSafely(T item); 
    } 
}