2016-10-21 17 views
4

Я использую Java 8. Во время обучения для передачи Java OCP 8 я нахожу некоторые фрагменты кода, которые я не понимаю и хочу знать, почему это так странно для меня.Почему границы настолько странны в Java?

У меня есть следующая иерархия:

class A {} 
class B extends A {} 
class C extends B {} 

первый один, этот код работы: ошибка

List<?> list1 = new ArrayList<A>() { 
    { 
     add(new A()); 
    } 
}; 

Но следующий код не работает, компиляция:

list1.add(new A()); 

Итак, почему мы не можем добавить новую запись таким образом?

второй один, этот код работы: ошибка

List<? extends A> list2 = new ArrayList<A>() { 
    { 
     add(new A()); 
     add(new B()); 
     add(new C()); 
    } 
}; 

Но следующий код не работает, компиляция:

list2.add(new A()); 
list2.add(new B()); 
list2.add(new C()); 

И последний один, этот код работы:

List<? super B> list3 = new ArrayList<A>() { 
    { 
     add(new A()); 
     add(new B()); 
     add(new C()); 
    } 
}; 

Но в следующем коде, когда мы добавив новый A(), ошибка компиляции:

list3.add(new A()); // compilation error 
list3.add(new B()); 
list3.add(new C()); 

Спасибо за ваши ответы!

+0

Возможный дубликат [Что такое PECS (продюсер продлевает потребительский супер)?] (Http://stackoverflow.com/questions/2723397/what-is-pecs-producer-extends-consumer-super) – user140547

+0

Это не дубликат , это конкретный поток использования границ. – badCoder

+0

@ 911DidBush, спасибо за ответ, код был написан в основном методе. – badCoder

ответ

3

Это ошибка компиляции, предназначенная для обеспечения безопасности типов. Если компилятор разрешил вам это сделать, представьте, что может произойти:

Для вопроса 1, как только объект list1 был объявлен, компилятор учитывает только объявленный тип, который является List<?>, и игнорирует тот факт, что это было совсем недавно присвоено ArrayList<A>.

List<?> list1 = ...; // The compiler knows list1 is a list of a certain type 
         // but it's not specified what the type is. It could be 
         // a List<String> or List<Integer> or List<Anything> 
list1.add(new A()); // What if list1 was e.g. a List<String>? 

Но:

List<?> list1 = new ArrayList<A>() { 
    { 
     add(new A()); 
    } 
}; 

Здесь вы присваиваете list1 выражения. Само выражение, т. Е. Все после =, не использует ? и на самом деле является анонимным классом, который расширяет ArrayList<A> и имеет блок инициализатора, который вызывает add(new A()), что хорошо.

Вторая проблема (со списком2) имеет ту же причину.

В третьем номере,

List<? super B> list3 = new ArrayList<A>() { 
    { 
     add(new A()); 
     add(new B()); 
     add(new C()); 
    } 
}; 

list3.add(new A()); // compilation error 

Компилятор видит list3 как List<? super B>. Это означает, что общий параметр может быть B или его суперкласс, A. Что, если это List<B>? Вы не можете добавить A в List<B>; поэтому компилятор отклоняет этот код.

1

Короткий ответ («почему это странно») заключается в том, что некоторые короткие короткие обозначения Java делают два фрагмента кода очень похожими, когда они действительно очень разные.

Таким образом, может показаться, что «если это работает, это тоже должно работать», но это не так, потому что важные различия в двух битах кода скрыты.

Давайте посмотрим на несколько кусков кода отдельно:

public interface ListFactory { 
    List<?> getList(); 
} 

Так кто собирается дать нам возможность запросить List<?>. Мы можем использовать его как это:

List<?> list1 = myListFactory.getList(); 

Но если мы делаем

list1.add(new A()); 

компилятор не может доказать это законно, потому что это зависит от того, произошло ли мы, чтобы получить реализацию ListFactory, которая возвращает List<A>, или, возможно, List<String>.

Что делать, если мы заменим выше

List<?> list1 = new SpecialList(); 
list1.add(new A()); 

Это немного ближе к исходному коду; но для компилятора такая же проблема существует: когда она оценивает list1.add(new A());, она не ищет подсказок в истории присвоений list1. Он знает только, что ссылочный тип времени компиляции list1 равен List<?>, поэтому, если list1.add(new A()); был незаконным, это все еще незаконно.

(На первый взгляд это может показаться недостатком компилятора, но я думаю, что это не так: обычно компилятор не имеет практического или желательного результата, чтобы попытаться узнать его больше, чем ссылочный тип. мы хотели бы ссылаться на наш объект таким образом, чтобы add(new A()) использовали бы другой ссылочный тип - например, List<A> list2 = new SpecialList();.)

Из вышеизложенного следует, что у нас есть SpecialList.java; давайте посмотрим на нее:

public class SpecialList extends ArrayList<A> { 
    public SpecialList() { 
     add(new A()); 
    } 
} 

Это может показаться немного глупым, но это, наверное, не удивительно, что нет ничего плохого с ним, насколько компилятор обеспокоен (до тех пор, как А определяется и java.util .ArrayList импортируется).

Отметьте, что add(new A()); в этом случае является сокращением для this.add(new A());. В конструкторе SpecialList() this является ссылкой типа SpecialList - который объявлен для расширения ArrayList<A> - и, конечно же, мы можем add(new A()) подкласс ArrayList<A>.

Так что теперь у нас есть все части, чтобы сделать что-то, как ваш исходный код:

List<?> list1 = new ArrayList<A>() { 
    { 
     add(new A()); 
    } 
} 
list1.add(new A()); 

Линии 3 и 6 здесь очень похожи теперь из-за синтаксическое Java мы применили. Но строка 3 действительно похожа на наш пример SpecialList() - ссылочный тип, через который вызывается add(), является анонимным подклассом ArrayList<A>. Строка 6, однако, в значительной степени похожа на то, что кажется - и поэтому она терпит неудачу по той же причине, что и в первых примерах пары.

Аналогичный анализ объяснит другие странные различия, которые вы видите.

 Смежные вопросы

  • Нет связанных вопросов^_^