Как я обращаюсь к непроверенным предупреждениям броска?

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

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

Решение

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

Проблема в том, что актерский состав не проверен. Итак, вы должны проверить это сами. Вы не можете просто проверить параметризованный тип с помощью 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? Есть ли что-то лучшее, что я мог бы бросить? Должен ли я сделать это проверенным исключением?

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

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

Пример:

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

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

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

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

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

В настройках Eclipse перейдите в раздел Java->Compiler->Errors/Warnings->Generic types и проверьте Ignore unavoidable generic type problems Флажок.

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

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

если не дух.

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

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

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

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

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

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

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

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

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

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

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);

В мире 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 позже.

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

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

Утилита Objects.Unchecked в ответе Esko Luontola выше - отличный способ избежать беспорядка в программе.

Если вы не хотите, чтобы SuppressWarnings применялся ко всему методу, Java заставит вас поместить его в локальный метод. Если вам нужен приведение к члену, это может привести к такому коду:

@SuppressWarnings("unchecked")
Vector<String> watchedSymbolsClone = (Vector<String>) watchedSymbols.clone();
this.watchedSymbols = watchedSymbolsClone;

Использование утилиты намного чище, и все еще очевидно, что вы делаете:

this.watchedSymbols = Objects.uncheckedCast(watchedSymbols.clone());

ПРИМЕЧАНИЕ: я считаю важным добавить, что иногда предупреждение действительно означает, что вы делаете что-то не так, например:

ArrayList<Integer> intList = new ArrayList<Integer>();
intList.add(1);
Object intListObject = intList; 

 // this line gives an unchecked warning - but no runtime error
ArrayList<String> stringList  = (ArrayList<String>) intListObject;
System.out.println(stringList.get(0)); // cast exception will be given here

Компилятор говорит вам, что это приведение НЕ будет проверяться во время выполнения, поэтому не возникнет ошибка времени выполнения, пока вы не попытаетесь получить доступ к данным в универсальном контейнере.

В этом конкретном случае я бы не сохранял 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);
    }
}

Подавление предупреждений не является решением. Вы не должны делать двухуровневое приведение в одном утверждении.

HashMap<String, String> getItems(javax.servlet.http.HttpSession session) {

    // first, cast the returned Object to generic HashMap<?,?>
    HashMap<?, ?> theHash = (HashMap<?, ?>)session.getAttribute("attributeKey");

    // next, cast every entry of the HashMap to the required type <String, String>
    HashMap<String, String> returingHash = new HashMap<>();
    for (Entry<?, ?> entry : theHash.entrySet()) {
        returingHash.put((String) entry.getKey(), (String) entry.getValue());
    }
    return returingHash;
}

Если мне нужно использовать API, который не поддерживает Generics... Я пытаюсь изолировать эти вызовы в процедурах-оболочках с как можно меньшим количеством строк. Затем я использую аннотацию SuppressWarnings, а также одновременно добавляю приведение типов безопасности.

Это просто личное предпочтение держать вещи как можно более аккуратными.

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

HashMap<String, Object> test = new HashMap();

который выдаст предупреждение, когда вам нужно сделать

HashMap<String, Object> test = new HashMap<String, Object>();

возможно, стоит посмотреть на

Обобщения в языке программирования Java

если вы не знакомы с тем, что нужно сделать.

Возможно, я неправильно понял вопрос (пример и пара окружающих строк были бы хорошими), но почему вы не всегда используете соответствующий интерфейс (и Java5+)? Я не вижу причин, почему вы хотели бы бросить на HashMap вместо Map<KeyType,ValueType>, На самом деле, я не могу себе представить причину, чтобы установить тип переменной HashMap вместо Map,

И почему источник Object? Это тип параметра устаревшей коллекции? Если это так, используйте дженерики и укажите нужный тип.

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

@SuppressWarnings("unchecked")
public static <K, V> HashMap<K, V> toHashMap(Object input, Class<K> key, Class<V> value) {
       assert input instanceof Map : input;

       for (Map.Entry<?, ?> e : ((HashMap<?, ?>) input).entrySet()) {
           assert key.isAssignableFrom(e.getKey().getClass()) : "Map contains invalid keys";
           assert value.isAssignableFrom(e.getValue().getClass()) : "Map contains invalid values";
       }

       if (input instanceof HashMap)
           return (HashMap<K, V>) input;
       return new HashMap<K, V>((Map<K, V>) input);
    }

Вот один из способов справиться с этим, когда я переопределяю equals() операция.

public abstract class Section<T extends Section> extends Element<Section<T>> {
    Object attr1;

    /**
    * Compare one section object to another.
    *
    * @param obj the object being compared with this section object
    * @return true if this section and the other section are of the same
    * sub-class of section and their component fields are the same, false
    * otherwise
    */       
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            // this exists, but obj doesn't, so they can't be equal!
            return false;
        }

        // prepare to cast...
        Section<?> other;

        if (getClass() != obj.getClass()) {
            // looks like we're comparing apples to oranges
            return false;
        } else {
            // it must be safe to make that cast!
            other = (Section<?>) obj;
        }

        // and then I compare attributes between this and other
        return this.attr1.equals(other.attr1);
    }
}

Кажется, это работает в Java 8 (даже скомпилировано с -Xlint:unchecked)

Почти каждая проблема в области компьютерных наук может быть решена путем добавления уровня косвенности * или чего-то еще.

Итак, представьте неуниверсальный объект более высокого уровня, чем Map, Без контекста это не будет выглядеть очень убедительно, но в любом случае:

public final class Items implements java.io.Serializable {
    private static final long serialVersionUID = 1L;
    private Map<String,String> map;
    public Items(Map<String,String> map) {
        this.map = New.immutableMap(map);
    }
    public Map<String,String> getMap() {
        return map;
    }
    @Override public String toString() {
        return map.toString();
    }
}

public final class New {
    public static <K,V> Map<K,V> immutableMap(
        Map<? extends K, ? extends V> original
    ) {
        // ... optimise as you wish...
        return Collections.unmodifiableMap(
            new HashMap<String,String>(original)
        );
    }
}

static Map<String, String> getItems(HttpSession session) {
    Items items = (Items)
        session.getAttribute("attributeKey");
    return items.getMap();
}

* За исключением слишком большого количества уровней косвенности.

Просто проверь его, прежде чем разыграть.

Object someObject = session.getAttribute("attributeKey");
if(someObject instanceof HashMap)
HashMap<String, String> theHash = (HashMap<String, String>)someObject;  

И для всех, кто спрашивает, довольно часто можно получать объекты, в которых вы не уверены, какого типа. Множество устаревших реализаций "SOA" передают различные объекты, которым вы не всегда должны доверять. (Ужасы!)

РЕДАКТИРОВАТЬ Один раз изменил код примера, чтобы он соответствовал обновлениям автора, и после некоторых комментариев я вижу, что instanceof не очень хорошо работает с генериками. Однако изменение проверки для проверки внешнего объекта, похоже, хорошо работает с компилятором командной строки. Пересмотренный пример теперь опубликован.

Если вы уверены, что типом, возвращаемым session.getAttribute(), является HashMap, то вы не можете типизировать этот точный тип, а полагаться только на проверку универсального HashMap.

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

Затем Eclipse будет удивлять предупреждениями, но, конечно, это может привести к ошибкам во время выполнения, которые трудно отладить. Я использую этот подход только в некритическом контексте.

Проблема заключается в следующем:

... = (HashMap<String, String>)session.getAttribute("attributeKey");

Результат session.getAttribute(...) является object что может быть что угодно, но так как вы "знаете", это HashMap<String, String> вы просто кастуете, не проверяя это в первую очередь. Итак, предупреждение. Чтобы быть педантичным, каким Java хочет, чтобы вы были в этом случае, вы должны получить результат и проверить его совместимость с instanceof,

Два способа, один из которых полностью избегает тег, другой использует непослушный, но приятный служебный метод.
Проблема в предварительно обобщенных коллекциях...
Я полагаю, что эмпирическое правило гласит: "бросать объекты по одной вещи за раз" - что это означает, когда вы пытаетесь использовать необработанные классы в обобщенном мире, потому что вы не знаете, что находится на этой карте (и действительно, JVM может даже обнаружить, что это даже не Карта!), когда вы думаете об этом, вы не можете разыграть ее. Если у вас была Map Map2, то HashSet keys = (HashSet) map2.keySet () не выдает предупреждение, несмотря на то, что это "акт веры" для компилятора (потому что это может оказаться TreeSet)... но это всего лишь один акт веры.

PS на возражение, что повторение, как в моем первом случае, "скучно" и "требует времени", ответ "нет боли, нет выгоды": обобщенная коллекция гарантированно будет содержать Map.Entrys и ничего остальное. Вы должны заплатить за эту гарантию. При систематическом использовании непатентованных средств этот платеж, красиво, принимает форму соответствия кодирования, а не машинного времени!
Одна школа мысли может сказать, что вы должны установить настройки Eclipse, чтобы такие ошибки не проверялись, а не предупреждения. В этом случае вам придется использовать мой первый способ.

package scratchpad;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;

public class YellowMouse {

    // First way

    Map<String, String> getHashMapStudiouslyAvoidingSuppressTag(HttpSession session) {
      Map<?, ?> theHash = (Map<?, ?>)session.getAttribute("attributeKey");

      Map<String, String> yellowMouse = new HashMap<String, String>();
      for( Map.Entry<?, ?> entry : theHash.entrySet() ){
        yellowMouse.put( (String)entry.getKey(), (String)entry.getValue() );
      }

      return yellowMouse;
    }


    // Second way

    Map<String, String> getHashMapUsingNaughtyButNiceUtilityMethod(HttpSession session) {
      return uncheckedCast( session.getAttribute("attributeKey") );
    }


    // NB this is a utility method which should be kept in your utility library. If you do that it will
    // be the *only* time in your entire life that you will have to use this particular tag!!

    @SuppressWarnings({ "unchecked" })
    public static synchronized <T> T uncheckedCast(Object obj) {
        return (T) obj;
    }


}

Это заставляет предупреждения уходить...

 static Map<String, String> getItems(HttpSession session) {
        HashMap<?, ?> theHash1 = (HashMap<String,String>)session.getAttribute("attributeKey");
        HashMap<String,String> theHash = (HashMap<String,String>)theHash1;
    return theHash;
}

Решение: отключите это предупреждение в Eclipse. Не @SuppressWarnings, просто отключите его полностью.

Некоторые из "решений", представленных выше, являются выходом из строя, делая код нечитаемым для подавления глупого предупреждения.

Другие вопросы по тегам