Что может произойти, если один из параметров типа не был указан при создании экземпляра коллекции? - PullRequest
0 голосов
/ 08 июня 2011

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

ArrayList<Integer> ali = new ArrayList<Integer>();

Говорят, что с этим соглашением, некоторые ошибки, такие как

String s = (String)ali(0)

Может привести к ошибке компиляции вместо исключений времени выполнения.

Однако я заметил, что хотя

ArrayList ali = new ArrayList<Integer>();

Будет причиной ситуации выше, вызывать исключения во время выполнения,

ArrayList<Integer> ali = new ArrayList();

Все равно будет вызывать ошибку времени компиляции в описанной выше ситуации.

Есть ли что-то, что я пропускаю, или мы могли бы игнорировать тип справа, если нам не нужна ясность кода?

Спасибо!

Ответы [ 6 ]

1 голос
/ 08 июня 2011

Вы правы в том, что ваш последний фрагмент кода на самом деле не опасен (хотя он генерирует предупреждение компилятора).

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

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

Однако компилятор не может гарантировать это в целом, поскольку это деталь реализации того, как работает конструктор ArrayList, который делает это безопасным. (Другая реализация списка может «публиковать» ссылку на себя извне, которая затем может быть использована для вставки элементов неправильного типа в этот список). Компилятор просто видит, что вы присваиваете что-то необработанного типа ArrayList переменной типа ArrayList<Integer>, и правильно говорит, что «вещь с правой стороны могла бы использоваться для знаете, в прошлом, кроме целых чисел, вы уверены, что это нормально? " Это примерно эквивалентно

ArrayList al = new ArrayList();
ArrayList<Integer> ali = al;

, где в этом слегка расширенном случае «временная» переменная al позволяет вызывать al.add("not an int") без ошибок времени компиляции.

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

1 голос
/ 08 июня 2011

Компилятор только проверяет, правильно ли используется переменная , которая объявляет универсальный тип.Если тип переменных является необработанным, то он не будет жаловаться (с ошибкой).Поэтому следующие строки компилируются и запускаются без ошибок:

ArrayList list = new ArrayList<Integer>();
list.add("hello");
String s = (String) list.get(0);

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

Как только переменная имеет универсальный тип, компилятор может проверить, правильно ли вы используете переменную (например, для коллекции: компилятор знает , что get(0)вернет универсальный тип и может пожаловаться на нелегальные приведения)

Последний пример безопасен (в данном случае).Это критично , если конструктор использует некоторые типизированные параметры.

Следующие строки показывают проблему:

ArrayList<Double> doubles = new ArrayList<Double>();
ArrayList<Integer> integers1 = new ArrayList<Integer>(doubles);  // error
ArrayList<Integer> integers2 = new ArrayList(doubles);           // no error

С третьей строкой мы можем законно заполнить a Integer типизированный массив со значениями Double, мы просто должны игнорировать предупреждение (и перехватывать все исключения во время выполнения;))

OT и Trivia

с Java7 получаем бриллиантовый оператор:

ArrayList<List<Integer>> multilist = new ArrayList<List<Integer>>(); // Java 1.5+
ArrayList<List<Integer>> multilist = new ArrayList<>();              // Java 7+
1 голос
/ 08 июня 2011
ArrayList<Integer> ali = new ArrayList();

и

ArrayList ali = new ArrayList<Integer>();

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

Вы делаете интересное замечание с помощью:

ArrayList<Integer> ali = new ArrayList();

Поскольку вы будете использовать только ali, у вас есть ссылки на безопасность.Однако у вас будет предупреждение компилятора для правой части выражения, поэтому лучше добавить параметризованный тип и не допускать предупреждений в компиляторе.Причина, по которой компилятор предупреждает вас, заключается в том, что кто-то может прийти и сделать это:

ArrayList<String> strings = new ArrayList<String>();
ArrayList<Integer> integers = new ArrayList(strings);

О, нет, у вас теперь есть строки в целых числах!

Именно здесь вывод типа Java 7то есть

ArrayList<Integer> ali = new ArrayList<>();

Таким образом, больше не будет необходимости указывать параметризованный тип, поскольку выводится Integer.Вы можете сделать это в Java 5 или 6, написав универсальный метод, такой как makeArrayList(), который выводит тип (см. Книгу Джошуа Блоха «Эффективная Java»)

0 голосов
/ 08 июня 2011

Сами по себе ни один из них не вызовет ошибок времени выполнения:

ArrayList ali = new ArrayList<Integer>();

или

ArrayList<Integer> ali = new ArrayList();

Однако это только потому, что вы не пытались заполнить список.Если вы это сделаете и допустите ошибку, вы можете получить неожиданные ClassCastException s при использовании значений, извлеченных из списка.Например:

ArrayList ali = new ArrayList();
ali.add("Hi mum");
ArrayList<Integer> oop = ali;  // unsafe conversion

Integer first = oop.get(0);

Последняя строка не выдаст ошибку или предупреждение компиляции, но во время выполнения выдаст ClassCastException.CCE выбрасывается, потому что компилятор выполняет неявное приведение типа как часть присваивания.

0 голосов
/ 08 июня 2011

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

0 голосов
/ 08 июня 2011

Проблема в том, что

ArrayList ali = new ArrayList<Integer>();

- это нетипизированная коллекция. Компилятор предупреждает вас об этом с помощью этого предупреждающего сообщения:

ArrayList is a raw type. References to generic type ArrayList<E> should be parameterized

Однако, поскольку вы не ввели его, компилятор не может знать, какие типы находятся в переменной ali.

Во время выполнения, однако, тип стирается - у вас фактически есть ArrayList<Object>. Когда вы извлекаете элемент (целое число) и пытаетесь присвоить его строке, он взрывается, конечно, с ClassCastException

...