Что такое «сырые типы» и почему их не следует использовать?
Вопросы:
- Что такое "сырой тип" (raw types) в Java, и почему я часто слышу, что их не следует использовать в новом коде?
- Какова альтернатива, если мы не можем использовать сырые типы, и в чем преимущество этих альтернатив?
5 ответ(ов)
"Сырой" тип в Java — это класс, который не является дженерик-типом и работает с "сырыми" объектами, а не с безопасными типизированными параметрами.
Например, до появления дженериков в Java вы бы использовали класс коллекции следующим образом:
LinkedList list = new LinkedList();
list.add(new MyObject());
MyObject myObject = (MyObject) list.get(0);
Когда вы добавляете объект в список, ему неважно, какой это тип, и когда вы извлекаете его из списка, вам необходимо явно привести его к ожидаемому типу.
Используя дженерики, вы убираете фактор "неизвестного", так как обязаны явно указать, какие типы объектов могут находиться в списке:
LinkedList<MyObject> list = new LinkedList<MyObject>();
list.add(new MyObject());
MyObject myObject = list.get(0);
Обратите внимание, что с дженериками вам не нужно приводить объект, который вы получаете с помощью метода get, коллекция заранее определена для работы только с MyObject. Этот факт является основным преимуществом дженериков. Он превращает источник ошибок времени выполнения в то, что может быть проверено на этапе компиляции.
Что такое "сырая" типизация и почему я часто слышу, что ее не стоит использовать в новом коде?
"Сырая" типизация — это использование обобщенного класса без указания аргумента типа для его параметризованных типов, например, использование List
вместо List<String>
. Когда в Java были введены обобщения, несколько классов были обновлены для поддержки этой функции. Использование этих классов как "сырых типов" (без указания аргумента типа) позволяло поддерживать совместимость с устаревшим кодом.
"Сырые типы" используются для обеспечения обратной совместимости. Их использование в новом коде не рекомендуется, поскольку применение обобщенного класса с аргументом типа позволяет достичь более строгой типизации, что, в свою очередь, может улучшить понимание кода и привести к более раннему выявлению потенциальных проблем.
Какова альтернатива, если мы не можем использовать "сырые" типы, и чем она лучше?
Предпочтительной альтернативой является использование обобщенных классов по назначению — с подходящими аргументами типа (например, List<String>
). Это позволяет программисту более конкретно указывать типы, передает больше информации будущим разработчикам о предполагаемом использовании переменной или структуры данных и позволяет компилятору обеспечивать лучшую строгую типизацию. Эти преимущества могут вместе улучшить качество кода и помочь предотвратить появление некоторых ошибок в коде.
Например, для метода, в котором программист хочет убедиться, что переменная типа List с именем 'names' содержит только строки:
List<String> names = new ArrayList<String>();
names.add("John"); // ОК
names.add(new Integer(1)); // ошибка компиляции
Конечно! Вот перевод на русский язык в стиле ответа на StackOverflow.com:
Здесь я рассматриваю несколько случаев, которые могут прояснить концепцию.
1. ArrayList<String> arr = new ArrayList<String>();
2. ArrayList<String> arr = new ArrayList();
3. ArrayList arr = new ArrayList<String>();
Случай 1
ArrayList<String> arr
— это переменная-ссылка на ArrayList
с типом String
, которая ссылается на объект ArrayList
типа String
. Это означает, что она может хранить только объекты типа String
.
Это строгий тип для String
, а не "сырой" тип, поэтому предупреждений не будет.
arr.add("hello"); // эта строка компилируется успешно, и предупреждений нет.
arr.add(23); // приведет к ошибке на этапе компиляции.
// ошибка: no suitable method found for add(int)
Случай 2
В этом случае ArrayList<String> arr
имеет строгий тип, но ваш объект new ArrayList();
является "сырым" типом.
arr.add("hello"); // эта строка компилируется, но вызывает предупреждение.
arr.add(23); // снова приведет к ошибке на этапе компиляции.
// ошибка: no suitable method found for add(int)
Здесь arr
является строгим типом. Поэтому при попытке добавить int
будет ошибка компиляции.
Предупреждение: Объект "сырого" типа ссылается на переменную с строгим типом
ArrayList
.
Случай 3
В этом случае ArrayList arr
является "сырым" типом, а ваш объект new ArrayList<String>();
имеет строгий тип.
arr.add("hello");
arr.add(23); // компилируется без ошибок, но вызывает предупреждение.
В это можно добавить любой тип объекта, поскольку arr
— это "сырой" тип.
Предупреждение: Объект строгого типа ссылается на переменную с "сырым" типом.
Надеюсь, это поможет вам понять различия между строгими и "сырыми" типами в Java!
Компилятор требует, чтобы вы написали так:
private static List<String> list = new ArrayList<String>();
Потому что иначе вы могли бы добавлять в list
любые типы, что делает создание объекта с new ArrayList<String>()
бессмысленным. Джавовские обобщения являются особенностью времени компиляции, поэтому объект, созданный с помощью new ArrayList<String>()
, будет без проблем принимать элементы типа Integer
или JFrame
, если его присвоить ссылке на "сырой тип" List
— сам объект ничего не знает о том, какие типы он должен содержать, только компилятор это знает.
Вот еще один случай, когда "сырые" типы могут вас подвести:
public class StrangeClass<T> {
@SuppressWarnings("unchecked")
public <X> X getSomethingElse() {
return (X)"Testing something else!";
}
public static void main(String[] args) {
final StrangeClass<String> withGeneric = new StrangeClass<>();
final StrangeClass withoutGeneric = new StrangeClass();
final String value1,
value2;
// Компилируется
value1 = withGeneric.getSomethingElse();
// Вызывает ошибку компиляции:
// incompatible types: java.lang.Object cannot be converted to java.lang.String
value2 = withoutGeneric.getSomethingElse();
}
}
Это может показаться неожиданным, поскольку вы можете подумать, что "сырой" тип влияет только на методы, связанные с параметром типа класса, однако на самом деле это также влияет на обобщенные методы с их собственными параметрами типов.
Как было упомянуто в принятом ответе, вы теряете всю поддержку обобщений в коде сырого типа. Каждый параметр типа преобразуется в его "стирание" (в приведенном примере это просто Object
).
Разница между <? super T> и <? extends T> в Java
Как создать обобщённый массив в Java?
Является ли List<Собака> подклассом List<Животное>? Почему дженерики в Java не являются неявно полиморфными?
Что такое PECS (Producer Extends Consumer Super)?
Как получить экземпляр класса обобщенного типа T?