В чём различие между unchecked conversion и unchecked cast при работе с обобщёнными типами Java?
Unchecked conversion — неявное присваивание или преобразование между raw-типом и параметризованным типом. Unchecked cast — явное приведение к параметризованному типу, безопасность которого компилятор не может доказать. В обоих случаях появляется предупреждение, но при conversion runtime-проверки обычно нет вовсе, а при cast проверяется только доступная после стирания типов часть типа.
Обобщения появились в Java при сохранении совместимости с кодом и библиотеками, написанными до их появления. Для этого были оставлены raw-типы, позволяющие использовать старые API, но взаимодействие с ними стало потенциально небезопасным.
Предупреждения unchecked показывают места, где компилятор вынужден довериться разработчику. Это компромисс между совместимостью старого кода и статической проверкой параметров типов.
Raw-тип не содержит информации о фактическом аргументе типа. Поэтому компилятор не может доказать, что объект List, полученный из старого API, действительно является List<String>.
Если предупреждение проигнорировать, некорректный элемент может попасть в параметризованную коллекцию. Ошибка часто проявится не в месте преобразования, а позднее — при чтении элемента и неявном приведении к ожидаемому типу.
Unchecked conversion возникает неявно, например при присваивании raw-коллекции параметризованной переменной:
В строке присваивания нет явного оператора приведения. Компилятор принимает преобразование ради совместимости, но предупреждает: содержимое raw могло иметь любой тип. Само присваивание не проверяет элементы и не гарантирует, что они являются String.
Unchecked cast возникает при явном приведении, например от Object или raw-типа к параметризованному типу. Runtime может проверить только стираемый тип List; параметр String обычно недоступен для проверки. Поэтому приведение к List<String> может пройти, а ошибка появиться при чтении элемента.
Разница важна диагностически: conversion указывает на неявное заражение типами при совместимости с raw-кодом, cast — на явно написанное утверждение разработчика о типе объекта. В обоих случаях следует по возможности устранить источник raw-типа, а не просто подавить предупреждение.
Если проверка действительно необходима, безопаснее получить элементы как Object, проверить каждый через instanceof и сформировать новую List<String>. Локальное @SuppressWarnings("unchecked") допустимо только после доказательства инварианта, например если метод гарантирует, что коллекция создана и заполнена исключительно строками.
Старый библиотечный метод возвращает raw-тип List. Рассматривались три варианта.
List<String>. Это минимальное изменение, но предупреждение скрывает возможное загрязнение кучи, а ошибка проявится поздно.Для внешнего или ненадёжного источника выбирается третий вариант: стоимость проверки предсказуема, а ошибка локализуется на границе системы. Для внутреннего legacy-кода с документированным контрактом возможен второй вариант, но подавление предупреждения должно быть узким и сопровождаться комментарием с обоснованием.
@SuppressWarnings("unchecked") проблему безопасности?Нет. Аннотация только скрывает диагностическое сообщение компилятора. Она не добавляет проверку элементов, не меняет стирание типов и не предотвращает ClassCastException. Её следует применять на минимальном участке после проверки инварианта, а не на всём классе или методе.
List<String> может пройти, хотя список содержит не строки?После стирания типов параметр String не участвует в обычной runtime-проверке приведения. Среда выполнения может убедиться, что объект является List, но не проверить тип каждого его элемента. Реальная проверка часто выполняется позднее, когда результат get используется как String.
List<?> и тем самым убрать предупреждение?Во многих API — да, если операции должны быть ограничены чтением объектов неизвестного типа. List<?> сохраняет информацию о том, что список параметризован, и запрещает небезопасную запись произвольных значений. Однако это не заменяет List<String>: из List<?> нельзя напрямую получить значение как String без проверки или приведения. Если API должен гарантировать именно строки, нужен параметризованный контракт List<String> либо явная проверка данных на границе.