Как использовать цикл foreach в Java для перебора значений в HashMap?

Я пытаюсь скомпилировать следующий код:

private String dataToString(){
    Map data = (HashMap<MyClass.Key, String>) getData();
    String toString = "";
    for( MyClass.Key key: data.keySet() ){
        toString += key.toString() + ": " + data.get( key );
    return toString;
}

Я получаю сообщение об ошибке в строке for:

incompatible types
found : java.lang.Object
required: MyClass.Key

Метод getData() возвращает Object (но в этом случае возвращенный Object имеет структуру HashMap). MyClass.Key - это перечисление, которое я создал для целей моего приложения (в другом файле класса - MyClass).

Когда я создал цикл foreach с такой же структурой в MyClass.java, я не столкнулся с этой проблемой.

Что я делаю неправильно?

Нет необходимости приводить getData () к HashMap, когда вы просто собираетесь назначить его Map. Скорее бросьте карту. Что, если getData () возвращает не-HashMap (например, TreeMap)?

Steve Kuo 15.01.2009 22:57

Я на самом деле упустил некоторую информацию здесь ... getData () на самом деле getData (String key), где key указывает желаемый объект, который я хочу получить. Итак, поскольку я знаю объект, который получаю, я точно знаю, к чему его следует применить.

troyal 15.01.2009 23:00
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
37
2
99 496
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Изменять:

Map data = (HashMap<MyClass.Key, String>) getData();

к

Map<MyClass.Key, String> data = (HashMap<MyClass.Key, String>) getData();

Проблема в том, что data.keySet() возвращает Collection<Object>, если данные просто Map. Как только вы сделаете его универсальным, keySet() вернет Collection<MyClass.Key>. Еще лучше ... перебрать entrySet(), который будет Collection<MyClass.Key, String>. Это позволяет избежать лишних поисков хеша.

Спасибо! Вы можете объяснить, почему это исправляет?

troyal 15.01.2009 22:34

Поскольку способ, которым вы объявили это, Map data объявляет данные как Map неизвестного типа, поэтому keySet () возвращает Object. Внесение изменения сообщает компилятору, что это ключи MyClass.Key, а не Object.

Paul Tomblin 15.01.2009 22:39

Когда вы назначаете getData только данным Map, вы фактически назначаете их Map <Object, Object>, явно определяя их как данные Map <MyClass.Key, String>, вы позволяете циклу foreach сохранять информацию о том, какой тип ключа.

Ryan Ahearn 15.01.2009 22:39

@cletus, правда, но это не было проблемой в этом вопросе.

Peter Štibraný 16.01.2009 00:01

Не думаю, что Клетус прочитал мой ответ. Очевидно, вы можете / должны использовать оба.

Craig P. Motlin 16.01.2009 02:36

Ответ Мотлина правильный.

У меня две записки ...

  1. Не используйте toString += ..., а используйте StringBuilder и добавляйте к нему данные.

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

Другой способ без предупреждения (и с помощью StringBuilder):

private String dataToString(){
    Map<?, ?> data = (Map<?, ?>) getData();
    StringBuilder toString = new StringBuilder();
    for (Object key: data.keySet()) {
        toString.append(key.toString());
        toString.append(": ");
        toString.append(data.get(key));
    }
    return toString.toString();
}

Это работает, потому что метод toString, который вы вызываете на key, определен в классе Object, поэтому вам вообще не нужно приведение типов.

Использование entrySet - еще лучший способ, так как не нужно делать еще один поиск на карте.

Вы можете избавиться от предупреждения с помощью @SuppressWarning ("не отмечено")

Ryan Ahearn 15.01.2009 22:42

Это просто скрытие проблем, а не их устранение. @SuppressWarning ("unchecked") следует использовать с БОЛЬШОЙ осторожностью.

Peter Štibraný 15.01.2009 22:43

Мне интересно, лучше ли хранить data.keSet () в локальной переменной или оптимизирован цикл foreach?

Alfred 04.03.2010 04:48

@Alfred: вам не нужно хранить data.keySet () в вашей собственной переменной. for (Ключ объекта: data.keySet ()) функционально эквивалентен: Iterator <Object> i = data.keySet (). iterator (); while (i.hasNext ()) {Ключ объекта = i.next (); ... здесь идет остаток цикла for ...} То есть data.keySet () вычисляется только один раз.

Peter Štibraný 04.03.2010 11:10

Вместо этого вы можете захватить entrySet, чтобы не использовать ключевой класс:

private String dataToString(){    
    Map data = (HashMap<MyClass.Key, String>) getData();    
    String toString = "";    
    for( Map.Entry entry: data.entrySet() ) {        
        toString += entry.getKey() + ": " + entry.getValue();
    }    
    return toString;
}

Итерации по entrySet более эффективны, чем по keySet, хотя мне также нужно вывести имена ключей?

troyal 15.01.2009 22:55

EntrySet более эффективен, потому что вам не нужно выполнять поиск по каждому ключу.

Michael Myers 15.01.2009 22:58

@Blue - намного эффективнее, ПОТОМУ ЧТО вы используете и ключ, и значение.

Paul Tomblin 15.01.2009 23:19
Ответ принят как подходящий

Чуть более эффективный способ сделать это:

  Map<MyClass.Key, String> data = (HashMap<MyClass.Key, String>) getData(); 
  StringBuffer sb = new StringBuffer();
  for (Map.Entry<MyClass.Key,String> entry : data.entrySet()) {
       sb.append(entry.getKey());
       sb.append(": ");
       sb.append(entry.getValue());
   }
   return sb.toString();

Если возможно, определите "getData", чтобы вам не понадобилось приведение типов.

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

Esko 15.01.2009 22:50

Я бы сделал это, но getData () используется во всем этом проекте, а также в других, поэтому лучше оставить его как есть (возврат объекта).

troyal 15.01.2009 22:53

+1, вам обязательно нужно изменить getData (), чтобы он возвращал Map <MyClass.Key, String>. Это не сломает старый код.

Craig P. Motlin 15.01.2009 23:06

потрясающий пример! Благодарю. мне нужно было использовать entry.getKey () и entry.getValue ().

ufk 24.01.2010 15:45

@ufk - ты прав. Это то, что я получаю, когда пишу код в уме, вместо того, чтобы смотреть в документацию по API. Когда вы используете столько языков, сколько я, вы не всегда можете хранить в памяти все детали API.

Paul Tomblin 24.01.2010 15:56

Я нашел этот простой пример на форум Java. Это синтаксис очень похож на список foreach, что я и искал.

import java.util.Map.Entry;
HashMap nameAndAges = new HashMap<String, Integer>();
for (Entry<String, Integer> entry : nameAndAges.entrySet()) {
        System.out.println("Name : " + entry.getKey() + " age " + entry.getValue());
}

[Обновлено:] Я тестировал его, и он отлично работает.

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