Java Bounded Generics: ошибка вывода типа? (Вызов метода, JLS 15.12.2.7)

Для следующего фрагмента кода:

import java.util.List;

public class Main {
    interface Interface1<T> {}
    interface Interface2<T> extends Interface1<T> {}
    static class Bound {}
    interface BoundedI1<T extends Bound> extends Interface1<T> {}
    interface BoundedI2<T extends Bound> extends Interface2<T> {}

    public static void main(String[] args) {
        test((List<BoundedI2<?>>) null);
        //test2((List<BoundedI2<?>>) null);
    }

    public static void test(List<? extends Interface2<? extends Bound>> list) { test2(list); }

    public static void test2(List<? extends Interface1<? extends Bound>> list) {}
}

Компилятор в порядке с первым вызовом, но жалуется, если я раскомментирую второй. Является ли это ошибкой в ​​системе вывода типов или кто-нибудь может объяснить, почему здесь не работают правила вывода в JLS?

Протестировано на Oracle JDK 6u43 и 7u45.

ОБНОВЛЕНИЕ: похоже, что eclipsec прекрасно его принимает. К сожалению, я не могу изменить наш набор инструментов :P, но интересно найти различия в компиляторах.

Сообщение об ошибке, напечатанное ideone (кстати, крутой инструмент):

Main.java:12: error: method test2 in class Main cannot be applied to given types;
        test2((List<BoundedI2<?>>) null);
        ^
  required: List<? extends Interface1<? extends Bound>>
  found: List<BoundedI2<?>>
  reason: actual argument List<BoundedI2<?>> cannot be converted to List<? extends Interface1<? extends Bound>> by method invocation conversion

ОБНОВЛЕНИЕ 2: это компилируется нормально, что указывает на то, что компилятор действительно считает, что BoundedI2<?> можно присвоить Interface1<? extends Bound>, что, по-видимому, более прямо противоречит JLS:

public class Main {
    interface Interface1<T> {}
    interface Interface2<T> extends Interface1<T> {}
    static class Bound {}
    interface BoundedI1<T extends Bound> extends Interface1<T> {}
    interface BoundedI2<T extends Bound> extends Interface2<T> {}

    public static void main(String[] args) {
        test((List<BoundedI2<?>>) null);
        //test2((List<BoundedI2<?>>) null);
        test3((BoundedI2<?>) null);
    }

    public static void test(List<? extends Interface2<? extends Bound>> list) { test2(list); }

    public static void test2(List<? extends Interface1<? extends Bound>> list) {}
    public static void test3(Interface1<? extends Bound> instance) {}
}

person user508633    schedule 18.07.2014    source источник
comment
Я засунул ваш код в eclipse, используя java 1.6.0_26-b03, и он скомпилировался без нареканий. Какую ошибку вы получаете?   -  person azurefrog    schedule 18.07.2014
comment
@azurefrog: Вы раскомментировали закомментированную строку, верно? При попытке я получаю сообщение об ошибке.   -  person user2357112 supports Monica    schedule 18.07.2014
comment
Да, я оставил обе строки без комментариев. Я зашел в свои настройки и включил каждую опцию для ошибок в дженериках, и все же eclipse вполне доволен компиляцией вашего кода.   -  person azurefrog    schedule 18.07.2014
comment
Хотя eclipse полностью доволен этим кодом, когда я пытаюсь скомпилировать его с помощью javac из командной строки, я получаю сообщение об ошибке: test2 (java.util.List<? extends Main.Interface1<? extends Main.Bound>>) in Main cannot be applied to (java.util.List<Main.BoundedI2<?>>). Я дважды проверил путь, который использует eclipse, чтобы убедиться, что это тот же компилятор. Это очень интересно.   -  person azurefrog    schedule 18.07.2014
comment
Здесь все в порядке. Протестировано с помощью eclipse + jdk1.7.0_45   -  person Wundwin Born    schedule 18.07.2014
comment
Это также не компилируется с JDK 1.8.0_05, поэтому улучшенные правила вывода типов в Java 8 не исправили это.... Интересно, что замена <?> на <? extends Bound> в закомментированной строке не приводит к ошибке. Возможно, правила вывода не учитывают такое комбинирование нескольких типов вывода, но я недостаточно знаком с правилами, чтобы сказать, так ли это. Есть еще одна проблема в том, что я не уверен, следует ли правильно воспринимать <?> как <? extends Object> или <? extends Bound>, но это еще одна проблема...   -  person awksp    schedule 18.07.2014
comment
Я тут путаюсь. Разве test() не должно компилироваться, поскольку List<BoundedI2<?>> нельзя присвоить List<? extends Interface2<? extends Bound>>? В частности, BoundedI2 расширяет Interface2, но <?> не является <? extends Bound>. Я что-то упускаю? Если BoundedI2 определено как <T extends Bound>, достаточно ли умен компилятор, чтобы знать, что <?> должно быть <? extends Bound>? Это вообще обоснованное заявление?   -  person awksp    schedule 18.07.2014
comment
Хорошо, я думаю, я, возможно, понял хотя бы часть проблемы. Я думаю, что разница между компиляторами сводится к выяснению того, когда неограниченный подстановочный знак действительно неограничен, то есть когда <?> действительно означает <? extends Bound>, в данном случае. Очевидно, Eclipse лучше справляется с этим из-за отсутствия сообщения об ошибке. К сожалению, на данный момент я не могу найти какие-либо отрывки JLS, описывающие такое поведение, поэтому я не могу точно сказать, является ли это ошибкой или просто Eclipse делает все возможное.   -  person awksp    schedule 18.07.2014
comment
Странно то, что компилятор выясняет это один раз (о чем свидетельствует указанный вами вызов test), но, похоже, не может понять то же самое, удаляя еще один шаг, что кажется необычным, поскольку алгоритм, описанный JLS, кажется быть полностью рекурсивным, без реальных пределов максимальной сложности, о которых можно было бы говорить. В частности, компилятор определяет, что List<BoundedI2<?>> можно присвоить List<? extends Interface2<? extends Bound>> якобы потому, что BoundedI2<?> можно присвоить Interface2<? extends Bound>; почему бы и не Interface1<? extends Bound>?   -  person user508633    schedule 20.07.2014
comment
Я думаю ты прав. И Interface1<? extends Bound> i = (BoundedI2<?>) null;, и Interface2<? extends Bound> i = (BoundedI2<?>) null; компилируются без замечаний, но включение их в параметр типа для List заставляет компилятор сдаться. Я склоняюсь к тому, что это ошибка в javac. Вы можете попробовать отправить отчет об ошибке в Oracle, хотя кто знает, будет ли он устранен (не получил ответа на последний отчет, который я отправил, поэтому не слишком им доволен). Кроме того, вы можете отправить уведомление кому-либо, используя @‹username›. Не уверен, что вы мне отвечали, но на всякий случай.   -  person awksp    schedule 20.07.2014
comment
Кроме того, в качестве примечания, конкретный раздел, на который вы ссылаетесь в JLS 8, не существует. Впрочем, на самом деле это не имеет большого значения, так как ошибка остается.   -  person awksp    schedule 20.07.2014


Ответы (2)


Похоже, что у компилятора командной строки есть некоторые трудности с обработкой одновременно

  • тот факт, что BoundedI2 является общим для T, который должен быть «Bound»
  • тот факт, что Interface2 расширяет Interface1

По крайней мере, без правильного создания экземпляра BoundedI2. Что действительно странно, так это то, что Eclipse, настроенный на тот же JDK, компилирует его просто отлично... Обратите внимание, что Eclipse использует свой внутренний компилятор для обработки инкрементной перекомпиляции во время ввода, поэтому он вообще не вызывает компилятор JDK (см. org/ пакет eclipse/jdt/internal/compiler).

Эта модификация делает его компилируемым как в Eclipse, так и в командной строке, заставляя BoundedI2 быть на конкретном типе, а не на выводе типа:

import java.util.List;

public class PerfTest {
    interface Interface1<T> {}
    interface Interface2<T> extends Interface1<T> {}
    static class Bound {}
    interface BoundedI1<T extends Bound> extends Interface1<T> {}
    interface BoundedI2<T extends Bound> extends Interface2<T> {}
    static class Actual extends Bound {}

    public static void main(String[] args) {
        test((List<BoundedI2<Actual>>) null);
        test2((List<BoundedI2<Actual>>) null);
    }

    public static void test(List<? extends Interface2<? extends Bound>> list) { test2(list); }

    public static void test2(List<? extends Interface1<? extends Bound>> list) {}
}
person Eric Nicolas    schedule 22.07.2014

В настоящее время отслеживается отчет об ошибке в OpenJDK с некоторыми комментариями для понимания: https://bugs.openjdk.java.net/browse/JDK-8051807

person user508633    schedule 15.08.2014