简体   繁体   English

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

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

I'm writing some matchers using the Hamcrest 1.2 library, but I'm having a hard time with Java wildcards. 我正在使用Hamcrest 1.2库编写一些匹配器,但我在使用Java通配符时遇到了困难。 When I try to compile the following code 当我尝试编译以下代码时

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

it produces the compile error 它会产生编译错误

$ 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

How to modify the code so that it will compile? 如何修改代码以便编译? I've tried different combinations of ? super 我试过不同的组合? super ? super and ? extends ? super? extends ? extends in the signatures of the Container class and the hasSomethingWhich method, but have not been able to make it compile (without the use of explicit method type parameters, but that produces ugly code: GenericsTest.<String>hasSomethingWhich ). ? extends在Container类和hasSomethingWhich方法的签名中,但是无法使其编译(不使用显式方法类型参数,但会产生丑陋的代码: GenericsTest.<String>hasSomethingWhich )。

Also alternative approaches for creating a succinct and readable assertion syntax are welcome. 还欢迎用于创建简洁且可读的断言语法的替代方法。 Whatever the syntax, it should accept as parameters a Container and a Matcher for matching the elements inside the Container. 无论语法如何,它都应该接受Container和Matcher作为参数来匹配Container内的元素。

The is(T) matcher returns a Matcher whose signature is Matcher<? super T> is(T)匹配器返回一个Matcher,其签名是Matcher<? super T> Matcher<? super T> . Matcher<? super T>

So if you deconstruct the line assertThat(container, hasSomethingWhich(is("foo"))) what you really have is: 因此,如果你解构行assertThat(container, hasSomethingWhich(is("foo")))你真正拥有的是:

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

The second line has a compilation error because the signature of your hasSomethingWhich method requires a parameter of Matcher<T> . 第二行有一个编译错误,因为hasSomethingWhich方法的签名需要一个Matcher<T>参数。 To match the return type of hamcrest's is(T) , your signature should instead be: 要匹配hamcrest的返回类型is(T) ,您的签名应该是:

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

(the difference is changing the parameter from Matcher<T> to Matcher<? super T> . (区别在于将参数从Matcher<T>更改为Matcher<? super T>

This will then force you to change the signature of hasSomethingWhich() to also accept a Matcher<? super T> 这将强制您更改hasSomethingWhich()的签名以接受Matcher<? super T> Matcher<? super T> like so: Matcher<? super T>像这样:

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

Here is the fully modified version of the original code you posted which compiles successfully for me. 是您发布的原始代码的完全修改版本,它为我成功编译。

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

to make it work: 使它工作:

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

the tmp variable is necessary, javac will only infer T==String in that assignment, not in arbitrary expressions. tmp变量是必需的,javac只会推断该赋值中的T == String,而不是任意表达式。 (or you can explicitly specify T as String when invoking the method). (或者您可以在调用方法时将T显式指定为String)。

If eclipse relaxes the inference rules, that is against the language spec. 如果eclipse放松了推理规则,那就违反了语言规范。 Let' see in this example why it is inappropriate: 让我们看看这个例子为什么不合适:

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

This method is inherently dangerous. 这种方法本质上是危险的。 given a Match<Object> , it can return a Matcher<Container<Foo>> where Foo can be anything. 给定Match<Object> ,它可以返回一个Matcher<Container<Foo>> ,其中Foo可以是任何东西。 There is no way to know what the heck T is, unless the caller supplies T explicitly, or the compiler has to infer T from the context. 除非调用者明确地提供T ,否则无法知道T是什么,或者编译器必须从上下文推断T

The language spec defines the inference rule in the above assignment statement, because the developer intention is absolutely clear that T should be exactly String 语言规范定义了上述赋值语句中的推理规则,因为开发人员的意图绝对清楚T应该是String

Advocates of more inference rules must supply the exact set of rules they want, prove that the rules are safe and robust, and comprehensible to mortals. 更多推理规则的倡导者必须提供他们想要的确切规则集,证明规则是安全和健全的,并且对于凡人来说是可理解的。

I was able to create a couple of workarounds to achieve the desired syntax. 我能够创建几个变通方法来实现所需的语法。

Option 1 选项1

One workaround is to create a replacement for the assertThat method, so that it takes a Container<T> as parameter. 一种解决方法是创建assertThat方法的替换,以便它将Container<T>作为参数。 The replacement assert method should even be able to have the same name, when the methods are in different classes. 当方法在不同的类中时,替换断言方法甚至应该能够具有相同的名称。

This requires weird additions of ? super 这需要奇怪的添加? super ? super for example in the return type of hasSomethingWhich and the type parameter of hasSomethingMatching had to be relaxed. ? super例如在返回类型hasSomethingWhich的类型和参数hasSomethingMatching不得不放宽。 So the code becomes hard to understand. 所以代码变得难以理解。

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

Option 2 选项2

The other solution, which is much simpler, is to give up on the type parameters and just use <?> . 另一个更简单的解决方案是放弃类型参数并使用<?> The tests will anyways find out at runtime if there is a type mismatch, so compile time type safety is of little use. 如果存在类型不匹配,测试将在运行时发现,因此编译时类型安全性几乎没用。

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