2010-09-30 77 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); 
    } 
}