#java #generics #collections #casting
#java #универсальные типы #Коллекции #Кастинг
Вопрос:
Я не понимаю, почему компилятор не может видеть, что приведение безопасно, когда параметризованный тип определен как расширение базового класса. Вот примеры приведений, которые, как мне кажется, должны быть ненужными. Кроме того, когда я включаю приведение, моя IDE (IntelliJ IDEA) предупреждает, что приведение снято, как бы предполагая, что я делаю что-то неправильно. Существует ли идиома, которая позволяет избежать этих приведений и предупреждений? Зачем вообще нужны приведения, учитывая, что в объявлении указано, что тип расширяет базовый класс?
class Shape {}
class Polygon extends Shape {}
public class Foo<T extends Shape>
{
Set<Polygon> polygons;
// Why must this be cast?
Set<T> shapes = (Set<T>) new HashSet<Polygon>();
T getFirst()
{
// Why must this be cast?
return (T) polygons.iterator().next();
}
Iterable<T> getShapes()
{
// Why must this be cast?
return (Iterable<T>) polygons;
}
}
Ответ №1:
Давайте предположим, что вы создали экземпляр своего класса следующим образом:
Foo<Circle> circleFoo = new Foo<Circle>( );
Тогда Set<Circle>
невозможно безопасно назначить HashSet<Polygon>
В getFirst
: вы не можете безопасно привести Polygon
к Circle
И в getShapes
: вы не можете безопасно привести Iterable<Polygon>
к Iterable<Circle>
.
Комментарии:
1. Я понял это в какой-то момент. Спасибо за краткое обновление.
Ответ №2:
T расширяет форму, полигон расширяет форму. Таким образом, нет причин, по которым T расширяет Polygon
Ответ №3:
Возможно, вам будет интересно прочитать это об универсальных типах Java.
В основном,
Box<Integer> and Box<Double> are not subtypes of Box<Number>
Ответ №4:
Set<T> shapes = (Set<T>) new HashSet<Polygon>();
Приведение необходимо, поскольку T
здесь может быть что угодно, что расширяется Shape
, и вы пытаетесь подогнать только полигоны. Circle
есть shape
, но его нет Polygon
. Наилучшей практикой является обработка параметризованных универсальных типов как уникального класса.
Если бы java разрешала вышеуказанное без приведения, это открыло бы дверь для добавления любого T
к set
. Представьте, что вы присвоили своему Polygon
набору значение a Set<T>
, а затем добавили Circle
к нему объекты. Это вызывает множество проблем во время выполнения.
Ответ №5:
// Why must this be cast?
Set<T> shapes = (Set<T>) new HashSet<Polygon>();
Это наименьшая из ваших проблем. Преобразование на самом деле логически неверно. Set<A>
не является подтипом Set<B>
, если A и B различны, даже если A является подтипом B. Если бы у нас были воспроизводимые обобщения, это приведение завершилось бы неудачей.
Комментарии:
1. Теперь я это понимаю. Этот код корректен только в том случае, когда класс
Foo
создается с параметрами типаShape
илиPolygon
. Черт возьми, я работаю с API, где аргумент типаClass<T>
передается в вызываемом вами методеclazz.isAssignableFrom(Polygon.class)
и внутри него, чтобы определить, какие приведения безопасны.
Ответ №6:
В первом примере Set<T>
не является базовым классом HashSet<Polygon>
.
Во втором примере тип polygons.iterator().next()
является Polygon
, что не совпадает с T
.