繁体   English   中英

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

[英]Java generics and wildcards: How to make this code compile?

我正在使用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 ? super? extends ? extends在Container类和hasSomethingWhich方法的签名中,但是无法使其编译(不使用显式方法类型参数,但会产生丑陋的代码: GenericsTest.<String>hasSomethingWhich )。

还欢迎用于创建简洁且可读的断言语法的替代方法。 无论语法如何,它都应该接受Container和Matcher作为参数来匹配Container内的元素。

is(T)匹配器返回一个Matcher,其签名是Matcher<? super 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> Matcher<? super T>像这样:

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

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

亚光是关于<? super T> <? super T> in hasSomethingMatching()/hasSomethingWhich()

使它工作:

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

tmp变量是必需的,javac只会推断该赋值中的T == String,而不是任意表达式。 (或者您可以在调用方法时将T显式指定为String)。

如果eclipse放松了推理规则,那就违反了语言规范。 让我们看看这个例子为什么不合适:

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

这种方法本质上是危险的。 给定Match<Object> ,它可以返回一个Matcher<Container<Foo>> ,其中Foo可以是任何东西。 除非调用者明确地提供T ,否则无法知道T是什么,或者编译器必须从上下文推断T

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

更多推理规则的倡导者必须提供他们想要的确切规则集,证明规则是安全和健全的,并且对于凡人来说是可理解的。

我能够创建几个变通方法来实现所需的语法。

选项1

一种解决方法是创建assertThat方法的替换,以便它将Container<T>作为参数。 当方法在不同的类中时,替换断言方法甚至应该能够具有相同的名称。

这需要奇怪的添加? super ? 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);
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM