【问题标题】:Comparing TreeMap contents gives incorrect answer比较 TreeMap 的内容给出了错误的答案
【发布时间】:2012-07-10 11:34:41
【问题描述】:

我使用 TreeMap 作为另一个 TreeMap 中的“键”

即

TreeMap<TreeMap<String, String>, Object>

在我的代码中,“对象”是一个个人构造,但在这个例子中,我使用了一个字符串。

我创建了一对 TreeMap 来测试 TreeMap.CompareTo() 和 TreeMap.HashCode() 方法。这从以下开始...

public class TreeMapTest 

public void testTreeMap()
{
TreeMap<String, String> first = new TreeMap<String, String>();
TreeMap<String, String> second = new TreeMap<String, String>();

first.put("one", "une");
first.put("two", "deux");
first.put("three", "trois");
second.put("une", "one");
second.put("deux", "two");
second.put("trois", "three");

TreeMap<TreeMap<String, String>, String> english = new TreeMap<TreeMap<String, String>, String>();
TreeMap<TreeMap<String, String>, String> french = new TreeMap<TreeMap<String, String>, String>();

english.put(first, "english");
french.put(second, "french");

我现在从这里调用英文项目,看看它是否包含密钥

if (english.containsKey(second))
{
System.out.println("english contains the key");
//throws error of ClassCastException: Java.util.TreeMap cannot be cast to
//Java.Lang.Comparable, reading the docs suggests this is the feature if the key is
//not of a supported type.
//this error does not occur if I use a HashMap structure for all maps, why is
//this key type supported for one map structure but not another?
}

但是我应该注意 HashMap 和 TreeMap 都指向 AbstractMap 父级中的同一个 HashCode() 方法。

我的第一个想法是将我的 TreeMap 转换为 HashMap,但这似乎有点笨拙!所以我决定将 hashCode() 方法应用于 2 个树形图对象。

int hc1 = first.hashCode();
int hc2 = second.hashCode();



if(hc1 == hc2)
{
systom.out.printline("values are equal " + hc1 + " " + hc2);
}

打印以下内容

values are equal 3877431 & 3877431

对我来说hashcode应该不同,因为key值不同,我找不到HashMap和TreeMap之间hashCode()方法的实现差异的细节。

请不要以下。 仅将 Keys 更改为 HashMap 不会停止 ClassCastException 错误。将所有映射更改为 HashMap 即可。所以 TreeMap 中的 containsKey() 方法有些东西不能正常工作,或者我误解了 - 谁能解释一下?

获取第一个和第二个映射对象的 hashCode 的部分总是产生相同的输出(无论我在这里使用哈希映射还是树映射)但是 if(english.ContainsKey(second)) 不会打印使用 HashMap 时的任何消息,因此 HashMap 实现中的某些内容显然与 compareTo() 方法不同。

我的主要问题是。

在哪里可以找到用于 TreeMap 对象的键类型的详细信息(以防止将来出现“ClassCastException”错误)。

如果我不能使用某种类型的对象作为键,为什么我首先允许将它作为键插入到 TreeMap 中? (当然,如果我可以插入它,我应该能够检查密钥是否存在?)

任何人都可以提出另一个命令 inster/retrieves 来替换我的 TreeMap 键对象的构造吗?

或者我是否可能发现了奇怪的行为。根据我的理解,我应该能够将 TreeMap 替换为 HashMap,还是我偶然发现了一个边缘场景?

提前感谢您的 cmets。

大卫。

ps。这个问题在我的代码中不是问题,因为我使用个人实用程序来创建一个依赖于键和值对的哈希(即我计算键哈希值与值哈希值不同......对不起,如果是一个令人困惑的句子!)我假设 hashCode 方法只是将所有值加在一起,而不考虑项目是键还是值。

pps。我不确定这是否是一个好问题,有没有关于如何改进它的建议?

编辑。

人们似乎认为我在做一些花哨的语言词典,这对我的例子并不感到意外,对此深表歉意。我用这个作为例子,因为它很容易进入我的脑海,写得很快,并展示了我的问题。

真正的问题如下。

我正在访问一个遗留数据库结构,它不能很好地与任何东西对话(结果集不是正向和反向可读等)。所以我抓取数据并从中创建对象。 最小的对象代表表中的一行(这是在上面的示例中我使用字符串值 'english' 或 'french' 的对象。

我有这些rowObjects的集合,每一行都有一个明显的key(这是指向相关rowObject的TreeMap)。

我不知道这是否能让事情变得更清楚!

编辑 2。

我觉得我需要进一步详细说明我使用的原始选择

hashMap<HashMap<String,string>, dataObject>

对于我的数据结构,然后转换为 TreeMap 以获得有序视图。

在编辑 1 中,我说旧版 DB 不能很好地运行(我怀疑这是 JDBC.ODBC 的问题,我不打算购买 JDBC 来与 DB 通信)。事实上,我在创建 java 'dataObject' 时对数据进行了一些修改。这意味着虽然 DB 可能会以升序或降序的方式吐出结果,但我无法知道它们以什么顺序插入到我的 dataObject 中。使用 likeHashMap 似乎是一个不错的解决方案(请参阅 duffymo 的建议),但我稍后需要以有序的方式提取数据,而不仅仅是连续提取数据(LinkedHashMap 仅保留插入顺序),而且我不倾向于乱排序所有内容和当我需要在另外两个项目之间插入一个新项目时制作副本,TreMap 会为我执行此操作......但如果我为键创建一个特定对象,它将只包含一个 TreeMap 作为成员,显然我将需要提供 compareTo 和 hashCode 方法。那么为什么不只是扩展 TreeMap(尽管 Duffymo 有一点要放弃该解决方案)!

【问题讨论】:

  • 您应该阅读 TreeMap 文档。 TreeMap 不是 HashMap。它不使用hashCode()。它的键应该是可比较的,因为 TreeMap 的重点是在内部使用可比较对象的树。 docs.oracle.com/javase/6/docs/api/java/util/TreeMap.html
  • TreeMap 键必须是它们自己的Comparable,或者您必须提供一个可以比较它们的键的Comparator。
  • @JBNizet 我已阅读文档docs.oracle.com/javase/7/docs/api/index.html?java/util/…(注意我使用的是 java 7 而不是 6)。它声明hashcode是从abstractMap继承的,就像HashMap的hashcode
  • TreeMap 不会在其 键 上调用 hashCode()。 TreeMap 的 hashCode() 方法被继承的事实与我的评论无关。您的帖子一直在谈论 hashCode(),但 hashCode() 与 TreeMap 完全无关。 TreeMap 不关心其键的 hashCode。
  • @JBNizet 文档指出 TreMap.put(k,v) 抛出:ClassCastException - 如果指定的键无法与地图中当前的键进行比较。因此,如果 TreeMap 在其继承的某处未同时实现可比较代码和哈希码,我将无法在不引发错误的情况下将其用作键(这不会发生)。这可能是对我的问题的更好描述。 TreeMap 显然没有可比性,所以我不应该在我的 TreeMap 中使用一个作为键。

标签: java dictionary hashcode treemap compareto


【解决方案1】:

这不是一个好主意。映射键必须是不可变的才能正常工作,而您的则不是。

你真正想做什么?当我看到人们用数据结构做这样的事情时,这让我觉得他们确实需要一个对象,但却忘记了 Java 是一种面向对象的语言。

看起来你想要一个粗略的字典来翻译语言。我会创建一个嵌入这些地图的LanguageLookup 类,并提供一些方法让用户更容易与之交互。更好的抽象和封装,更多的信息隐藏。这些应该是您的设计目标。考虑如何添加除英语和法语之外的其他语言,以便您可以在其他上下文中使用它。

public class LanguageLookup { 
    private Map<String, String> dictionary;

    public LanguageLookup(Map<String, String> words) {
        this.dictionary = ((words == null) ? new HashMap<String, String>() : new HashMap<String, String>(words));
    }

    public String lookup(String from) {
        return this.dictionary.get(from);
    }

    public boolean hasWord(String word) { 
        return this.dictionary.containsKey(word);
    }
}

在您的情况下,您似乎想将一个英语单词翻译成法语,然后查看法语词典是否包含该单词:

Map<String, String> englishToFrenchWords = new HashMap<String, String>();
englishToFrenchWords.put("one", "une");
Map<String, String> frenchToEnglishWords = new HashMap<String, String>();
frenchToEnglishWords.put("une", "one");
LanguageLookup englishToFrench = new LanguageLookup(englishToFrenchWords);
LanguageLookup frenchToEnglish = new LanguageLookup(frenchToEnglishWords);
String french = englishToFrench.lookup("one");
boolean hasUne = frenchToEnglish.hasWord(french);

【讨论】:

  • 我曾考虑过创建自己的小“keyClass”,但它必须是它们的集合才能链接到单个项目,或者必须在其中包含键值对的集合它。那么为什么当 java 有可以工作的集合时重新创建轮子,然后我只是创建我的自定义。我想我可以编写自己的 TreeMap 类,它继承自 treemap,然后覆盖 hashCode() 和 compareTo() 方法。
  • 这不是编写自己的 TreeMap 的充分理由。如果我正在审查你的代码,我会让你把它撕掉。
  • @duffmo 我添加了一些编辑,希望能澄清我的情况。我打算使用另一个表现更好的数据库(德比将是我的选择,因为我打算稍后再将 if 用于其他东西,以便我可以使用相同的通信代码)将我原来的遗留数据库转换为临时数据库基础。
【解决方案2】:

您的 TreeMap 不是 Comparable,因此您无法将其添加到 SortedMap,并且它不是不可变的,因此您无法将其添加到 HashMap。您可以使用 IdentityMap 但怀疑 EnumMap 是更好的选择。

enum Language { ENGLISH, FRENCH }

Map<Language, Map<Language, Map<String, String>>> dictionaries = 
                                                    new EnumMap<>(Language.class);

Map<Language, Map<String, String>> fromEnglishMap = new EnumMap<>(Language.class);
dictionaries.put(Language.ENGLISH, fromEnglishMap);
fromEnglishMap.put(Language.FRENCH, first);

Map<Language, Map<String, String>> fromFrenchMap = new EnumMap<>(Language.class);
dictionaries.put(Language.FRENCH, fromFrenchMap);
fromEnglishMap.put(Language.ENGLISH, second);

Map<String, String> fromEnglishToFrench= dictionaries.get(Language.ENGLISH)
                                             .get(Language.FRENCH);

【讨论】:

  • 我看到你在这里做了什么(我的大脑花了一段时间才把它放在一起),好主意,不幸的是在我开始之前我不知道我的枚举的值是什么,我明白了它们以编程方式从遗留数据库中获取,请参阅我对原始问题的编辑。所以我不确定如何处理代码的自动创建以构建我的枚举等等等。+1,因为这听起来像是一个很好的解决方案。
  • 您可以创建非枚举Language 类的实例。
【解决方案3】:

关于Hashmap有效而Treemap无效的问题:

Treemap 是一个“排序映射”,这意味着条目是根据键排序的。这意味着通过实现Comparable 接口,密钥必须是可比较的。地图通常不会实现这一点,我强烈建议您不要创建自定义类型来添加此功能。正如 duffymo 所提到的,使用地图作为键是一个糟糕的想法。

【讨论】:

  • 我已经编辑了我的问题,你还有更多的 cmets 吗?我也回复了上面的 duffymo,并说我将使用 Derby 作为我的临时数据存储,而不是使用映射作为键。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-02-17
  • 1970-01-01
相关资源
最近更新 更多