Вопросы:
- Что такое типы raw в Java, и почему я часто слышу, что они не должны использоваться в новом коде?
- Какая альтернатива, если мы не сможем использовать сырые типы и как это лучше?
Спецификация языка Java определяет необработанный тип следующим образом:
Необработанный тип определяется как один из:
Тип ссылки, который формируется путем принятия имени объявления типового типа без списка аргументов сопутствующего типа.
Тип массива, тип элемента которого является необработанным.
Тип члена
static
не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!
Конечно, если вы хотите names
разрешить Boolean
, тогда вы можете объявить его как List<Object> names
, и приведенный выше код будет скомпилирован.
<Object>
как параметры типа?Ниже приведена цитата из Effective Java 2nd Edition, Item 23: Не используйте raw-типы в новом коде:
Какая разница между исходным типом
List
и параметризованным типомList<Object>
? Понятно, что первый отказался от проверки общего типа, в то время как последний явно сказал компилятору, что он способен хранить объекты любого типа. Пока вы можете передатьList<String>
параметру типаList
, вы не можете передать его параметру типаList<Object>
. Существуют правила подтипирования для генериков, аList<String>
- подтип исходного типаList
, но не параметризованного типаList<Object>
. Как следствие, вы теряете безопасность типов, если используете тип raw, напримерList
, но не для использования параметризованного типа типаList<Object>
.
Чтобы проиллюстрировать эту точку, рассмотрим следующий метод, который принимает List<Object>
и добавляет a new Object()
.
void appendNewObject(List<Object> list) {
list.add(new Object());
}
Дженерики в Java инвариантны. A List<String>
не является List<Object>
, поэтому следующее генерирует предупреждение компилятора:
List<String> names = new ArrayList<String>();
appendNewObject(names); // compilation error!
Если вы указали appendNewObject
на использование параметра raw type List
as, то это скомпилируется, и поэтому вы потеряете безопасность типа, которую вы получаете от дженериков.
<?>
как параметра типа? List<Object>
, List<String>
и т.д. все List<?>
, поэтому может возникнуть соблазн просто сказать, что они просто List
. Однако существует большая разница: поскольку a 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
.
В более простых выражениях, когда используется тип raw, конструкторы, методы экземпляра и не 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);
}
}
Когда мы используем raw MyType
, getNames
также стирается, так что он возвращает raw List
!
JLS 4.6 продолжает объяснять следующее:
Стирание стилей также сопоставляет подпись конструктора или метода сигнатуре, которая не имеет параметризованных типов или переменных типа. Стирание конструктора или сигнатуры метода
s
является сигнатурой, состоящей из то же имя, что иs
, и стирания всех формальных типов параметров, указанных вs
.Возвращаемый тип метода и параметры типа общего метода или конструктора также подвергаются стиранию, если подпись метода или конструктора стирается.
Стирание сигнатуры общего метода не имеет параметров типа.
В следующем отчете об ошибке содержатся некоторые мысли от Маурицио Чимадамора, разработчика-компилятора, и Алекс Бакли, одного из авторов JLS, о том, почему должно происходить такое поведение: https://bugs.openjdk.java.net/browse/JDK-6400189. (Короче говоря, это упрощает спецификацию.)
Здесь другая цитата из JLS 4.8:
Использование типов raw допускается только как уступка совместимости устаревшего кода. Использование необработанных типов в коде, написанном после введения родословности в язык программирования Java, настоятельно не рекомендуется. Возможно, что будущие версии языка программирования Java будут запрещать использование необработанных типов.
В эффективном Java 2nd Edition также есть следующее:
Учитывая, что вы не должны использовать необработанные типы, почему дизайнеры языка допускают их? Чтобы обеспечить совместимость.
Платформа Java вот-вот вступит в свое второе десятилетие, когда были представлены дженерики, и существовало огромное количество Java-кода, который не использовал дженерики. Было сочтено критически важным, что весь этот код остается законным и совместим с новым кодом, который использует дженерики. Должно быть законным передавать экземпляры параметризованных типов методам, которые были предназначены для использования с обычными типами, и наоборот. Это требование, известное как совместимость с миграцией, приняло решение поддерживать типы raw.
Таким образом, необработанные типы НИКОГДА не должны использоваться в новом коде. Вы всегда должны использовать параметризованные типы.
К сожалению, поскольку Java-дженерики не переопределены, есть два исключения, в которых необработанные типы должны использоваться в новом коде:
List.class
, а не List<String>.class
Оператор instanceof
, например. o instanceof Set
, не o instanceof Set<String>
Что такое типы raw в Java и почему я часто слышу, что они не должны использоваться в новом коде?
Необработанные типы - это древняя история языка Java. В начале было Collections
, и они держали Objects
не более и не меньше. Каждая операция на Collections
требовала от Object
отбрасывания до требуемого типа.
List aList = new ArrayList();
String s = "Hello World!";
aList.add(s);
String c = (String)aList.get(0);
В то время как это работало большую часть времени, произошли ошибки
List aNumberList = new ArrayList();
String one = "1";//Number one
aNumberList.add(one);
Integer iOne = (Integer)aNumberList.get(0);//Insert ClassCastException here
В старых нетривиальных коллекциях нельзя было обеспечить безопасность типов, поэтому программисту приходилось запоминать то, что он хранил в коллекции.
Генераторы, изобретенные, чтобы обойти это ограничение, разработчик объявит сохраненный тип один раз, и вместо этого компилятор сделает это.
List<String> aNumberList = new ArrayList<String>();
aNumberList.add("one");
Integer iOne = aNumberList.get(0);//Compile time error
String sOne = aNumberList.get(0);//works fine
Для сравнения:
// Old style collections now known as raw types
List aList = new ArrayList(); //Could contain anything
// New style collections with Generics
List<String> aList = new ArrayList<String>(); //Contains only Strings
Более сложный интерфейс Compareable:
//raw, not type save can compare with Other classes
class MyCompareAble implements CompareAble
{
int id;
public int compareTo(Object other)
{return this.id - ((MyCompareAble)other).id;}
}
//Generic
class MyCompareAble implements CompareAble<MyCompareAble>
{
int id;
public int compareTo(MyCompareAble other)
{return this.id - other.id;}
}
Обратите внимание, что невозможно реализовать интерфейс CompareAble
с compareTo(MyCompareAble)
с необработанными типами.
Почему вы не должны их использовать:
Object
, хранящиеся в Collection
, должны быть представлены перед использованием.Object
Что делает компилятор: Дженерики обратно совместимы, они используют одни и те же классы Java, как это делают сырые типы. Магия происходит в основном во время компиляции.
List<String> someStrings = new ArrayList<String>();
someStrings.add("one");
String one = someStrings.get(0);
Будет скомпилирован как:
List someStrings = new ArrayList();
someStrings.add("one");
String one = (String)someStrings.get(0);
Это тот же код, который вы могли бы написать, если бы вы использовали исходные типы напрямую. Думаю, я не уверен, что происходит с интерфейсом CompareAble
, я предполагаю, что он создает две функции compareTo
, один из которых принимает MyCompareAble
, а другой принимает Object
и передает его в первую очередь после его литья.
Каковы альтернативы сырым типам: Используйте generics
Необработанный тип - это имя общего класса или интерфейса без каких-либо аргументов типа. Например, учитывая общий класс Box:
public class Box<T> {
public void set(T t) { /* ... */ }
// ...
}
Чтобы создать параметризованный тип Box<T>
, вы указываете фактический аргумент типа для параметра формального типа T
:
Box<Integer> intBox = new Box<>();
Если аргумент фактического типа опущен, вы создаете необработанный тип Box<T>
:
Box rawBox = new Box();
Следовательно, Box
является необработанным типом родового типа Box<T>
. Однако не общий тип или тип интерфейса не является сырым типом.
Необработанные типы отображаются в устаревшем коде, поскольку многие классы API (например, классы Collections) не были типичными до JDK 5.0. При использовании сырых типов вы по существу получаете поведение до генериков - a Box
дает вам Object
s. Для обратной совместимости допускается присвоение параметризованного типа его необработанному типу:
Box<String> stringBox = new Box<>();
Box rawBox = stringBox; // OK
Но если вы назначаете необработанный тип параметризованному типу, вы получите предупреждение:
Box rawBox = new Box(); // rawBox is a raw type of Box<T>
Box<Integer> intBox = rawBox; // warning: unchecked conversion
Вы также получаете предупреждение, если используете тип raw для вызова общих методов, определенных в соответствующем родовом типе:
Box<String> stringBox = new Box<>();
Box rawBox = stringBox;
rawBox.set(8); // warning: unchecked invocation to set(T)
Предупреждение показывает, что проверки типов типов обходного типа обходят общий тип, откладывая улов небезопасного кода на время выполнения. Поэтому вам следует избегать использования необработанных типов.
В разделе "Тип стирания" содержится дополнительная информация о том, как компилятор Java использует необработанные типы.
Как уже упоминалось ранее, при смешивании устаревшего кода с общим кодом вы можете встретить предупреждающие сообщения, похожие на следующие:
Примечание. Example.java использует непроверенные или небезопасные операции.
Примечание. Перекомпиляция с -Xlint: непроверенная для деталей.
Это может произойти при использовании более старого API, который работает с необработанными типами, как показано в следующем примере:
public class WarningDemo {
public static void main(String[] args){
Box<Integer> bi;
bi = createBox();
}
static Box createBox(){
return new Box();
}
}
Термин "unchecked" означает, что компилятор не имеет достаточной информации о типе для выполнения всех проверок типов, необходимых для обеспечения безопасности типа. Предупреждение "unchecked" по умолчанию отключено, хотя компилятор дает подсказку. Чтобы просмотреть все "непроверенные" предупреждения, перекомпилируйте с помощью -Xlint: unchecked.
Повторная компиляция предыдущего примера с -Xlint: unchecked показывает следующую дополнительную информацию:
WarningDemo.java:4: warning: [unchecked] unchecked conversion
found : Box
required: Box<java.lang.Integer>
bi = createBox();
^
1 warning
Чтобы полностью отключить непроверенные предупреждения, используйте флаг -Xlint: -unchecked. Аннотация @SuppressWarnings("unchecked")
подавляет непроверенные предупреждения. Если вы не знакомы с синтаксисом @SuppressWarnings
, см. Аннотации.
Оригинальный источник: Учебники по Java
private static List<String> list = new ArrayList<String>();
Вы должны указать параметр типа.
Предупреждение сообщает, что типы, которые определены для поддержки generics, должны быть параметризованы, а не использовать их необработанную форму.
List
определяется для поддержки дженериков: public class List<E>
. Это позволяет выполнять много типов безопасных операций, которые проверяются во время компиляции.
"Явный" тип в Java - это класс, который не является общим и имеет дело с "сырыми" объектами, а не с типом типа типичных параметров типа.
Например, до того, как были созданы дженерики Java, вы должны использовать класс коллекции следующим образом:
LinkedList list = new LinkedList();
list.add(new MyObject());
MyObject myObject = (MyObject)list.get(0);
Когда вы добавляете свой объект в список, ему все равно, какой тип объекта он есть, и когда вы его получите из списка, вы должны явно указать его на тот тип, который вы ожидаете.
Используя generics, вы удаляете "неизвестный" коэффициент, потому что вы должны явно указать, какой тип объектов может идти в списке:
LinkedList<MyObject> list = new LinkedList<MyObject>();
list.add(new MyObject());
MyObject myObject = list.get(0);
Обратите внимание, что с помощью дженериков вам не нужно бросать объект, исходящий из вызова get, сбор предварительно задан для работы с MyObject. Этот факт является основным движущим фактором для генериков. Он меняет источник ошибок времени выполнения во что-то, что можно проверить во время компиляции.
Компилятор хочет, чтобы вы это записывали:
private static List<String> list = new ArrayList<String>();
потому что в противном случае вы могли бы добавить любой тип, который вам нравится, в list
, делая экземпляр как new ArrayList<String>()
бессмысленным. Генераторы Java - это функция времени компиляции, поэтому объект, созданный с помощью new ArrayList<String>()
, с радостью примет элементы Integer
или JFrame
, если они назначены ссылке "raw type" list
- сам объект ничего не знает о какие типы он должен содержать, только компилятор делает.
Что такое необработанный тип и почему я часто слышу, что их нельзя использовать в новом коде?
"Необработанный тип" - это использование универсального класса без указания аргумента (ов) типа для его параметризованного типа (ов), например. используя List
вместо List<String>
. Когда дженерики были введены в Java, несколько классов были обновлены для использования дженериков. Использование этого класса в качестве "необработанного типа" (без указания аргумента типа) позволило сохранить прежний код.
"Необработанные типы" используются для обратной совместимости. Их использование в новом коде не рекомендуется, потому что использование универсального класса с аргументом типа позволяет более сильную типизацию, что, в свою очередь, может улучшить понятность кода и привести к появлению потенциальных проблем раньше.
Какая альтернатива, если мы не сможем использовать необработанные типы и как это лучше?
Предпочтительной альтернативой является использование общих классов по назначению - с подходящим аргументом типа (например, List<String>
). Это позволяет программисту более конкретно указывать типы, придавая будущим сопровождающим больше смысла предполагаемое использование переменной или структуры данных и позволяет компилятору обеспечивать лучшую безопасность типов. Эти преимущества вместе могут улучшить качество кода и помочь предотвратить появление некоторых ошибок кодирования.
Например, для метода, в котором программист хочет, чтобы переменная List, называемая "имена", содержала только строки:
List<String> names = new ArrayList<String>();
names.add("John"); // OK
names.add(new Integer(1)); // compile error
Здесь я рассматриваю несколько случаев, через которые вы можете очистить концепцию
1. ArrayList<String> arr = new ArrayList<String>();
2. ArrayList<String> arr = new ArrayList();
3. ArrayList arr = new ArrayList<String>();
ArrayList<String> arr
это ссылочная переменная ArrayList
с типом String
, которая ссылается на ArralyList
Object of Type String
. Это означает, что он может содержать только объект типа String.
Это строгое значение String
не Raw Type, поэтому оно никогда не будет вызывать предупреждение.
arr.add("hello");// alone statement will compile successfully and no warning.
arr.add(23); //prone to compile time error.
//error: no suitable method found for add(int)
В этом случае ArrayList<String> arr
является строгим типом, но ваш объект new ArrayList();
является сырым типом.
arr.add("hello"); //alone this compile but raise the warning.
arr.add(23); //again prone to compile time error.
//error: no suitable method found for add(int)
здесь arr
является строгим типом. Таким образом, он будет увеличивать время компиляции при добавлении integer
.
Предупреждение: - A
Raw
Тип Объект ссылается на типStrict
Референтная переменнаяArrayList
.
В этом случае ArrayList arr
является сырым типом, но ваш Object new ArrayList<String>();
является строгим типом.
arr.add("hello");
arr.add(23); //compiles fine but raise the warning.
Он добавит в него любой тип объекта, потому что arr
- это тип Raw.
Предупреждение: - A
Strict
Объект Type ссылается на переменнуюRaw
, на которую ссылается переменная.
Необработанный тип - это отсутствие параметра типа при использовании родового типа.
Необработанный тип не должен использоваться, поскольку он может вызвать ошибки времени выполнения, например, вставить double
в то, что должно было быть Set
int
s.
Set set = new HashSet();
set.add(3.45); //ok
При извлечении материала из Set
вы не знаете, что выйдет. Предположим, что вы ожидаете, что это все int
s, вы отбрасываете его на Integer
; исключение во время выполнения, когда идет double
3.45.
С параметром типа, добавленным в ваш Set
, вы сразу же получите ошибку компиляции. Эта превентивная ошибка позволяет устранить проблему, прежде чем что-то взорвется во время работы (таким образом, экономя время и усилия).
Set<Integer> set = new HashSet<Integer>();
set.add(3.45); //NOT ok.
Вот еще один случай, когда необработанные типы будут кусать вас:
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;
// Compiles
value1 = withGeneric.getSomethingElse();
// Produces compile error:
// incompatible types: java.lang.Object cannot be converted to java.lang.String
value2 = withoutGeneric.getSomethingElse();
}
}
Как уже упоминалось в принятом ответе, вы теряете всю поддержку обобщений в коде необработанного типа. Каждый параметр типа преобразуется в его стирание (которое в приведенном выше примере является просто Object
).
Говорят, что ваш list
является list
объектов, не идентифицированных. Это значит, что Java не знает, какие объекты находятся внутри списка. Затем, когда вы хотите итерировать список, вы должны использовать каждый элемент, чтобы иметь доступ к свойствам этого элемента (в данном случае String).
В общем, это лучшая идея параметризации коллекции, поэтому у вас нет проблем с преобразованием, вы сможете добавлять элементы параметризованного типа, и ваш редактор предложит вам подходящие методы для выбора.
private static List<String> list = new ArrayList<String>();
Необработанный тип - это имя общего класса или интерфейса без каких-либо аргументов типа. Например, учитывая общий класс Box:
public class Box<T> {
public void set(T t) { /* ... */ }
// ...
}
Чтобы создать параметризованный тип поля, вы указываете фактический аргумент типа для параметра формального типа T:
Box<Integer> intBox = new Box<>();
Если аргумент фактического типа опущен, вы создаете необработанный тип Box:
Box rawBox = new Box();
Я нашел эту страницу после выполнения некоторых выборочных упражнений и с тем же самым загадкой.
============== Я перешел от этого кода, предоставив образец ================
public static void main(String[] args) throws IOException {
Map wordMap = new HashMap();
if (args.length > 0) {
for (int i = 0; i < args.length; i++) {
countWord(wordMap, args[i]);
}
} else {
getWordFrequency(System.in, wordMap);
}
for (Iterator i = wordMap.entrySet().iterator(); i.hasNext();) {
Map.Entry entry = (Map.Entry) i.next();
System.out.println(entry.getKey() + " :\t" + entry.getValue());
}
====================== Этот код ======================= ==
public static void main(String[] args) throws IOException {
// replace with TreeMap to get them sorted by name
Map<String, Integer> wordMap = new HashMap<String, Integer>();
if (args.length > 0) {
for (int i = 0; i < args.length; i++) {
countWord(wordMap, args[i]);
}
} else {
getWordFrequency(System.in, wordMap);
}
for (Iterator<Entry<String, Integer>> i = wordMap.entrySet().iterator(); i.hasNext();) {
Entry<String, Integer> entry = i.next();
System.out.println(entry.getKey() + " :\t" + entry.getValue());
}
}
=============================================== ================================
Это может быть безопаснее, но потребовалось 4 часа, чтобы одурачить философию...
Сырые типы прекрасны, когда они выражают то, что вы хотите выразить.
Например, функция десериализации может вернуть List
, но не знает тип элемента списка. Таким образом, List
- это соответствующий тип возврата.
Избегайте необработанных типов
Необработанные типы относятся к использованию универсального типа без указания параметра типа.
Например,
Список является необработанным типом, а List<String>
является параметризованным типом.
Когда дженерики были представлены в JDK 1.5, необработанные типы были сохранены только для обеспечения обратной совместимости со старыми версиями Java. Хотя использование необработанных типов все еще возможно,
Их следует избегать:
Они менее выразительны и не документируют себя так же, как параметризованные типы. Пример
import java.util.*;
public final class AvoidRawTypes {
void withRawType() {
//Raw List doesn't self-document,
//doesn't state explicitly what it can contain
List stars = Arrays.asList("Arcturus", "Vega", "Altair");
Iterator iter = stars.iterator();
while (iter.hasNext()) {
String star = (String) iter.next(); //cast needed
log(star);
}
}
void withParameterizedType() {
List < String > stars = Arrays.asList("Spica", "Regulus", "Antares");
for (String star: stars) {
log(star);
}
}
private void log(Object message) {
System.out.println(Objects.toString(message));
}
}
Для справки: https://docs.oracle.com/javase/tutorial/java/generics/rawTypes.html