Как я обращаюсь к непроверенным предупреждениям броска? - PullRequest
551 голосов
/ 04 февраля 2009

Затмение дает мне предупреждение следующего вида:

Тип безопасности: непроверенное приведение из объекта в 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");

Type safety: The method put(Object, Object) belongs to the raw type HashMap.  References to generic type HashMap<K,V> should be parameterized.

Ответы [ 24 ]

509 голосов
/ 04 февраля 2009

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

Если это абсолютно необходимо, то хотя бы попытайтесь ограничить область действия аннотации @SuppressWarnings. Согласно его Javadocs , он может идти по локальным переменным; таким образом, это даже не влияет на весь метод.

Пример:

@SuppressWarnings("unchecked")
Map<String, String> myMap = (Map<String, String>) deserializeMap();

Невозможно определить, действительно ли Map должен иметь общие параметры <String, String>. Вы должны заранее знать, какими должны быть параметры (или вы узнаете, когда получите ClassCastException). Вот почему код генерирует предупреждение, потому что компилятор не может знать, безопасен ли он.

140 голосов
/ 04 февраля 2009

К сожалению, здесь нет хороших вариантов. Помните, что цель всего этого - сохранить безопасность типов. « Java Generics » предлагает решения для работы с неуниверсализированными унаследованными библиотеками, и одна из них, в частности, называется «метод пустого цикла» в разделе 8.2. В основном, сделайте небезопасный бросок и подавите предупреждение. Затем переберите карту так:

@SuppressWarnings("unchecked")
Map<String, Number> map = getMap();
for (String s : map.keySet());
for (Number n : map.values());

Если обнаружен непредвиденный тип, вы получите исключение ClassCastException во время выполнения, но, по крайней мере, это произойдет близко к источнику проблемы.

110 голосов
/ 04 февраля 2009

Ничего себе; Думаю, я разобрался с ответом на свой вопрос. Я просто не уверен, что оно того стоит! :)

Проблема в том, что актерский состав не проверен. Итак, вы должны проверить это сами. Невозможно просто проверить параметризованный тип с помощью 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(
                "Cannot cast to HashMap<"+ keyClass.getSimpleName()
                +", "+ valueClass.getSimpleName() +">"
                +", value "+ value +" is not a "+ valueClass.getSimpleName()
            );
        }
    } else {
        throw new AssertionError(
            "Cannot cast to HashMap<"+ keyClass.getSimpleName()
            +", "+ valueClass.getSimpleName() +">"
            +", key "+ key +" is not a " + keyClass.getSimpleName()
        );
    }
  }
  return output;
}

Это много работы, возможно, за очень маленькую награду ... Я не уверен, буду ли я ее использовать или нет. Буду признателен за любые комментарии относительно того, думают ли люди, что это того стоит или нет. Кроме того, я был бы признателен за предложения по улучшению: есть ли что-то лучшее, что я могу сделать, кроме как бросить AssertionErrors? Есть ли что-то лучшее, что я мог бы бросить? Должен ли я сделать это проверенным исключением?

49 голосов
/ 21 октября 2011

В Eclipse Preferences, перейдите в Java-> Компилятор-> Ошибки / Предупреждения-> Общие типы и установите флажок Ignore unavoidable generic type problems

Это удовлетворяет цели вопроса, т.е.

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

если не дух.

26 голосов
/ 04 февраля 2009

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

public class Objects {

    /**
     * Helps to avoid using {@code @SuppressWarnings({"unchecked"})} when casting to a generic type.
     */
    @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

20 голосов
/ 02 марта 2011

Это сложно, но вот мои нынешние мысли:

Если ваш API возвращает Object, то вы ничего не можете сделать - несмотря ни на что, вы будете слепо использовать объект. Вы можете разрешить Java бросать ClassCastExceptions, или вы можете проверить каждый элемент самостоятельно и выбросить Assertions или IllegalArgumentExceptions или некоторые другие, но все эти проверки runtime эквивалентны. Вы должны отключить время компиляции без проверки, независимо от того, что вы делаете во время выполнения.

Я бы просто предпочел слепую проверку и позволить JVM выполнить для меня проверку во время выполнения, поскольку мы «знаем», что должен возвращать API, и обычно готовы предположить, что API работает. Используйте дженерики везде над актерами, если они вам нужны. Вы на самом деле ничего не покупаете там, так как у вас все еще есть один слепой бросок, но по крайней мере вы можете использовать дженерики оттуда вверх, чтобы JVM могла помочь вам избежать слепых бросков в других частях вашего кода.

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

12 голосов
/ 04 февраля 2009

В мире HTTP Session вы не можете избежать преобразования, так как API написан таким образом (принимает и возвращает только Object).

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

Замените HashMap выделенным классом:

import java.util.AbstractMap;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class Attributes extends AbstractMap<String, String> {
    final Map<String, String> content = new HashMap<String, String>();

    @Override
    public Set<Map.Entry<String, String>> entrySet() {
        return content.entrySet();
    }

    @Override
    public Set<String> keySet() {
        return content.keySet();
    }

    @Override
    public Collection<String> values() {
        return content.values();
    }

    @Override
    public String put(final String key, final String value) {
        return content.put(key, value);
    }
}

Затем приведите к этому классу вместо Map<String,String>, и все будет проверено в том месте, где вы пишете свой код. Никаких неожиданностей ClassCastExceptions позже.

11 голосов
/ 13 августа 2015

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

  1. Передайте класс интересующего типа в качестве параметра во время выполнения (Class<T> inputElementClazz). Тогда вы можете использовать: inputElementClazz.cast(anyObject);

  2. Для приведения типов в Коллекцию используйте подстановочный знак? вместо универсального типа T для подтверждения того, что вы действительно не знаете, какие объекты ожидать от устаревшего кода (Collection<?> unknownTypeCollection). В конце концов, это то, что хочет сказать нам предупреждение «unchecked cast»: мы не можем быть уверены, что получим Collection<T>, поэтому честно то, что нужно использовать Collection<?>. Если это абсолютно необходимо, можно собрать коллекцию известного типа (Collection<T> knownTypeCollection).

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

public void dragFinished(StructuredViewer structuredViewer, Class<T> inputElementClazz) {
    IStructuredSelection selection = (IStructuredSelection) structuredViewer.getSelection();
    // legacy code returns an Object from getFirstElement,
    // the developer knows/hopes it is of type inputElementClazz, but the compiler cannot know
    T firstElement = inputElementClazz.cast(selection.getFirstElement());

    // legacy code returns an object from getInput, so we deal with it as a Collection<?>
    Collection<?> unknownTypeCollection = (Collection<?>) structuredViewer.getInput();

    // for some operations we do not even need a collection with known types
    unknownTypeCollection.remove(firstElement);

    // nothing prevents us from building a Collection of a known type, should we really need one
    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();
}

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

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

dragFinishedStrategy.dragFinished(viewer, Product.class);
8 голосов
/ 04 февраля 2009

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

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

public static void main(String[] args) {
    Map<String, Integer> map = new HashMap<String, Integer>();
    map.put("a", 1);
    map.put("b", 2);
    Object obj = map;

    Map<String, Integer> ok = safeCastMap(obj, String.class, Integer.class);
    Map<String, String> error = safeCastMap(obj, String.class, String.class);
}

@SuppressWarnings({"unchecked"})
public static <K, V> Map<K, V> safeCastMap(Object map, Class<K> keyType, Class<V> valueType) {
    checkMap(map);
    checkMapContents(keyType, valueType, (Map<?, ?>) map);
    return (Map<K, V>) map;
}

private static void checkMap(Object map) {
    checkType(Map.class, map);
}

private static <K, V> void checkMapContents(Class<K> keyType, Class<V> valueType, Map<?, ?> map) {
    for (Map.Entry<?, ?> entry : map.entrySet()) {
        checkType(keyType, entry.getKey());
        checkType(valueType, entry.getValue());
    }
}

private static <K> void checkType(Class<K> expectedType, Object obj) {
    if (!expectedType.isInstance(obj)) {
        throw new IllegalArgumentException("Expected " + expectedType + " but was " + obj.getClass() + ": " + obj);
    }
}
8 голосов
/ 01 сентября 2017

В Android Studio, если вы хотите отключить проверку, вы можете использовать:

//noinspection unchecked
Map<String, String> myMap = (Map<String, String>) deserializeMap();
...