Что такое сырой тип?
Спецификация языка Java определяет необработанный тип следующим образом:
Необработанный тип определяется как один из:
Ссылочный тип, который формируется путем взятия имени объявления универсального типа без сопровождающего списка аргументов типа.
Тип массива, тип элемента которого является необработанным типом.
Не static
тип элемента необработанного типа R
, который не унаследован от суперкласса или суперинтерфейса R
.
Вот пример для иллюстрации:
public class MyType<E> {
class Inner { }
static class Nested { }
public static void main(String[] args) {
MyType mt; // warning: MyType is a raw type
MyType.Inner inn; // warning: MyType.Inner is a raw type
MyType.Nested nest; // no warning: not parameterized type
MyType<Object> mt1; // no warning: type parameter given
MyType<?> mt2; // no warning: type parameter given (wildcard OK!)
}
}
Здесь MyType<E>
является параметризованным типом ( JLS 4.5 ). Обычно этот термин обычно называют просто MyType
для краткости, но технически это имя MyType<E>
.
mt
имеет необработанный тип (и генерирует предупреждение о компиляции) по первому пункту в приведенном выше определении; inn
также имеет необработанный тип по третьему пункту.
MyType.Nested
не является параметризованным типом, хотя это тип члена параметризованного типа MyType<E>
, потому что это static
.
mt1
и mt2
оба объявлены с фактическими параметрами типа, поэтому они не являются необработанными типами.
Что особенного в необработанных типах?
По сути, необработанные типы ведут себя так же, как и до появления дженериков. То есть, во время компиляции это абсолютно законно.
List names = new ArrayList(); // warning: raw type!
names.add("John");
names.add("Mary");
names.add(Boolean.FALSE); // not a compilation error!
Приведенный выше код работает отлично, но предположим, что у вас также есть следующее:
for (Object o : names) {
String name = (String) o;
System.out.println(name);
} // throws ClassCastException!
// java.lang.Boolean cannot be cast to java.lang.String
Теперь у нас возникают проблемы во время выполнения, потому что names
содержит что-то, что не instanceof String
.
Предположительно, если вы хотите, чтобы names
содержал только String
, вы могли бы , возможно, по-прежнему использовать необработанный тип и вручную проверять каждый add
, а затем приведение вручную к String
каждому предмету с names
. Еще лучше , хотя НЕ для использования необработанного типа и позволяет компилятору сделать всю работу за вас , используя всю мощь Java-обобщений.
List<String> names = new ArrayList<String>();
names.add("John");
names.add("Mary");
names.add(Boolean.FALSE); // compilation error!
Конечно, если вы DO хотите names
разрешить Boolean
, то вы можете объявить его как List<Object> names
, и приведенный выше код скомпилируется.
Смотри также
Чем необработанный тип отличается от использования <Object>
в качестве параметров типа?
Ниже приводится цитата из Effective Java 2nd Edition, Item 23: Не используйте необработанные типы в новом коде :
В чем разница между необработанным типом List
и параметризованным типом List<Object>
? Грубо говоря, первый отказался от проверки универсального типа, в то время как последний явно сказал компилятору, что он способен содержать объекты любого типа. Хотя вы можете передать List<String>
параметру типа List
, вы не можете передать его параметру типа List<Object>
. Существуют правила подтипов для обобщений, и List<String>
является подтипом необработанного типа List
, но не параметризованного типа List<Object>
. Как следствие, вы теряете безопасность типов, если используете необработанный тип, такой как List
, но не если вы используете параметризованный тип, такой как List<Object>
.
Чтобы проиллюстрировать это, рассмотрим следующий метод, который принимает List<Object>
и добавляет new Object()
.
void appendNewObject(List<Object> list) {
list.add(new Object());
}
Обобщения в Java инвариантны. List<String>
не является List<Object>
, поэтому следующее сгенерирует предупреждение компилятора:
List<String> names = new ArrayList<String>();
appendNewObject(names); // compilation error!
Если бы вы объявили appendNewObject
для получения необработанного типа List
в качестве параметра, то это скомпилировалось бы, и поэтому вы потеряли бы безопасность типов, которую вы получаете от обобщений.
Смотри также
Чем необработанный тип отличается от использования <?>
в качестве параметра типа?
List<Object>
, List<String>
и т. Д. - все это List<?>
, поэтому может быть соблазнительно сказать, что они просто List
. Однако есть существенное отличие: поскольку List<E>
определяет только add(E)
, вы не можете добавить какой-либо произвольный объект к List<?>
. С другой стороны, поскольку необработанный тип List
не имеет безопасности типов, вы можете add
почти что угодно до List
.
Рассмотрим следующий вариант предыдущего фрагмента:
static void appendNewObject(List<?> list) {
list.add(new Object()); // compilation error!
}
//...
List<String> names = new ArrayList<String>();
appendNewObject(names); // this part is fine!
Компилятор прекрасно защитил вас от возможного нарушения инвариантности типов List<?>
! Если бы вы объявили параметр как необработанный тип List list
, код скомпилировался бы, и вы нарушили бы инвариант типа List<String> names
.
Необработанный тип - это стирание этого типа
Вернуться к JLS 4.8:
Можно использовать в качестве типа стирание параметризованного типа или стирание типа массива, тип элемента которого является параметризованным типом. Такой тип называется необработанным типом .
[...]
Суперклассы (соответственно, суперинтерфейсы) необработанного типа являются стиранием суперклассов (суперинтерфейсов) любой параметризации универсального типа.
Тип конструктора, метода экземпляра или не static
поля необработанного типа C
, который не унаследован от его суперклассов или суперинтерфейсов, является необработанным типом, который соответствует стиранию его типа в общем декларация, соответствующая C
.
Проще говоря, когда используется необработанный тип, конструкторы, методы экземпляра и не static
поля также стираются .
Возьмите следующий пример:
class MyType<E> {
List<String> getNames() {
return Arrays.asList("John", "Mary");
}
public static void main(String[] args) {
MyType rawType = new MyType();
// unchecked warning!
// required: List<String> found: List
List<String> names = rawType.getNames();
// compilation error!
// incompatible types: Object cannot be converted to String
for (String str : rawType.getNames())
System.out.print(str);
}
}
Когда мы используем необработанный MyType
, getNames
также стирается, так что он возвращает необработанный List
!
JLS 4.6 продолжает объяснять следующее:
Стирание типа также сопоставляет сигнатуру конструктора или метода с сигнатурой, которая не имеет параметризованных типов или переменных типа. Стирание сигнатуры конструктора или метода s
является сигнатурой, состоящей из той же самой имя s
и стирание всех типов формальных параметров, приведенных в s
.
Тип возвращаемого значения метода и параметры типа универсального метода или конструктора также стираются при удалении сигнатуры метода или конструктора.
Удаление подписи универсального метода не имеет параметров типа.
В следующем сообщении об ошибке содержатся некоторые мысли Маурицио Симадамор, разработчика компилятора, и Алекса Бакли, одного из авторов JLS, о том, почему такое поведение должно происходить: https://bugs.openjdk.java.net/browse/JDK-6400189. (Короче говоря, упрощает спецификацию.)
Если это небезопасно, почему разрешено использовать необработанный тип?
Вот еще одна цитата из JLS 4.8:
Использование необработанных типов разрешено только в качестве уступки совместимости устаревшего кода. Настоятельно не рекомендуется использовать необработанные типы в коде, написанном после введения универсальности в язык программирования Java. Вполне возможно, что будущие версии языка программирования Java будут запрещать использование необработанных типов.
Effective Java 2nd Edition также имеет следующее:
Учитывая, что вы не должны использовать необработанные типы, почему разработчики языка разрешили это? Для обеспечения совместимости.
Платформа Java собиралась вступить во второе десятилетие, когда появились дженерики, и существовало огромное количество Java-кода, который не использовал дженерики. Было сочтено критически важным, чтобы весь этот код оставался легальным и совместимым с новым кодом, который использует дженерики. Должно быть допустимо передавать экземпляры параметризованных типов в методы, которые были разработаны для использования с обычными типами, и наоборот. Это требование, известное как совместимость миграции , привело к принятию решения о поддержке необработанных типов.
В итоге необработанные типы НИКОГДА не должны использоваться в новом коде. Вы всегда должны использовать параметризованные типы .
Нет ли исключений?
К сожалению, из-за того, что дженерики Java не реализованы, есть два исключения, где необработанные типы должны использоваться в новом коде:
- Литералы класса, например
List.class
, а не List<String>.class
instanceof
операнд, например o instanceof Set
, а не o instanceof Set<String>
Смотри также