Короткий ответ («почему это странно») заключается в том, что некоторые короткие короткие обозначения 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, однако, в значительной степени похожа на то, что кажется - и поэтому она терпит неудачу по той же причине, что и в первых примерах пары.
Аналогичный анализ объяснит другие странные различия, которые вы видите.
Возможный дубликат [Что такое PECS (продюсер продлевает потребительский супер)?] (Http://stackoverflow.com/questions/2723397/what-is-pecs-producer-extends-consumer-super) – user140547
Это не дубликат , это конкретный поток использования границ. – badCoder
@ 911DidBush, спасибо за ответ, код был написан в основном методе. – badCoder