7

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

39

Описание проблемы:

Я получаю предупреждение в Eclipse следующего вида:

Безопасность типов: Непроверенное приведение из Object в HashMap

Это связано с вызовом API, над которым у меня нет контроля, которое возвращает объект типа Object:

HashMap<String, String> getItems(javax.servlet.http.HttpSession session) {
    HashMap<String, String> theHash = (HashMap<String, String>) session.getAttribute("attributeKey");
    return theHash;
}

Я хотел бы избежать предупреждений Eclipse, если это возможно, так как они теоретически указывают как минимум на потенциальную проблему в коде. Пока не нашел хорошего способа избавиться от этого предупреждения. Я могу вынести строку, вызывающую предупреждение, в отдельный метод и добавить аннотацию @SuppressWarnings("unchecked") к этому методу, тем самым ограничив влияние блока кода, где я игнорирую предупреждения. Есть ли лучшие варианты? Я не хочу отключать эти предупреждения в Eclipse.

Ранее код выглядел проще, но все равно вызывал предупреждения:

HashMap getItems(javax.servlet.http.HttpSession session) {
    HashMap theHash = (HashMap) session.getAttribute("attributeKey");
    return theHash;
}

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

HashMap items = getItems(session);
items.put("this", "that");

// Безопасность типов: Метод put(Object, Object) принадлежит сырому типу HashMap. Обращения к обобщенному типу HashMap<K,V> должны быть параметризованы.

Вопрос:

Как лучше устранить предупреждение о безопасности типов в Eclipse при работе с возвращаемыми объектами типа Object из API, над которым у меня нет контроля?

5 ответ(ов)

1

Похоже, я сам пришел к ответу на свой вопрос, но не уверен, стоит ли это того! 😃

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

Тем не менее, вы можете произвести проверку для каждого элемента в хэш-карте с помощью instanceof, и таким образом создать новый хэш, который будет типобезопасным. И вы не вызовете никаких предупреждений.

Благодаря mmyers и Esko Luontola я параметризовал код, который изначально написал здесь, чтобы его можно было обернуть в утилитный класс и использовать для любой параметризованной HashMap. Если вы хотите лучше понять код и не очень знакомы с генериками, я рекомендую посмотреть историю правок этого ответа.

public static <K, V> HashMap<K, V> castHash(HashMap input,
                                            Class<K> keyClass,
                                            Class<V> valueClass) {
  HashMap<K, V> output = new HashMap<K, V>();
  if (input == null)
      return output;
  for (Object key: input.keySet().toArray()) {
    if ((key == null) || (keyClass.isAssignableFrom(key.getClass()))) {
        Object value = input.get(key);
        if ((value == null) || (valueClass.isAssignableFrom(value.getClass()))) {
            K k = keyClass.cast(key);
            V v = valueClass.cast(value);
            output.put(k, v);
        } else {
            throw new AssertionError(
                "Не удается привести к HashMap<"+ keyClass.getSimpleName()
                +", "+ valueClass.getSimpleName() +">"
                +", значение "+ value +" не является "+ valueClass.getSimpleName()
            );
        }
    } else {
        throw new AssertionError(
            "Не удается привести к HashMap<"+ keyClass.getSimpleName()
            +", "+ valueClass.getSimpleName() +">"
            +", ключ "+ key +" не является " + keyClass.getSimpleName()
        );
    }
  }
  return output;
}

Это требует значительных усилий, возможно, за очень небольшую награду... Я не уверен, буду ли я это использовать. Буду признателен за мнения: стоит ли это того? Также буду рад предложениям по улучшению: есть ли что-то лучше, чем выбрасывать AssertionError? Могу ли я выбрасывать что-то еще? Должен ли я сделать это проверяемым исключением?

0

В настройках Eclipse перейдите в раздел Java → Компилятор → Ошибки/Предупреждения → Общие типы и установите флажок Игнорировать непредотвратимые проблемы с обобщенными типами.

Это поможет решить поставленную задачу, а именно:

Я хотел бы избежать предупреждений Eclipse...

хоть и не совсем соответствует духу вопроса.

0

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

public class Objects {

    /**
     * Помогает избежать использования {@code @SuppressWarnings({"unchecked"})} при приведении к обобщенному типу.
     */
    @SuppressWarnings({"unchecked"})
    public static <T> T uncheckedCast(Object obj) {
        return (T) obj;
    }
}

Использовать его можно следующим образом:

import static Objects.uncheckedCast;
...

HashMap<String, String> getItems(javax.servlet.http.HttpSession session) {
    return uncheckedCast(session.getAttribute("attributeKey"));
}

Некоторую дополнительную информацию можно найти здесь: http://cleveralias.blogs.com/thought_spearmints/2006/01/suppresswarning.html

0

Эти вопросы действительно сложные, но вот мои текущие мысли:

Если ваш API возвращает объект типа Object, то вы ничего не сможете с этим сделать — в любом случае вам придётся выполнять неявное приведение типа. Вы можете позволить Java выбрасывать исключения ClassCastException или проверить каждый элемент самостоятельно и выбрасывать AssertionError или IllegalArgumentException, но все эти временные проверки эквивалентны. Вам нужно подавить временные неявные приведения типов, что бы вы ни делали во время выполнения.

Я бы предпочёл просто выполнить неявное приведение и позволить JVM выполнять проверку во время выполнения за меня, поскольку мы "знаем", что должен возвращать API и обычно готовы предполагать, что он работает. Используйте обобщения (generics) везде выше приведения, если они вам нужны. Вы на самом деле ничего не выигрываете в этом случае, так как всё равно у вас останется одно неявное приведение, но, по крайней мере, вы сможете использовать обобщения на более высоком уровне, так что JVM сможет помочь вам избежать неявных приведений в других частях вашего кода.

В данном конкретном случае, предположительно, вы можете видеть вызов метода SetAttribute и знаете, какого типа объект передаётся, поэтому просто выполнить неявное приведение к тому же типу при выходе не является чем-то аморальным. Добавьте комментарий, ссылающийся на SetAttribute, и забудьте об этом.

0

Вот укороченный пример, который избегает предупреждения "unchecked cast" с помощью двух стратегий, упомянутых в других ответах.

  1. Передавайте класс интересующего типа в качестве параметра во время выполнения (Class<T> inputElementClazz). Затем вы можете использовать: inputElementClazz.cast(anyObject);
  2. Для приведения типов коллекции используйте подстановочный знак ?, вместо обобщенного типа T, чтобы подтвердить, что вы действительно не знаете, какие объекты ожидать от устаревшего кода (Collection<?> unknownTypeCollection). В конце концов, именно это предупреждение "unchecked cast" пытается нам сообщить: мы не можем быть уверены, что получаем Collection<T>, поэтому правильный подход — использовать Collection<?>. Если это абсолютно необходимо, коллекцию известного типа всё равно можно создать (Collection<T> knownTypeCollection).

Устаревший код, взаимодействующий в приведенном ниже примере, имеет атрибут "input" в StructuredViewer (StructuredViewer — это виджет дерева или таблицы, "input" — это модель данных, стоящая за ним). Этот "input" может быть любой Java коллекцией.

public void dragFinished(StructuredViewer structuredViewer, Class<T> inputElementClazz) {
    IStructuredSelection selection = (IStructuredSelection) structuredViewer.getSelection();
    // устаревший код возвращает объект из getFirstElement,
    // разработчик знает/надеется, что это тип inputElementClazz, но компилятор не может знать
    T firstElement = inputElementClazz.cast(selection.getFirstElement());

    // устаревший код возвращает объект из getInput, поэтому мы обрабатываем его как Collection<?>
    Collection<?> unknownTypeCollection = (Collection<?>) structuredViewer.getInput();

    // для некоторых операций нам даже не нужна коллекция с известными типами
    unknownTypeCollection.remove(firstElement);

    // ничто не мешает нам создать коллекцию известного типа, если это действительно необходимо
    Collection<T> knownTypeCollection = new ArrayList<T>();
    for (Object object : unknownTypeCollection) {
        T aT = inputElementClazz.cast(object);
        knownTypeCollection.add(aT);
        System.out.println(aT.getClass());
    }

    structuredViewer.refresh();
}

Естественно, приведенный выше код может вызывать ошибки выполнения, если мы используем устаревший код с неверными типами данных (например, если мы установим массив в качестве "input" для StructuredViewer, вместо Java коллекции).

Пример вызова метода:

dragFinishedStrategy.dragFinished(viewer, Product.class);
Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь