Какой тип безопасности будет потерян, если Generics поддерживает подтип?
Рассмотрим фрагмент:
Number[] numbers = {1, 2.3, 4.5f, 6000000000000000000L};
Совершенно нормально делать это, Number
- абстрактный класс.
Вперед,
List<Long> listLong = new ArrayList<Long>();
listLong.add(Long.valueOf(10));
List<Number> listNumbers = listLong; // compiler error - LINE 3
listNumbers.add(Double.valueOf(1.23));
Была построена линия 3, которая была успешно скомпилирована,
мы получим List
of Number
s, т.е.
for(Number num: listNumbers ){
System.out.println(num);
}
// 10
// 1.23
которые являются всеми числами.
Я наткнулся на это в книге,
Generics не поддерживает подтипирование, потому что это вызовет проблемы в достижение безопасности типа. Вот почему List<T>
не считается подтип List<S>
, где S
- супертип T
Какой тип безопасности был бы потерян в этом конкретном случае, как обсуждалось выше, должны ли были успешно компилироваться строки 3?
Ответы
Ответ 1
List<Long> listLong = new ArrayList<Long>();
List<Number> listNumbers = listLong;
Итак, listNumbers
и listLong
будут двумя ссылками на один и тот же список, если это возможно, правильно?
listNumbers.add(Double.valueOf(1.23));
Итак, вы могли бы добавить Double в этот список. listLong
, типа List<Long>
, таким образом, будет содержать Double. Таким образом, безопасность типа будет нарушена.
Ответ 2
Если бы это было так, то мы могли бы добавить другие различные подтипы Number
в listNumbers
, которые должны быть запрещены.
Представьте, что теперь вы вставляете объекты типа Double
и Long
, а позже вы пытаетесь использовать Long#reverse
. Ваш код будет компилироваться, но, конечно, не удастся во время выполнения (плохо) первого Double
, который он выйдет.
Ответ 3
Используйте пример с не-абстрактным базовым классом:
public class Human {
public string getName() {
// ...
}
}
public class Student extends Human {
public void learn(Subject subject) {
// ...
}
}
public class Teacher extends Human {
public void teach(Subject subject) {
// ...
}
}
В любом месте, где ожидается Human
, a Student
или Teacher
будут делать то же самое, так как они полностью реализуют интерфейс Human
. (В этом случае на них можно вызвать getName()
.) Наследование Java гарантирует, что это так технически. Выполнение семантической работы - это задание автора класса, так что его код выполняет принцип подстановки Лискова.
Разве это не означает, что мы можем также заменить Collection<Teacher>
, где ожидается a Collection<Human>
? Не всегда. Рассмотрим следующий метод:
public class Human {
// ...
public void join(Set<Human> party) {
party.add(this);
}
}
Теперь, если Java разрешает передавать Set<Student>
в качестве участника, любые попытки не Student
Human
присоединиться к этой стороне должны были сбой во время выполнения.
Как правило, контейнер подтипа непригоден, если приемник (вызываемый в случае аргумента функции, вызывающий в случае возвращаемого значения функции) хочет что-то вставить в него, но приемлемо, если приемник хочет только извлечь материал и использовать его. Контейнер супертипа непригоден, если приемник хочет извлечь и использовать его, но приемлемо, если приемник только когда-либо помещает в него материал. В результате, если получатель извлекает материал из коллекции и помещает материал в коллекцию, обычно требуется набор из фиксированного типа.
Наш метод join
помещает Human
в party
, поэтому мы также можем разрешить Set<Object>
или не общий Set
или эквивалентно a Set<?>
. Java позволяет нам делать это с более низкоуровневыми подстановочными знаками:
public class Human {
// ...
public void join(Set<? super Human> party) {
party.add(this);
}
}
Чтобы открыть возможности для подклассов, верхние ограниченные подстановочные знаки:
public class Teacher extends Human {
public void teach(Subject subject, Set<? extends Student> schoolClass) {
for (Student student : class) {
student.learn(subject);
}
}
}
Теперь, если мы когда-либо подклассы Student
, переданный schoolClass
может быть Set
этого подтипа тоже.
Ответ 4
Концепция, о которой вы говорите, variance.
Иными словами, если S
является супертипом T
, является List<S>
подтипом, супертипом, равным типом или не обращается к List<T>
?
Ответ для List
- и всех других Java-дженериков * - является "несвязанным", т.е. инвариантным.
class SuperType {}
class Type extends SuperType {}
class SubType extends Type {}
List<Type> list = ...
List<SuperType> superList = list;
superList.add(new SuperType());
// no, we shouldn't be able to add a SuperType to list
List<SubType> subList = list;
SubType item = subList.get(0);
// no, there not necessarily only SubType items in list
* В Java есть понятие дисперсии "use-site", с подстановочными знаками (?
). Это будет ограничено, какие методы можно вызвать.
List<Type> list = ...
List<? super SubType> wildcardList = list;
wildcardList.add(new SubType());
// but...everything we get() is an Object
или
List<Type> list = ...
List<? extends SuperType> wildcardList = list;
SuperType item = wildcard.get(0);
// but...it impossible to add()
FYI, некоторые языки имеют понятие дисперсии определения-места, например. Scala. Итак, List[Int]
действительно является подтипом List[Number]
. Это возможно с неизменяемыми коллекциями (опять же, с ограниченным набором методов), но, очевидно, не для изменчивых.