代码之家  ›  专栏  ›  技术社区  ›  Esko Luontola

Java泛型和通配符:如何编译此代码?

  •  3
  • Esko Luontola  · 技术社区  · 14 年前

    我正在用 Hamcrest 1.2 库,但我很难用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
    

    如何修改代码以便编译?我尝试过不同的组合 ? super ? extends 在容器类的签名和hassomethingwhich方法中,但无法使其编译(不使用显式方法类型参数,但会产生难看的代码: GenericsTest.<String>hasSomethingWhich )。

    此外,还欢迎使用其他方法来创建简洁易读的断言语法。无论语法如何,它都应该接受一个容器和一个匹配器作为参数来匹配容器中的元素。

    3 回复  |  直到 14 年前
        1
  •  3
  •   matt b    14 年前

    这个 is(T) matcher返回签名为的matcher 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)
    

    (不同之处在于改变参数 匹配器<t> 匹配器<?超级T> .

    这将迫使您更改 hasSomethingWhich() 也接受 匹配器<?超级T> 就像这样:

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

    Here 是您发布的原始代码的完全修改版本,它为我成功编译。

        2
  •  1
  •   irreputable    14 年前

    马特说得对 <? super T> 在里面 hasSomethingMatching()/hasSomethingWhich()

    要使其工作:

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

    这个 tmp 变量是必需的,javac将只在该赋值中推断t==string,而不是在任意表达式中。(或者可以在调用方法时显式地将t指定为字符串)。

    如果Eclipse放宽了推理规则,那就违反了语言规范。让我们在本例中看看为什么它不合适:

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

    这种方法本质上是危险的。给出了一个 Match<Object> ,它可以返回 Matcher<Container<Foo>> 在哪里? Foo 可以是任何东西。没办法知道到底是怎么回事 T 是,除非呼叫者提供 T 或者编译器必须推断 T 从上下文来看。

    语言规范在上面的赋值语句中定义了推理规则,因为开发人员的意图是绝对清楚的,t应该是精确的 String

    更多推理规则的拥护者必须提供他们想要的一套精确的规则,证明这些规则是安全和可靠的,并且是人类可以理解的。

        3
  •  1
  •   Esko Luontola    14 年前

    我能够创建两个解决方法来实现所需的语法。

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