Что такое необработанный тип и почему мы не должны его использовать? - PullRequest
600 голосов
/ 05 мая 2010

Вопросы:

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

Ответы [ 15 ]

692 голосов
/ 05 мая 2010

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

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

JLS 4.8 Типы сырья

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

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

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

  • Не 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>

Смотри также

58 голосов
/ 06 мая 2010

Что такое необработанные типы в 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, хранящееся в 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 и передает его первому после приведения.

Каковы альтернативы необработанным типам: Используйте генерики

24 голосов
/ 11 августа 2013

Необработанный тип - это имя универсального класса или интерфейса без аргументов типа. Например, учитывая общий класс 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. При использовании необработанных типов вы, по сути, получаете пре-обобщенное поведение - Box дает вам Object с. Для обратной совместимости допускается присвоение параметризованного типа его необработанному типу:

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)

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

В разделе «Стирание типа» содержится дополнительная информация о том, как компилятор 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

19 голосов
/ 16 июня 2010
 private static List<String> list = new ArrayList<String>();

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

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

16 голосов
/ 05 мая 2010

«Необработанный» тип в 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. Этот факт является основным движущим фактором для дженериков. Он изменяет источник ошибок времени выполнения на что-то, что можно проверить во время компиляции.

12 голосов
/ 13 мая 2016

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

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, которая ссылается на 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)

Дело 2

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

Дело 3

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

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

Он добавит в него любой тип объекта, потому что arr является необработанным типом.

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

12 голосов
/ 16 июня 2010

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

private static List<String> list = new ArrayList<String>();

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

11 голосов
/ 05 мая 2010

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

«Необработанный тип» - это использование универсального класса без указания аргумента (ов) типа для его параметризованного типа (типов), например, используя 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
8 голосов
/ 05 мая 2010

A raw -типа - это отсутствие параметра типа при использовании универсального типа.

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

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

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

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

Set<Integer> set = new HashSet<Integer>();
set.add(3.45); //NOT ok.
6 голосов
/ 04 декабря 2017

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

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).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...