Java泛型和通配符:如何使这段代码编译?

发布于 2024-09-25 17:12:55 字数 2515 浏览 7 评论 0 原文

我正在使用 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

如何修改代码以使其能够编译?我尝试过 的不同组合?超级和<代码>? extends 在 Container 类和 hasSomethingWhich 方法的签名中,但无法使其编译(不使用显式方法类型参数,但这会产生丑陋的代码:GenericsTest. ;hasSomethingWhich)。

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

I'm writing some matchers using the Hamcrest 1.2 library, but I'm having a hard time with Java wildcards. 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 and ? 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).

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

烟火散人牵绊 2024-10-02 17:12:55

is(T) 匹配器返回一个签名为 Matcher

因此,如果您解构行 assertThat(container, hasSomethingWhich(is("foo"))) 您真正拥有的是:

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

第二行有一个编译错误,因为您的 hasSomethingWhich< 的签名/code> 方法需要 Matcher 参数。为了匹配 hamcrest 的 is(T) 返回类型,您的签名应该是:(

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

区别在于将参数从 Matcher 更改为 Matcher< 所示

然后,这将迫使您更改 hasSomethingWhich() 的签名以接受 Matcher,如下

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

: a href="http://gist.github.com/604664" rel="nofollow">Here 是您发布的原始代码的完全修改版本,它对我来说可以成功编译。

The is(T) matcher returns a Matcher whose signature is Matcher<? super T>.

So if you deconstruct the line assertThat(container, hasSomethingWhich(is("foo"))) what you really have is:

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>. To match the return type of hamcrest's is(T), your signature should instead be:

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>.

This will then force you to change the signature of hasSomethingWhich() to also accept a Matcher<? super T> like so:

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

Here is the fully modified version of the original code you posted which compiles successfully for me.

一束光,穿透我孤独的魂 2024-10-02 17:12:55

马特关于 hasSomethingMatching()/hasSomethingWhich() 中的 super T>

使其工作:

    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,它可以返回 Matcher>,其中 Foo 可以是任何内容。没有办法知道 T 到底是什么,除非调用者显式提供 T,或者编译器必须从上下文推断 T

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

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

matt is right about <? 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. (or you can explicitly specify T as String when invoking the method).

If eclipse relaxes the inference rules, that is against the language spec. 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. 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.

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

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.

白衬杉格子梦 2024-10-02 17:12:55

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

选项 1

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

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

I was able to create a couple of workarounds to achieve the desired syntax.

Option 1

One workaround is to create a replacement for the assertThat method, so that it takes a Container<T> as parameter. 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 for example in the return type of hasSomethingWhich and the type parameter of hasSomethingMatching had to be relaxed. 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

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);
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文