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

611

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

Безопасность типов: непроверенное приведение из объекта в 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.
skiphoppy
источник
Если вы используете HttpSession подобным образом, ознакомьтесь со статьей Брайана Гетца на эту тему: ibm.com/developerworks/library/j-jtp09238.html
Том Хотин - tackline
Если неконтролируемый актерский состав неизбежен, хорошей идеей будет плотно связать его с чем-то, что логически представляет его тип (например, enumили даже экземпляры Class<T>), чтобы вы могли взглянуть на него и знать, что это безопасно.
Филипп Гуин
4
Связанный / обман: Тип безопасности: Не
проверено
3
возможный дубликат Тип безопасности:
непроверенный
Я хотел бы добавить, я обнаружил, что могу добавить только @SuppressWarnings («unchecked») на уровне метода, который содержит код, вызывающий нарушение. Так что я разбил код на рутину, где я должен был это сделать. Я всегда думал, что вы могли бы сделать это сразу над рассматриваемой строкой.
JGFMK

Ответы:

557

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

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

Пример:

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

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

Майкл Майерс
источник
112
+1 за указание на то, что он может идти по локальным переменным. Затмение предлагает только добавить его ко всему методу ...
thSoft
17
Eclipse 3.7 (Indigo) поддерживает добавление непроверенных к локальным переменным.
sweetfa
78
Предупреждение не только потому, что компилятор не знает, что приведение является безопасным. Например, не String s = (String) new Object() ;получает предупреждения, даже если компилятор не знает, что приведение безопасно. Предупреждение состоит в том, что компилятор (а) не знает, что приведение является безопасным И (b) не будет генерировать полную проверку во время выполнения в точке приведения. Будет проверка, что это Hashmap, но не будет проверки, что это HashMap<String,String>.
Теодор Норвелл
9
К сожалению, даже если приведение и предупреждение относятся к присваиванию , аннотация должна идти в объявлении переменной ... Так что, если объявление и присваивание находятся в разных местах (скажем, снаружи и внутри блока try соответственно) Теперь Eclipse генерирует два предупреждения: исходное непроверенное приведение и новая диагностика «ненужных аннотаций».
Ти Стрга
6
Обходной путь для аннотации, которая должна сопровождать объявление локальной переменной, которая может находиться в другой области действия, отличной от фактического преобразования, заключается в создании локальной переменной в области преобразования специально для выполнения преобразования в той же строке. как декларация. Затем присвойте эту переменную фактической переменной, которая находится в другой области видимости. Этот метод я также использовал для подавления предупреждения при приведении к переменной экземпляра, так как здесь также нельзя применить аннотацию.
Джефф Локхарт
169

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

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

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

Жюльен Частанг
источник
6
Гораздо лучше ответ, чем предоставленный skiphoppy, по нескольким причинам: 1) Этот код намного, намного короче. 2) Этот код фактически генерирует ClassCastException, как и ожидалось. 3) Этот код не делает полную копию исходной карты. 4) Циклы могут быть легко обернуты в отдельный метод, используемый в assert, который легко устранит снижение производительности в рабочем коде.
Стейн де Витт
6
Разве не существует вероятность того, что компилятор Java или JIT-компилятор решит, что результаты этого кода не используются, и «оптимизирует» его, не компилируя?
RenniePet
1
Это не совсем мертвый код, если он потенциально может вызвать исключение. Я не знаю достаточно о JIT-компиляторах, используемых сегодня, чтобы гарантировать, что ни один из них не испортит это, но я чувствую себя довольно уверенно, говоря, что они не должны этого делать.
GrandOpener
3
Это все еще не гарантирует безопасность типов, поскольку та же карта все еще используется. Первоначально он мог быть определен как Map <Object, Object>, в котором просто есть строки и числа, а затем, если будет добавлено логическое значение, пользователь этого кода получит запутанный и довольно сложный для отслеживания сюрприз. Единственный способ гарантировать безопасность типов - это скопировать их в новую карту с запрошенным типом, который гарантирует, что в него разрешено вводить.
user2219808
112

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

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

skiphoppy
источник
68
это сбивает с толку, но я думаю, что все, что вы сделали, это обмен ClassCastExceptions на AssertionErrors.
Дастин Гетц
59
Чувак, это определенно не стоит того! Представьте себе беднягу, который должен вернуться и изменить какой-то код с этим беспорядком. Мне не нравится подавлять предупреждения, но я думаю, что здесь меньшее зло.
Крейг Б
69
Дело не только в том, что это ужасный, запутанный беспорядок (когда вы не можете избежать одного обильного комментария, он может помочь программисту по обслуживанию); перебор каждого элемента в коллекции превращает приведение из операции O (1) в операцию O (n). Это то, чего никогда не ожидать и может легко превратиться в ужасное замедление тайны.
Дэн возится с огнем
22
@ Неужели ты прав? В общем, никто никогда не должен этим заниматься.
skiphoppy
4
Некоторые комментарии ... сигнатура метода неверна, потому что она не «чертовски», а просто копирует существующую карту в новую карту. Кроме того, он может быть реорганизован для принятия любой карты и не полагаться на сам HashMap (т. Е. Взять Map и вернуть Map в сигнатуре метода, даже если внутренний тип HashMap). Вам на самом деле не нужно выполнять приведение или сохранение в новую карту - если вы не выбросили ошибку подтверждения, то у данной карты есть нужные типы внутри нее, как сейчас. Создание новой карты с универсальными типами не имеет смысла, так как вы все еще можете сделать ее сырой и поместить что угодно.
MetroidFan2002
51

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

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

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

если не дух.

Дейв
источник
1
Ах, спасибо за это :) Я получаю uses unchecked or unsafe operations.ошибку " " javac, но добавление @SuppressWarnings("unchecked")делает Eclipse несчастным, утверждая, что подавление было ненужным. Снятие отметки с этого поля делает Eclipse и javacведет себя так же, чего я и хотел. Явное подавление предупреждения в коде намного яснее, чем подавление его повсюду в Eclipse.
dimo414
26

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

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

Эско Луонтола
источник
18
не отрицание, но обертка не добавляет ничего, кроме просто подавления предупреждения.
Дастин Гетц
3
+1, так как это решение не тратит драгоценные строки кода.
Тино
1
@ErikE Слишком много. Гораздо более дорогие мониторы большего размера и с более высоким разрешением, чтобы освободить место для всех потраченных линий, большой стол, на котором можно разместить все эти большие мониторы, большая комната для установки большего стола и проницательный начальник ..
Тино
1
@ErikE Полосы прокрутки, для vi? Вы шутите?
Тино
21

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

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

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

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

Дастин Гетц
источник
16

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

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

  2. Для приведения типов Коллекции используйте подстановочный знак? вместо универсального типа T для подтверждения того, что вы действительно не знаете, какие объекты ожидать от унаследованного кода ( Collection<?> unknownTypeCollection). В конце концов, это то, что хочет сказать нам предупреждение «непроверенный актерский состав»: мы не можем быть уверены, что мы получили Collection<T>, поэтому честное дело - использовать a 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);
StaticNoiseLog
источник
13

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

Йоахим Зауэр
источник
Это действительно полезный ответ.
GPrathap
10

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

//noinspection unchecked
Map<String, String> myMap = (Map<String, String>) deserializeMap();
Ян Моравец
источник
2
Это работает и в IntelliJ IDE
neXus
8

В этом конкретном случае я бы не сохранял 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);
    }
}
Эско Луонтола
источник
1
Потрясающие; Я думаю, что могу объединить это с моим ответом, чтобы параметризировать его и избежать необходимости вообще подавлять предупреждения!
skiphoppy
1
+1 вероятно лучший рецепт (легко понять и поддерживать), чтобы сделать это безопасно с проверками во время выполнения
Тино
8

Служебная функция 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

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

Гонен я
источник
5

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

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;
}
аббас
источник
1
Его пятилетний вопрос? Вам нужно проделать такую ​​большую работу? Учитывая, что Java имеет тип стирания, вторая хэш-карта должна быть идентична первой во время выполнения; Я думаю, что было бы более эффективно и избежать копирования, если бы вы просто перебрали записи и убедились, что все они являются экземплярами строк. Или, TBH, проверьте источник JAR сервлета, который вы используете, и убедитесь, что он только помещает строки.
Rup
1
По сей день я вижу это предупреждение в проектах. Его проблема заключалась не в проверке типа, а в предупреждении, вызванном «положением» в незадействованную карту.
Аббас
2

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

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

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

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

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

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

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

Марк Дэвидсон
источник
1
К сожалению, не все так просто. Код добавлен.
skiphoppy
1
Я пришел сюда в поисках ответа на немного другую проблему: и вы сказали мне именно то, что мне нужно! Спасибо!
staticsan
2

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

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

phihag
источник
2
Я почти уверен, что переключение на Map в этом случае ничего не изменит, но спасибо за совет по программированию, который может изменить способ, которым я делаю некоторые вещи, к лучшему. Источником объекта является API, который я не могу контролировать (код добавлен).
skiphoppy
2

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

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

Fortyrunner
источник
2

Возьмите этот, это намного быстрее, чем создание нового 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);
    }
jfreundo
источник
key.isAssignableFrom(e.getKey().getClass())можно записать какkey.isInstance(e.getKey())
user102008
1

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

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

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

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

стог
источник
8
К сожалению, дженерики делают это невозможным. Это не просто HashMap, это HashMap с информацией о типах. И если я устраню эту информацию, я просто отправлю предупреждения в другое место.
skiphoppy
1

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

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

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

Том Хотин - Tackline
источник
1
Цитата приписана покойному профессору Дэвиду Уилеру. en.wikipedia.org/wiki/…
Стивен С.
1

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

Джим Дэен
источник
0

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

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

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

Лукас Нормантас
источник
0

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

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


}
Майк Грызун
источник
тот факт, что у вас нет прав на комментарии, не позволяет вам редактировать ответы других, чтобы добавлять свои комментарии; Вы редактируете ответы других, чтобы улучшить их в форматировании, синтаксисе, ..., чтобы не добавлять свое мнение о них. Когда вы достигнете 50 повторений, вы сможете комментировать везде, в то же время я совершенно уверен, что вы можете сопротивляться (или, если вы действительно не можете, написать свои комментарии к существующим ответам в своем посте). (примечание для других: я пишу это, потому что я видел - и отклонил - его предложенные комментарии-изменения к другим сообщениям в инструментах модерации)
Matteo Italia
-1

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

 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;
}
lukewm
источник
1
Нет, это не так. На самом деле, это создает два предупреждения, где сначала было одно.
Стейн де Витт
Ах хорошо. Не уверен, почему я так думал.
Lukewm
-3

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

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

Марк Рим
источник
9
Могу я спросить, почему? глобальное отключение предупреждения скроет другие места, где эта проблема реальна. добавление @SuppressWarningsне делает код вообще нечитаемым.
MByD