Что такое непроверенный бросок и как его проверить?
Я думаю, что понимаю, что означает непроверенное приведение (приведение от одного к другому другого типа), но что значит "проверить" приведение? Как я могу проверить гипс, чтобы избежать этого предупреждения в Eclipse?
3 ответа:
Непроверенное приведение означает, что выполняется (неявно или явно) приведение от универсального типа к неквалифицированному типу или наоборот. Например, эта строка
Set<String> set = new HashSet();
Произведет такое предупреждение.
Обычно для таких предупреждений есть веская причина, поэтому вы должны попытаться улучшить свой код, а не подавлять предупреждение. Цитата из эффективного Java, 2-е издание:
Конечно, не всегда так легко устранить предупреждения, как в приведенном выше коде. Без однако, увидев ваш код, вы не сможете сказать, как сделать его безопасным.Устраните все непроверенные предупреждения, которые вы можете. Если вы устраните все предупреждения, вы уверены, что ваш код является типобезопасным, что является очень хорошая вещь. Это означает, что вы не получите
ClassCastException
во время выполнения, и это повышает вашу уверенность в том, что ваша программа ведет себя так, как вы задумали.Если вы не можете устранить предупреждение, и вы можете доказать, что код, который спровоцированное предупреждение является типобезопасным, затем (и только после этого) подавите предупреждение с аннотацией
@SuppressWarnings("unchecked")
. Если вы подавляете предупреждения не доказав предварительно, что код безопасен для печати, вы только даете себе один ложное чувство безопасности. Код может компилироваться без каких-либо предупреждений, но он все еще может броситьClassCastException
во время выполнения. Если, однако, вы игнорируете непроверенные предупреждения, которые, как вы знаете, безопасны (вместо того, чтобы подавлять их), вы не заметит, когда появится новое предупреждение, представляющее реальную проблему. То новое предупреждение затеряется среди всех ложных тревог, которые вы не замолчали.
Чтобы более подробно остановиться на том, что написал Петр:
Приведения от не-универсальных типов к универсальным типам могут работать просто отлично во время выполнения, потому что универсальные параметры стираются во время компиляции, поэтому мы остаемся с законным приведением. Однако позже код может завершиться ошибкой с неожиданным ClassCastException из-за неправильного предположения относительно параметра type. Например:List l1 = new ArrayList(); l1.add(33); ArrayList<String> l2 = (ArrayList<String>) l1; String s = l2.get(0);
Непроверенное предупреждение в строке 3 указывает на то, что компилятор не может гарантировать безопасность типов больше, в том смысле, что неожиданное ClassCastException может произойти где-то позже. И это происходит в строке 4, которая выполняет неявное приведение.
Непроверенное приведение, в отличие от проверенного приведения, не проверяет безопасность типов во время выполнения.
Вот пример, основанный на разделеConsider typesafe heterogenous containers
3-го изд. "эффективной Java" Джошуа Блоха, но класс контейнера намеренно нарушен - он хранит и возвращает неправильный тип:public class Test { private static class BrokenGenericContainer{ private final Map<Class<?>, Object> map= new HashMap<>(); public <T> void store(Class<T> key, T value){ map.put(key, "broken!"); // should've been [value] here instead of "broken!" } public <T> T retrieve(Class<T> key){ // return key.cast(map.get(key)); // a checked cast return (T)map.get(key); // an unchecked cast } } public static void main(String[] args) { BrokenGenericContainer c= new BrokenGenericContainer(); c.store(Integer.class, 42); List<Integer> ints = new ArrayList<>(); ints.add(c.retrieve(Integer.class)); Integer i = ints.get(0); } }
Еслиretrieve()
использует непроверенное приведение -(T)map.get(key)
- запуск этой программы приведет к появлениюClassCastException
в строкеInteger i = ints.get(0)
. Методretrieve()
будет завершен, поскольку фактический тип не проверялось во время выполнения:Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer at Test.main(Test.java:27)
Но еслиretrieve()
использует проверенное приведение -key.cast(map.get(key))
- запуск этой программы приведет кClassCastException
, происходящему в строкеkey.cast(map.get(key))
, потому что проверенное приведение обнаружит, что тип неверен, и выдаст исключение. Методretrieve()
не завершится:Может показаться, что разница невелика, но в случае с непроверенным броском aException in thread "main" java.lang.ClassCastException: Cannot cast java.lang.String to java.lang.Integer at java.lang.Class.cast(Class.java:3369) at Test$BrokenGenericContainer.retrieve(Test.java:16) at Test.main(Test.java:26)
String
успешно пробился в AList<Integer>
. В реальных приложениях последствия этого может быть... ну, сурово. В случае с проверенным приведением несоответствие типов было обнаружено как можно раньше.
Чтобы избежать предупреждения о непроверенных приведениях, можно использовать
@SuppressWarnings("unchecked")
, если программист действительно уверен, что метод действительно безопасен. Лучшая альтернатива-использовать дженерики и проверенные слепки, когда это возможно.Как выразился Джошуа блох,
...непроверенные предупреждения очень важны. Не игнорируйте их.
Для полноты картины, это ответ рассматривается специфика затмения.