Почему Java принудительно приводит универсальные типы?

#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 .