2

我有以下代码,我试图将 StringBuffer 对象作为键放在 TreeSet 中。我这样做的原因是看看我是否可以将可变对象作为键。我没有收到任何编译错误。但是当我运行这段代码时,我得到了代码下方的错误。特别是,我明白了java.lang.StringBuffer cannot be cast to java.lang.Comparable。这个错误说明了什么?

从 javadoc 我看到 StringBuffer 类被声明为 final ( public final class StringBuffer),这是否意味着它是不可变的,因此是可散列的?

我是散列和不可变的东西的新手,所以请在这里帮助我。

谢谢

import java.util.*;
class MutableKeys {
public static void main(String[] args) {
        StringBuffer one = new StringBuffer("one");
        StringBuffer  two = new StringBuffer("two");
        StringBuffer three = new StringBuffer("three");
        Set<StringBuffer> sb=new TreeSet<StringBuffer>();
        sb.add(one);
        sb.add(two);
        sb.add(three);
        System.out.println("set before change: "+ sb);
        one.append("onemore");
        System.out.println("set After change: "+ sb);
    }
}

Exception in thread "main" java.lang.ClassCastException: java.lang.StringBuffer cannot be cast to java.lang.Comparable
    at java.util.TreeMap.put(TreeMap.java:542)
    at java.util.TreeSet.add(TreeSet.java:238)
    at inheritance.MutableKeys.main
4

8 回答 8

4
  1. 这意味着您不能将其子类化StringBufferpublic final class StringBufferStringBuffer 是非常可变的(这就是重点,您可以修改缓冲区的内容。)

  2. 您不想使用可变的东西作为键,因为在对象被修改后,其 equals() 和 hashcode() 方法将返回不同的结果,您将无法再在 Map 中找到它。

  3. 如果您真的想在 TreeSet 中使用 StringBuffer,则必须提供自己的 Comparator,因为 StringBuffer 没有实现 Comparable。

于 2013-08-22T19:37:02.940 回答
3

只需添加一个比较器类,然后在您的 TreeSet 中使用它,如下所示:

class Comparatorbuff implements Comparator<StringBuffer> {

        @Override
        public int compare(StringBuffer s1, StringBuffer s2) {
            return s1.toString().compareTo(s2.toString());

        }

}

in your main method: modify as follows
Set<StringBuffer> sb=new TreeSet<StringBuffer>(new Comparatorbuff());
于 2013-08-22T23:25:07.713 回答
2

问题是TreeSet对您放入其中的项目进行排序。因为StringBuffer没有实现Comparable,所以TreeSet不知道如何对它们进行排序。您应该在Comparator创建TreeSet. 您的比较器将告诉TreeSet如何对StringBuffers 进行排序。要么,要么你可以使用 a HashSet,它不对元素进行排序。

就不变性而言:类声明中的 final 关键字意味着您不能子类化(扩展)它。它本身并没有使类不可变。不可变意味着对象的状态一旦创建就不能更改。StringBuffers 绝对可以在创建后更改其状态,因此它们不是不可变的。

于 2013-08-22T19:36:05.110 回答
1

声明一个类final并不意味着它是不可变的,它意味着不允许任何类继承它。其实StringBuffer是很可变的;这就是课堂的重点。

因为StringBuffer不是Comparable,你TreeSet不知道如何对你的StringBuffers. Set但是,让可变对象成为任何类型(或Map)中的键都是一个坏主意。如果您必须使用 a TreeSet,则创建并使用一个自定义Comparator对象来比较StringBuffer对象。

于 2013-08-22T19:36:27.807 回答
0

TreeSet只接受ComparableasStringBuffer不是对象的Comaprable对象。

树集#add

Throws-ClassCastException - 如果指定的对象无法与当前在此集合中的元素进行比较。

您可以使用String对象(因为字符串是可比较的)而不是StringBuffer对象。
例如:

    Set<String> sb=new TreeSet<String>();
    sb.add(one.toString());
    sb.add(two.toString());
    sb.add(three.toString());
    System.out.println("set before change: "+ sb);
    System.out.println("set After change: "+ sb);
于 2013-08-22T19:37:23.350 回答
0

你问了几个问题:

  1. 一般问题:“你能有一个可变的哈希键吗”
  2. 具体问题:“StringBuffer 可以用作 TreeSet 的键”

你有一些困惑,我会帮你解决

Java 中的 Maps 使用了 2 种识别策略(或多或少)。

  1. 散列:输入“Foo”被转换为生成唯一访问数组索引的数字的最佳尝试。(纯粹主义者,请不要滥用我,我是故意简化)。该索引是存储您的值的位置。“Foo”和“Bar”实际上可能生成相同的索引值,这意味着它们都将映射到相同的数组位置。显然这是行不通的,所以这就是“equals()”方法的用武之地;它用于消除歧义

  2. 比较:通过使用比较方法,您不需要这个额外的消歧步骤,因为比较从一开始就不会产生这种冲突。“Foo”唯一的键是“Foo”。不过,一个非常好的主意是,如果可以的话,将“equals()”定义为 compareTo() == 0; 为了一致性起见。不是要求。

现在回答你的一般问题:

  1. 映射的键可以是可变的。答:是的,非常非常糟糕和愚蠢。示例:Map.put(k,v); k.modifyInternalHash(); Map.get(k) = null; // 这里不好
    实际上这是由于粗心散列而发生的。虽然这可能发生在比较地图上,但诊断起来会容易得多。

  2. StringBuffer 可以用作 TreeMap/Set 的键吗?是的。使用替代构造函数:TreeSet(Comparator< T >comparator) 并为 StringBuffer 定义自己的比较方法

祝你好运

于 2013-08-22T19:47:52.323 回答
0

是的,您可以,但正如上述答案所述,您必须编写一个比较器。

但真正的问题是你为什么要这样做?StringBuffer 的目的是在创建字符串时修改状态。由于它是 SortedMap 中的一个键,因此您不应该修改该键,因此保存 StringBuffer 没有意义。你想要做的是调用 StringBuffer.toString() ,它返回一个 String 并使用 String 作为你的键。

于 2013-08-22T20:16:10.520 回答
0

我们依赖于强制的默认自然排序顺序,对象应该是同质的和可比较的,否则我们会得到运行时异常,说 Class cast 异常。当且仅当 Corresopding 类实现了可比较的接口时,才说对象是可比较的。String 类和所有包装类已经实现了可比较的接口,但 String Buffer 类没有实现可比较的接口。因此,它将对上述代码给出类成本例外。

于 2019-12-12T19:28:29.607 回答