Ошибка компиляции обобщений Java - метод метода (Class <capture # 1-of? Extends Interface>) в типе <type>не применим для аргументов - PullRequest
5 голосов
/ 05 ноября 2011

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

Проблема связана с генериками, и я восстановил упрощенную версию кода, которая выдает ошибку компиляции. Ошибка возникает в самой последней строке кода, показанной ниже.

Я просматривал все сети, но не могу найти достойного объяснения, почему компилятор Java не принимает код. Я предполагаю, что если бы разрешить код, было бы возможно создать проблему приведения класса в Bar.operationOnBar (), но я не вижу, как.

Может кто-нибудь объяснить мне, почему это не компилируется?

public interface Interface {
}


public class Type implements Interface {
}

public class Bar<T> {
    public Bar(Class<T> clazz) {
    }

    public void operationOnBar(Class<T> arg){
    }
}

public class Foo {
    public <T> Bar<T> bar(Class<T> clazz){
        return new Bar<T>(clazz);
    }
    public static void main(String[] args) {
        Class<? extends Interface> extendsInterfaceClazz = Type.class;
        new Foo().bar(extendsInterfaceClazz).operationOnBar(Type.class);
    }
}

Ошибка компиляции во второй строке Foo.main ():

The method operationOnBar(Class<capture#1-of ? extends Interface>) in the type Bar<capture#1-of ? extends Interface> is not applicable for the arguments (Class<Type>)

Btw. Я решил это путем перевода Type.class в Class, таким образом компилятор не может увидеть, что универсальным типом Class является «Тип» вместо «расширяет интерфейс».

Ответы [ 4 ]

5 голосов
/ 05 ноября 2011

Небольшой совет: если вы не уверены, почему компилятор запрещает какое-либо преобразование, относящееся к универсальному, замените рассматриваемые универсальные классы на List<T>. Тогда было бы легко найти пример, который нарушает безопасность типов.

Эта замена верна, так как в настоящее время Java не предоставляет способ получить какие-либо априорные знания о возможном поведении универсальных классов (то есть в ней отсутствует способ указать ковариацию и контравариантность универсальных классов в их объявлениях, как в C # 4 и Скала). Поэтому Class<T> и List<T> эквивалентны для компилятора в отношении их возможного поведения, и компилятор должен запретить преобразования, которые могут вызвать проблемы с List<T> и для других универсальных классов.

В вашем случае:

public class Bar<T> {
    private List<T> l;

    public Bar(List<T> l) {
        this.l = l;
    }

    public void operationOnBar(List<T> arg) {
        l.addAll(arg);
    }
}

List<Type1> l1 = new ArrayList<Type1>();
List<? extends Interface> l2 = l1;
List<Type2> l3 = Arrays.asList(new Type2());

new Foo().bar(l2).operationOnBar(l3);

Type1 t = l1.get(0); // Oops!
0 голосов
/ 06 ноября 2011

Общим способом решения подобных проблем является введение универсального аргумента для повторяющегося типа, что обычно означает введение нового универсального метода (класс тоже подойдет, но не обязательно).

public static void main(String[] args) {
    fn(Type.class);
}
private static <T extends Interface> void fn(Class<T> extendsInterfaceClazz) {
    new Foo().bar(extendsInterfaceClazz).operationOnBar(extendsInterfaceClazz);
}

Не совсем связано с вопросом, но я бы посоветовал использовать рефлексию с осторожностью. Это очень, очень редко хорошее решение.

0 голосов
/ 05 ноября 2011

Вы согласитесь, что это не должно компилироваться:

 1   Class<? extends Interface> clazz = AnotherType.class;
 2   new Foo().bar(clazz).operationOnBar(Type.class);

Проблема в том, что Javac немного тупой; при компиляции строки № 2 все, что она знает о переменной clazz, это ее объявленный тип; он забывает конкретный тип, которому он был присвоен. Так что то, что присвоено clazz в строке # 1, не имеет значения, компилятор должен отклонить строку # 2.

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

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

0 голосов
/ 05 ноября 2011

Вы также можете изменить подпись метода operationOnBar на:

public void operationOnBar(Class<? extends Interface> arg){
...