Что такое непроверенный бросок и как его проверить?


Я думаю, что понимаю, что означает непроверенное приведение (приведение от одного к другому другого типа), но что значит "проверить" приведение? Как я могу проверить гипс, чтобы избежать этого предупреждения в Eclipse?

3 28

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() не завершится:

Exception 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)
Может показаться, что разница невелика, но в случае с непроверенным броском a String успешно пробился в A List<Integer>. В реальных приложениях последствия этого может быть... ну, сурово. В случае с проверенным приведением несоответствие типов было обнаружено как можно раньше.

Чтобы избежать предупреждения о непроверенных приведениях, можно использовать @SuppressWarnings("unchecked"), если программист действительно уверен, что метод действительно безопасен. Лучшая альтернатива-использовать дженерики и проверенные слепки, когда это возможно.

Как выразился Джошуа блох,

...непроверенные предупреждения очень важны. Не игнорируйте их.


Для полноты картины, это ответ рассматривается специфика затмения.