Вопросов:
- Что такое необработанные типы в Java, и почему я часто слышу, что они не должны использоваться в новом коде?
- Какая альтернатива, если мы не можем использовать необработанные типы, и как это лучше?
Ответы:
Спецификация языка 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!
Конечно, если вы НЕ хотите , names
чтобы позволить Boolean
, то вы можете объявить его как List<Object> names
и приведенный выше код будет компилироваться.
<Object>
качестве параметров типа?Ниже приводится цитата из Effective Java 2nd Edition, пункт 23. Не используйте необработанные типы в новом коде :
В чем разница между необработанным типом
List
и параметризованным типомList<Object>
? Грубо говоря, первый отказался от проверки универсального типа, в то время как последний явно сказал компилятору, что он способен содержать объекты любого типа. Хотя вы можете передаватьList<String>
параметр типаList
, вы не можете передать его параметру типаList<Object>
. Существуют правила подтипов для обобщений, иList<String>
это подтип необработанного типаList
, но не параметризованного типаList<Object>
. Как следствие, вы теряете безопасность типов, если вы используете сырой тип какList
, но не если вы используете параметризованный тип какList<Object>
.
Чтобы проиллюстрировать это, рассмотрим следующий метод, который принимает List<Object>
и добавляет a new Object()
.
void appendNewObject(List<Object> list) {
list.add(new Object());
}
Обобщения в Java инвариантны. A List<String>
не является a List<Object>
, поэтому следующее генерирует предупреждение компилятора:
List<String> names = new ArrayList<String>();
appendNewObject(names); // compilation error!
Если бы вы объявили, appendNewObject
что в List
качестве параметра принимается необработанный тип , он скомпилируется, и поэтому вы потеряете безопасность типов, которую вы получаете от обобщений.
<?>
в качестве параметра типа?List<Object>
и List<String>
т. д. все List<?>
, так что может быть заманчиво просто сказать, что они просто List
вместо. Тем не менее, есть существенное отличие: поскольку a List<E>
определяет только add(E)
, вы не можете добавить любой произвольный объект в a 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>
o instanceof Set<?>
также разрешен, чтобы избежать необработанного типа (хотя это только поверхностно в этом случае).
n
удаленных компонентов для каждого реализующего класса с идентичным кодом.
TypeName.class
, где TypeName
простой идентификатор ( jls ). Говоря гипотетически, я думаю, что на самом деле это тоже может быть. Может быть, в качестве подсказки, List<String>.class
это вариант, который JLS специально вызывает ошибку компилятора, поэтому, если они когда-нибудь добавят ее в язык, я ожидаю, что это тот, который они используют.
Что такое необработанные типы в Java, и почему я часто слышу, что они не должны использоваться в новом коде?
Raw-типы - это древняя история языка 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
Более сложный сравниваемый интерфейс:
//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
хранится в a Collection
, должно быть разыграно перед использованием.Object
Что делает компилятор: Generics обратно совместимы, они используют те же классы 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
функции, одну из которых принимает a, MyCompareAble
а другую принимает Object
и передает его первой после приведения.
Какие альтернативы необработанным типам: Используйте дженерики
Необработанный тип - это имя универсального класса или интерфейса без аргументов типа. Например, учитывая общий класс 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
Вы также получите предупреждение, если используете необработанный тип для вызова универсальных методов, определенных в соответствующем универсальном типе:
Box<String> stringBox = new Box<>();
Box rawBox = stringBox;
rawBox.set(8); // warning: unchecked invocation to set(T)
Предупреждение показывает, что необработанные типы обходят общие проверки типов, откладывая перехват небезопасного кода до времени выполнения. Поэтому вам следует избегать использования необработанных типов.
Раздел Erasure Type содержит больше информации о том, как компилятор 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» отключено, хотя компилятор дает подсказку. Чтобы увидеть все «непроверенные» предупреждения, перекомпилируйте с -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 Tutorials
«Необработанный» тип в 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. Этот факт является основным движущим фактором для дженериков. Это превращает источник ошибок времени выполнения во что-то, что можно проверить во время компиляции.
?
прежнему предлагает безопасность типов. Я покрыл это в своем ответе.
private static List<String> list = new ArrayList<String>();
Вы должны указать тип-параметра.
Предупреждение предупреждает, что типы, которые определены для поддержки обобщений, должны быть параметризованы, а не использовать их необработанную форму.
List
определяется поддержки дженериков: public class List<E>
. Это позволяет выполнять многие безопасные для типов операции, которые проверяются во время компиляции.
private static List<String> list = new ArrayList<>();
Что такое необработанный тип и почему я часто слышу, что его не следует использовать в новом коде?
«Необработанный тип» - это использование универсального класса без указания аргумента (ов) типа для его параметризованного типа (типов), например, с использованием List
вместо List<String>
. Когда дженерики были введены в Java, несколько классов были обновлены для использования дженериков. Использование этих классов в качестве «необработанного типа» (без указания аргумента типа) позволило устаревшему коду все еще компилироваться.
«Необработанные типы» используются для обратной совместимости. Их использование в новом коде не рекомендуется, поскольку использование универсального класса с аргументом типа обеспечивает более строгую типизацию, что, в свою очередь, может улучшить понятность кода и привести к более раннему выявлению потенциальных проблем.
Какая альтернатива, если мы не можем использовать необработанные типы, и как это лучше?
Предпочтительной альтернативой является использование обобщенных классов по назначению - с подходящим аргументом типа (например List<String>
). Это позволяет программисту более конкретно указывать типы, сообщает будущим разработчикам больше смысла о предполагаемом использовании переменной или структуры данных, а также позволяет компилятору обеспечивать лучшую безопасность типов. Вместе эти преимущества могут улучшить качество кода и предотвратить появление некоторых ошибок кодирования.
Например, для метода, в котором программист хочет убедиться, что переменная List с именем 'names' содержит только строки:
List<String> names = new ArrayList<String>();
names.add("John"); // OK
names.add(new Integer(1)); // compile error
polygenelubricants
ссылки «необработанного типа» из stackoverflow.com/questions/2770111/… в мой собственный ответ, но я полагаю, что оставлю их для использования в своем собственном ответе.
Компилятор хочет, чтобы вы написали это:
private static List<String> list = new ArrayList<String>();
потому что в противном случае вы можете добавить любой тип, который вам нравится list
, делая создание экземпляров new ArrayList<String>()
бессмысленным. Обобщения Java являются только функцией времени компиляции, поэтому созданный объект с new ArrayList<String>()
радостью примет элементы Integer
или JFrame
элементы, если они назначены для ссылки на «необработанный тип» List
- сам объект ничего не знает о том, какие типы он должен содержать, только компилятор.
Здесь я рассматриваю несколько случаев, с помощью которых вы можете прояснить концепцию
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
объект типа String
. Это означает, что он может содержать только объект типа String.
Это строгий, а String
не необработанный тип, поэтому он никогда не выдаст предупреждение.
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
.
Предупреждение : -
Raw
Объект типа связан сStrict
типом ссылочной переменнойArrayList
.
В этом случае ArrayList arr
это необработанный тип, но ваш объект new ArrayList<String>();
является строгим типом.
arr.add("hello");
arr.add(23); //compiles fine but raise the warning.
Он добавит в него любой тип объекта, потому что arr
это необработанный тип.
Предупреждение : -
Strict
Объектraw
типа ссылается на переменную, на которую ссылается тип.
Сырья типа является отсутствие в параметре типа при использовании универсального типа.
Сырье типа не следует использовать , поскольку это может привести к ошибкам во время выполнения, как и вставки double
в то , что должно было быть Set
в int
с.
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
из unespecified объектов. То есть Java не знает, какие объекты находятся внутри списка. Затем, когда вы хотите перебрать список, вы должны привести каждый элемент, чтобы иметь доступ к свойствам этого элемента (в данном случае, String).
В целом, лучше параметризовать коллекции, так что у вас не будет проблем с преобразованием, вы сможете добавлять только элементы параметризованного типа, и ваш редактор предложит вам подходящие методы для выбора.
private static List<String> list = new ArrayList<String>();
Необработанный тип - это имя универсального класса или интерфейса без аргументов типа. Например, учитывая общий класс Box:
public class Box<T> {
public void set(T t) { /* ... */ }
// ...
}
Чтобы создать параметризованный тип Box, вы предоставляете фактический аргумент типа для параметра формального типа T:
Box<Integer> intBox = new Box<>();
Если фактический аргумент типа опущен, вы создаете необработанный тип Box:
Box rawBox = new Box();
Избегайте необработанных типов
Необработанные типы относятся к использованию универсального типа без указания параметра типа.
Например ,
Список является необработанным типом, а 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
Я нашел эту страницу после того, как выполнил несколько типовых упражнений и получил точно такую же загадку.
============== Я пошел из этого кода, как показано в примере ===============
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 часа, чтобы разобраться в философии ...
Сырые типы хороши, когда они выражают то, что вы хотите выразить.
Например, функция десериализации может возвращать a List
, но она не знает тип элемента списка. Так List
что соответствующий тип возврата здесь.