Я пытаюсь скомпилировать следующий код:
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 () на самом деле getData (String key), где key указывает желаемый объект, который я хочу получить. Итак, поскольку я знаю объект, который получаю, я точно знаю, к чему его следует применить.




Изменять:
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>. Это позволяет избежать лишних поисков хеша.
Спасибо! Вы можете объяснить, почему это исправляет?
Поскольку способ, которым вы объявили это, Map data объявляет данные как Map неизвестного типа, поэтому keySet () возвращает Object. Внесение изменения сообщает компилятору, что это ключи MyClass.Key, а не Object.
Когда вы назначаете getData только данным Map, вы фактически назначаете их Map <Object, Object>, явно определяя их как данные Map <MyClass.Key, String>, вы позволяете циклу foreach сохранять информацию о том, какой тип ключа.
@cletus, правда, но это не было проблемой в этом вопросе.
Не думаю, что Клетус прочитал мой ответ. Очевидно, вы можете / должны использовать оба.
Ответ Мотлина правильный.
У меня две записки ...
Не используйте toString += ..., а используйте StringBuilder и добавляйте к нему данные.
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 ("не отмечено")
Это просто скрытие проблем, а не их устранение. @SuppressWarning ("unchecked") следует использовать с БОЛЬШОЙ осторожностью.
Мне интересно, лучше ли хранить data.keSet () в локальной переменной или оптимизирован цикл foreach?
@Alfred: вам не нужно хранить data.keySet () в вашей собственной переменной. for (Ключ объекта: data.keySet ()) функционально эквивалентен: Iterator <Object> i = data.keySet (). iterator (); while (i.hasNext ()) {Ключ объекта = i.next (); ... здесь идет остаток цикла for ...} То есть data.keySet () вычисляется только один раз.
Вместо этого вы можете захватить 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, хотя мне также нужно вывести имена ключей?
EntrySet более эффективен, потому что вам не нужно выполнять поиск по каждому ключу.
@Blue - намного эффективнее, ПОТОМУ ЧТО вы используете и ключ, и значение.
Чуть более эффективный способ сделать это:
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 () - это наиболее эффективный способ итерации по карте, и он содержит исходные ссылки на карту, поэтому, если вы изменяете запись, вы изменяете фактическую карту и так далее. Тоже очень удобно.
Я бы сделал это, но getData () используется во всем этом проекте, а также в других, поэтому лучше оставить его как есть (возврат объекта).
+1, вам обязательно нужно изменить getData (), чтобы он возвращал Map <MyClass.Key, String>. Это не сломает старый код.
потрясающий пример! Благодарю. мне нужно было использовать entry.getKey () и entry.getValue ().
@ufk - ты прав. Это то, что я получаю, когда пишу код в уме, вместо того, чтобы смотреть в документацию по API. Когда вы используете столько языков, сколько я, вы не всегда можете хранить в памяти все детали API.
Я нашел этот простой пример на форум 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());
}
[Обновлено:] Я тестировал его, и он отлично работает.
Нет необходимости приводить getData () к HashMap, когда вы просто собираетесь назначить его Map. Скорее бросьте карту. Что, если getData () возвращает не-HashMap (например, TreeMap)?