Что такое сырой тип и почему мы не должны его использовать?

Вопросов:

  • Что такое типы raw в Java и почему я часто слышу, что их нельзя использовать в новом коде?
  • Какая альтернатива, если мы не сможем использовать сырые типы, и как это лучше?

    Что такое сырой тип?

    Спецификация языка Java определяет необработанный тип следующим образом:

    JLS 4.8 Типы сырья

    Необработанный тип определяется как один из следующих:

    • Тип ссылки, который формируется путем принятия имени объявления типового типа без списка аргументов сопутствующего типа.

    • Тип массива, тип элемента которого является необработанным.

    • Тип static типа необработанного типа R который не наследуется от суперclassа или суперинтерфейса R

    Вот пример для иллюстрации:

     public class MyType { 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 mt1; // no warning: type parameter given MyType mt2; // no warning: type parameter given (wildcard OK!) } } 

    Здесь MyType является параметризованным типом ( JLS 4.5 ). Общепринято, чтобы разговорно ссылаться на этот тип как на простой MyType , но технически это имя MyType .

    mt имеет исходный тип (и генерирует предупреждение о компиляции) первой точкой маркера в приведенном выше определении; inn также есть сырой тип по третьей точке.

    MyType.Nested не является параметризованным типом, хотя он является типом-членом параметризованного типа MyType , потому что он является 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 names = new ArrayList(); names.add("John"); names.add("Mary"); names.add(Boolean.FALSE); // compilation error! 

    Конечно, если вы хотите, чтобы names допускали Boolean , вы можете объявить их как Listnames , и приведенный выше код будет скомпилирован.

    Смотрите также

    • Учебники Java / Generics

    Как исходный тип отличается от использования как параметров типа?

    Ниже приведена цитата из Effective Java 2nd Edition, Item 23: Не используйте необработанные типы в новом коде :

    Какая разница между исходным типом List и параметризованным типом List ? Понятно, что первый отказался от проверки общего типа, в то время как последний явно сказал компилятору, что он способен хранить объекты любого типа. Хотя вы можете передать List в параметр List типов, вы не можете передать его параметру типа List . Существуют правила подтипирования для генериков, а List – это подтип типа raw type List , но не параметризованного типа List . Как следствие, вы теряете безопасность типов, если используете такой тип raw, как List , но не для использования параметризованного типа List .

    Чтобы проиллюстрировать эту точку, рассмотрим следующий метод, который принимает List и добавляет new Object() .

     void appendNewObject(List list) { list.add(new Object()); } 

    Дженерики в Java инвариантны. List не является List , поэтому следующее генерирует предупреждение компилятора:

     List names = new ArrayList(); appendNewObject(names); // compilation error! 

    Если вы объявили appendNewObject чтобы взять параметр типа «Тип» в качестве исходного appendNewObject , тогда это будет скомпилировано, и поэтому вы потеряете безопасность типа, которую вы получаете от дженериков.

    Смотрите также

    • В чем разница между и ?
    • java generics (not) ковариация

    Как исходный тип отличается от использования Как параметра типа?

    List , List и т. Д. – это все List , Поэтому может возникнуть соблазн просто сказать, что они просто являются List . Однако существует большая разница: поскольку List определяет только add(E) , вы не можете добавить только какой-либо произвольный объект в List . С другой стороны, поскольку в List типа сырого типа нет безопасности типа, вы можете add что-нибудь в List .

    Рассмотрим следующий вариант предыдущего fragmentа:

     static void appendNewObject(List list) { list.add(new Object()); // compilation error! } //... List names = new ArrayList(); appendNewObject(names); // this part is fine! 

    Компилятор отлично справился с защитой от потенциального нарушения инвариантности типа List ! Если бы вы объявили этот параметр как список типов исходного типа, тогда код будет компилироваться, и вы нарушите инвариант типа List names .


    Необработанный тип – это стирание этого типа

    Вернуться к JLS 4.8:

    В качестве типа можно использовать стирание параметризованного типа или стирание типа массива, тип элемента которого является параметризованным. Такой тип называется сырым типом .

    […]

    Суперclassами (соответственно суперинтерфейсами) необработанного типа являются стирания суперclassов (суперинтерфейсов) любой из параметризации родового типа.

    Тип конструктора, метод экземпляра или static поля необработанного типа C который не унаследован от его суперclassов или суперинтерфейсов, является необработанным типом, который соответствует стиранию его типа в общем объявлении, соответствующем C

    В более простых выражениях, когда используется тип raw, конструкторы, методы экземпляра и static поля также стираются .

    Возьмем следующий пример:

     class MyType { List getNames() { return Arrays.asList("John", "Mary"); } public static void main(String[] args) { MyType rawType = new MyType(); // unchecked warning! // required: List found: List List 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 стирается, так что он возвращает необработанный List !

    JLS 4.6 продолжает объяснять следующее:

    Тип erasure также сопоставляет подпись конструктора или метода сигнатуре, которая не имеет параметризованных типов или переменных типа. Стирание конструктора или сигнатуры метода 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-дженерики не переопределены, есть два исключения, в которых необработанные типы должны использоваться в новом коде:

    • Литералы classов, например List.class , а не List.class
    • instanceof operand, например o instanceof Set , а не o instanceof Set

    Смотрите также

    • Почему Collection.class Illegal?

    Что такое типы 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 aNumberList = new ArrayList(); 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 aList = new ArrayList(); //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 { int id; public int compareTo(MyCompareAble other) {return this.id - other.id;} } 

    Обратите внимание, что невозможно реализовать интерфейс CompareAble с compareTo(MyCompareAble) с необработанными типами. Почему вы не должны их использовать:

    • Любой Object хранящийся в Collection должен быть отлит до его использования
    • Использование дженериков позволяет проверять время компиляции
    • Использование исходных типов – это то же самое, что хранить каждое значение как Object

    Что делает компилятор: Generics обратно совместимы, они используют те же classы java, что и типы raw. Магия происходит в основном во время компиляции.

     List someStrings = new ArrayList(); 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 и передает его в первую очередь после его литья.

    Каковы альтернативы сырым типам: использование дженериков

    Необработанный тип – это имя общего classа или интерфейса без аргументов типа. Например, учитывая общий class Box:

     public class Box { public void set(T t) { /* ... */ } // ... } 

    Чтобы создать параметризованный тип Box , вы указываете фактический аргумент типа для параметра формального типа T :

     Box intBox = new Box<>(); 

    Если фактический аргумент типа опущен, вы создаете необработанный тип Box :

     Box rawBox = new Box(); 

    Поэтому Box является сырым типом универсального типа Box . Однако не общий тип или тип интерфейса не является сырым типом.

    Необработанные типы отображаются в устаревшем коде, поскольку многие classы API (например, classы Collections) не были типичными до JDK 5.0. При использовании необработанных типов вы, по существу, получаете поведение, предшествующее дженерикам – Box дает вам Object . Для обратной совместимости допускается присвоение параметризованного типа его необработанному типу:

     Box stringBox = new Box<>(); Box rawBox = stringBox; // OK 

    Но если вы назначите тип raw параметризованному типу, вы получите предупреждение:

     Box rawBox = new Box(); // rawBox is a raw type of Box Box intBox = rawBox; // warning: unchecked conversion 

    Вы также получаете предупреждение, если используете необработанный тип для вызова общих методов, определенных в соответствующем родовом типе:

     Box 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 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 bi = createBox(); ^ 1 warning 

    Чтобы полностью отключить непроверенные предупреждения, используйте флаг -Xlint: -unchecked. @SuppressWarnings("unchecked") подавляют непроверенные предупреждения. Если вы не знакомы с синтаксисом @SuppressWarnings , см. Аннотации.

    Оригинальный источник: учебные пособия по Java

      private static List list = new ArrayList(); 

    Вы должны указать параметр типа.

    Предупреждение предупреждает, что типы, которые определены для поддержки генериков, должны быть параметризованы, а не использовать их необработанную форму.

    List определен для поддержки generics: public class List . Это позволяет выполнять многие операции типа, проверенные временем компиляции.

    «Сырой» тип в Java – это class, который является неэквивалентным и имеет дело с «сырыми» объектами, а не с типовыми типами типовых параметров.

    Например, до того, как были созданы дженерики Java, вы должны использовать class коллекции следующим образом:

     LinkedList list = new LinkedList(); list.add(new MyObject()); MyObject myObject = (MyObject)list.get(0); 

    Когда вы добавляете свой объект в список, ему все равно, какой тип объекта он есть, и когда вы его получите из списка, вы должны явно указать его на тот тип, который вы ожидаете.

    Используя generics, вы удаляете «неизвестный» коэффициент, потому что вы должны явно указать, какой тип объектов может идти в списке:

     LinkedList list = new LinkedList(); list.add(new MyObject()); MyObject myObject = list.get(0); 

    Обратите внимание, что с помощью дженериков вам не нужно бросать объект, исходящий из вызова get, сбор предварительно задан для работы с MyObject. Этот факт является основным движущим фактором для генериков. Он меняет источник ошибок времени выполнения во что-то, что можно проверить во время компиляции.

    Что такое сырой тип и почему я часто слышу, что их нельзя использовать в новом коде?

    «Необработанный тип» – это использование универсального classа без указания аргумента (ов) типа для его параметризованного типа (ов), например, с помощью List вместо List . Когда дженерики были введены в Java, несколько classов были обновлены для использования дженериков. Использование этого classа в качестве «необработанного типа» (без указания аргумента типа) позволило сохранить прежний код.

    «Необработанные типы» используются для обратной совместимости. Их использование в новом коде не рекомендуется, потому что использование универсального classа с аргументом типа позволяет усилить типизацию, что, в свою очередь, может улучшить понятность кода и привести к возникновению потенциальных проблем раньше.

    Какая альтернатива, если мы не сможем использовать сырые типы, и как это лучше?

    Предпочтительной альтернативой является использование общих classов по назначению – с подходящим аргументом типа (например, List ). Это позволяет программисту более конкретно указывать типы, придавая будущим сопровождающим больше смысла предполагаемое использование переменной или структуры данных и позволяет компилятору обеспечивать лучшую безопасность типов. Эти преимущества вместе могут улучшить качество кода и помочь предотвратить появление некоторых ошибок кодирования.

    Например, для метода, когда программист хочет, чтобы переменная List с именем «names» содержала только строки:

     List names = new ArrayList(); names.add("John"); // OK names.add(new Integer(1)); // compile error 

    Компилятор хочет, чтобы вы это написали:

     private static List list = new ArrayList(); 

    потому что в противном случае вы могли бы добавить любой тип, который вам нравится, в list , создавая экземпляр как new ArrayList() бессмысленно. Генераторы Java – это только функция времени компиляции, поэтому объект, созданный с помощью new ArrayList() , с радостью примет элементы Integer или JFrame если они привязаны к ссылке List «raw type» – сам объект ничего не знает о том, какие типы он должен содержать, только компилятор.

    Здесь я рассматриваю несколько случаев, посредством которых вы можете очистить концепцию

     1. ArrayList arr = new ArrayList(); 2. ArrayList arr = new ArrayList(); 3. ArrayList arr = new ArrayList(); 

    Дело 1

    ArrayList arr – это ссылочная переменная ArrayList с типом String ArralyList объект ArralyList типа 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) 

    Случай 2

    В этом случае ArrayList arr является строгим типом, но ваш Object 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 Type ссылается на ссылочную переменную типа ArrayList Strict .

    Случай 3

    В этом случае ArrayList arr является сырым типом, но ваш Object new ArrayList(); является строгим типом.

      arr.add("hello"); arr.add(23); //compiles fine but raise the warning. 

    Он добавит в него любой тип объекта, потому что arr – это Raw Type.

    Предупреждение : – Объект Strict типа ссылается на переменную, привязанную к raw типу.

    Необработанным типом является отсутствие параметра типа при использовании родового типа.

    Необработанный тип не должен использоваться, потому что это может привести к ошибкам во время выполнения, например, вставить double в то, что должно было быть Set int s.

     Set set = new HashSet(); set.add(3.45); //ok 

    Когда вы извлекаете материал из Set , вы не знаете, что выйдет. Предположим, что вы ожидаете, что это все int s, вы передаете его Integer ; исключение во время выполнения, когда приходит double 3.45.

    С параметром типа, добавленным в ваш Set , вы сразу же получите ошибку компиляции. Эта превентивная ошибка позволяет устранить проблему, прежде чем что-то взорвется во время работы (таким образом, сэкономив время и усилия).

     Set set = new HashSet(); set.add(3.45); //NOT ok. 

    Говорят, что ваш list – это List объектов, не идентифицированных. Это значит, что Java не знает, какие объекты находятся внутри списка. Затем, когда вы хотите итерировать список, вы должны использовать каждый элемент, чтобы иметь доступ к свойствам этого элемента (в данном случае, String).

    В общем, это лучшая идея параметризации коллекции, поэтому у вас нет проблем с преобразованием, вы сможете добавлять элементы параметризованного типа, и ваш редактор предложит вам подходящие методы для выбора.

     private static List list = new ArrayList(); 

    учебная страница .

    Необработанный тип – это имя общего classа или интерфейса без аргументов типа. Например, учитывая общий class Box:

     public class Box { public void set(T t) { /* ... */ } // ... } 

    Чтобы создать параметризованный тип поля, вы указываете фактический аргумент типа для параметра формального типа T:

     Box intBox = new Box<>(); 

    Если аргумент фактического типа опущен, вы создаете необработанный тип Box:

     Box rawBox = new Box(); 

    Вот еще один случай, когда сырые типы вас укусят:

     public class StrangeClass { @SuppressWarnings("unchecked") public  X getSomethingElse() { return (X)"Testing something else!"; } public static void main(String[] args) { final StrangeClass withGeneric = new StrangeClass<>(); final StrangeClass withoutGeneric = new StrangeClass(); final String value1, value2; // Works value1 = withGeneric.getSomethingElse(); // Produces compile error: // incompatible types: java.lang.Object cannot be converted to java.lang.String value2 = withoutGeneric.getSomethingElse(); } } 

    Как уже упоминалось в принятом ответе, вы теряете всякую поддержку дженериков в коде исходного типа. Каждый параметр типа преобразуется в его стирание (которое в приведенном выше примере является просто Object ).

    Я нашел эту страницу после выполнения некоторых выборочных упражнений и с тем же самым загадкой.

    ============== Я перешел от этого кода в качестве примера по образцу ===============

     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 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();) { Entry entry = i.next(); System.out.println(entry.getKey() + " :\t" + entry.getValue()); } } 

    ================================================== =============================

    Это может быть безопаснее, но потребовалось 4 часа, чтобы одурачить философию ...

    Сырые типы прекрасны, когда они выражают то, что вы хотите выразить.

    Например, функция десериализации может возвращать List , но не знает тип элемента списка. Таким образом, List является подходящим типом возврата.

    Interesting Posts

    Почему мы не должны открывать изображение в электронном письме?

    Ошибка Grub "no such device" не соответствует конфигурации

    Свойства Java UTF-8 в Eclipse

    Динамическое распределение неизвестной матрицы в C

    Confused with wpf ComboBox DisplayMemberPath, SelectedValue и SelectedValuePath

    Как определить функцию lambda для копирования копии вместо ссылки на C #?

    Как связать родную библиотеку и библиотеку JNI внутри JAR?

    Сделать регистр с регулярным выражением нечувствительным в ASP.NET RegularExpressionValidator

    Получение списка активных активных streamов в .NET?

    Создание экрана предпочтений с поддержкой (v21) Панели инструментов

    Почему сценарий моих приложений, развернутый как исполняемый API API, возвращает разрешение «Отказано»?

    Можно ли переименовать шрифты в Windows 7?

    Аргументы командной строки Eclipse

    Слушайте изменения в DIV и действуйте соответственно

    Можно ли настроить статическую IP-альтернативу для всех адаптеров с помощью реестра?

    Давайте будем гением компьютера.