Обобщения и шаблоны Java: как заставить этот код компилироваться? - PullRequest
3 голосов
/ 30 сентября 2010

Я пишу некоторые сопоставления, используя библиотеку 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 в сигнатурах класса Container и метода hasSomethingWhich, но не смог его скомпилировать (без использования явных параметров типа метода, но это приводит к уродливому коду: GenericsTest.<String>hasSomethingWhich).

Также приветствуются альтернативные подходы для создания краткого и читаемого синтаксиса утверждений.Независимо от синтаксиса, он должен принимать в качестве параметров Контейнер и Соответствующий элемент для сопоставления элементов внутри Контейнера.

Ответы [ 3 ]

3 голосов
/ 30 сентября 2010

Устройство сопоставления 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)

Здесь - это полностью измененная версия исходного кода, который вы разместили, который успешно скомпилирован для меня.

1 голос
/ 01 октября 2010

Мне удалось создать пару обходных путей для достижения желаемого синтаксиса.

Вариант 1

Одним из обходных путей является создание замены для метода assertThat, чтобы в качестве параметра он принимал Container<T>. Заменяющий метод assert должен даже иметь одинаковое имя, когда методы находятся в разных классах.

Для этого требуются странные добавления ? 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);
    }
}
1 голос
/ 30 сентября 2010

Мэтт прав около <? super T> в hasSomethingMatching()/hasSomethingWhich()

, чтобы заставить его работать:

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

необходима переменная tmp, javac выведет только T == Stringв этом назначении, а не в произвольных выражениях.(или вы можете явно указать T как String при вызове метода).

Если затмение ослабляет правила вывода, то это противоречит спецификации языка.Давайте посмотрим в этом примере, почему это неуместно:

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

Этот метод по своей природе опасен.учитывая Match<Object>, он может вернуть Matcher<Container<Foo>>, где Foo может быть чем угодно.Нет никакого способа узнать, что это за хрень T, если только вызывающая сторона не предоставит T явно, или компилятор не выведет T из контекста.

Спецификация языка определяет правило вывода вприведенное выше утверждение присваивания, поскольку намерение разработчика абсолютно ясно, что T должно быть точно String

Сторонники большего количества правил вывода должны предоставить точный набор правил, который они хотят, доказать, что правила безопасны и надежны,и понятным для смертных.

...