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

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

Тип безопасности: снятый снимок с объекта на 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.

Ответ 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(
                "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;
}

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

Ответ 2

Очевидным ответом, конечно же, является не выполнение неконтролируемого актера.

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

Пример:

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

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

Ответ 3

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

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

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

Ответ 4

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

Это соответствует задаче вопроса, т.е.

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

если не дух.

Ответ 5

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

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

Ответ 6

Это сложно, но вот мои текущие мысли:

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

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

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

Ответ 7

В мире сеансов HTTP вы действительно не можете избежать приведения, так как 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 позже.

Ответ 8

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

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

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

Унаследованный код, сопряженный в приведенном ниже примере, имеет атрибут "вход" в StructuredViewer (StructuredViewer - это виджет дерева или таблицы, "вход" - это модель данных позади него). Этот "enter" может представлять собой любую коллекцию 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);

Ответ 9

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

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

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

Ответ 10

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

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

Ответ 11

Функция Objects.Unchecked utility в ответе выше 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

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

Ответ 12

Предупреждение - это не решение. Вы не должны делать двухуровневое кастинг в одном утверждении.

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

Ответ 13

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

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

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

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

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

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

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

Ответ 14

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

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

Ответ 15

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

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

Ответ 16

Возьмите это, это намного быстрее, чем создание нового 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);
    }

Ответ 17

Просто введите текст, прежде чем вы его произнесите.

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

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

EDIT Изменен пример кода один раз, чтобы соответствовать обновлениям плакатов, и после некоторых комментариев я вижу, что instanceof не играет хорошо с генериками. Однако изменение проверки для проверки внешнего объекта, похоже, хорошо сочетается с компилятором командной строки. Пересмотренный пример теперь опубликован.

Ответ 18

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

Итак, введите не общий объект, который имеет более высокий уровень, чем 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();
}

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

Ответ 19

Здесь один из способов справиться с этим, когда я переопределяю операцию 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)

Ответ 20

Проблема здесь:

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

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

Ответ 21

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

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

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

Ответ 22

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

PS к возражению, что итерация, как на моем первом пути, "скучна" и "требует времени", ответ "без боли не получает": в обобщенной коллекции будет храниться Map.Entry < String, String > s и ничего остальное. Вы должны заплатить за эту гарантию. При систематическом использовании дженериков этот платеж, красиво, принимает форму соответствия кодированию, а не машинного времени!
Одна школа мысли может сказать, что вы должны установить настройки 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;
    }


}

Ответ 23

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

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

Ответ 24

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

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