Java中的CharSequence与String有何区别?

465

在Android编程中,大多数文本值都使用CharSequence

为什么?这有什么好处,使用CharSequence相对于String有哪些主要影响?

在使用它们和从一个类型转换到另一个类型时,主要的区别是什么,会有什么问题需要注意?


1
更好的答案可以在Java中CharSequence和String的确切区别找到。 - Suragch
9个回答

363

字符串是 CharSequences,所以您可以直接使用字符串而不必担心。Android 只是想帮助您通过允许指定其他 CharSequence 对象(如 StringBuffer)。


98
除非在回调函数中Android给我传递了CharSequence并且我需要一个String,否则请调用charSeq.toString()。 - Martin Konicek
108
请注意来自CharSequence Javadoc的这一警告:该接口不改进equalshashCode方法的一般契约。因此,比较实现CharSequence接口的两个对象的结果通常是未定义的。每个对象可能是由不同的类实现的,而且不能保证每个类都能够测试其实例与其他类的实例进行相等性比较。因此,不适宜将任意CharSequence实例用作集合中的元素或映射中的键。 - Trevor Robinson
3
@Pacerier认为这更多是一个实际限制。 CharSequence 是在 JDK 1.4 中进行修补的,以引入一个通用接口来处理包含字符序列的对象。其中一些对象包含其他状态,因此将 Object.equals 定义为“包含相同的字符序列”可能没有意义。例如,NIO CharBuffer 只公开其 positionlimit 之间的字符作为 CharSequence,尽管它可能包含许多其他字符。 - Trevor Robinson
8
@TrevorRobinson,设计上的问题在于最初在Object中定义了equals/hashCode方法... - Pacerier
4
在我的看法中,ObjectCharSequence中都不存在设计缺陷,没有必要使用接口来确保实现间的相等性。虽然不要求两个集合实现在Collection接口中提供相等性,但如果它们选择这样做,则可以提供相等性。在我看来,CharSequence应该限制在输入方面,并且在返回类型中使用较少。 - Brett Ryan
显示剩余3条评论

95

CharSequence = 接口
String = 具体实现

你说:

从一个转换到另一个

无法从 String 转换。

  • 每个 String 对象都是一个 CharSequence
  • 每个 CharSequence 都可以生成一个 String。调用 CharSequence::toString。如果 CharSequence 恰好是一个 String,则该方法返回对其自身对象的引用。

换句话说,每个 String 都是一个 CharSequence,但不是每个 CharSequence 都是一个 String

面向接口编程

在 Android 中编程时,大多数文本值都期望为 CharSequence。

为什么?有什么好处,使用 CharSequence 比 String 的主要影响是什么?

通常,面向接口编程比面向具体类编程更好。这样可以提高灵活性,因此我们可以在不破坏其他代码的情况下在特定接口的具体实现之间进行切换。

当开发一个API以供各种情况下的程序员使用时,应编写代码以提供和接受最通用的接口。这使得调用程序员可以自由地使用该接口的各种实现,无论哪种实现对他们的特定上下文最好。
例如,看看Java Collections Framework。如果您的API提供或接受对象的有序集合,请将方法声明为使用List而不是ArrayListLinkedList或任何其他第三方List实现。
当编写一个仅在一个特定位置由您的代码使用的快速简单方法时,与编写用于多个位置使用的API相比,您不需要费心使用更通用的接口而不是特定的具体类。但即便如此,使用最通用的接口也不会有害。

它们之间的主要区别是什么,使用它们会遇到什么问题?

  • String表示一个单一的文本片段,完全在内存中,并且是不可变的。
  • CharSequence则表示一个未知的具体实现特征的文本片段。

CharSequence对象可能代表一个巨大的文本块,因此具有内存影响。或者可能是许多分别跟踪的文本块,在调用toString时需要将它们拼接在一起,因此存在性能问题。实现甚至可能从远程服务检索文本,因此存在延迟影响。

如何进行相互转换?

通常不会进行来回转换。 String就是一个CharSequence。如果您的方法声明需要一个CharSequence,调用者可以传递一个String对象,也可以传递其他东西,例如StringBufferStringBuilder。您的方法代码将简单地使用传递的任何内容,调用任何CharSequence方法。

最接近转换的情况是,如果您的代码接收到一个CharSequence并且您知道需要一个String。也许您正在与旧代码进行接口,该代码编写为String类而不是编写为CharSequence接口。或者您的代码将密集地处理文本,例如重复循环或以其他方式进行分析。在这种情况下,您希望仅承受任何可能的性能损失一次,因此您首先调用toString。然后使用您所知道的完全在内存中的单个文本继续进行工作。
扭曲的历史
请注意已接受的答案上的评论。 CharSequence接口是针对现有类结构进行的修补,因此存在一些重要的微妙之处(equals()hashCode())。请注意Java的各个版本(1、2、4和5)标记在类/接口上-多年来发生了很大的变化。理想情况下,CharSequence应该从一开始就存在,但生活就是如此。
我的类图可以帮助你更好地了解Java 7/8中的字符串类型。我不确定这些是否都适用于Android,但整体上下文仍然可能对你有用。请见下方:

diagram of various string-related classes and interfaces


4
你是自己画这个图的吗?想知道是否有一份目录列出了不同数据结构的这些图。 - user171943
8
由我编写,使用OmniGroup的OmniGraffle应用程序手工制作。 - Basil Bourque

40

我认为最好使用CharSequence。原因是String实现了CharSequence接口,因此可以将String传递给CharSequence,但是不能将CharSequence传递给String,因为CharSequence没有实现String接口。此外,在Android中,EditText.getText()方法返回一个Editable,它也实现了CharSequence,并且可以轻松地传递到其中一个,而不是轻松地传递到String中。CharSequence处理所有操作!


10
你可以使用charSequence.toString()这个方法。 - Jorge Fuentes González
2
@jorge:除非该序列是可变的(或由于任何原因需要复制字符以使其成为不可变字符串),否则这将相对低效。 - Lawrence Dol

23

通常使用接口允许您以最小的副作用改变实现。虽然 java.lang.String 非常受欢迎,但在某些情况下,可能希望使用其他实现方式。通过围绕 CharSequences 构建 API 而不是 Strings,代码提供了这样的机会。


10

在实际的Android代码中,经常会遇到一个问题,即使用CharSequence.equals进行比较是有效的,但并不一定能达到预期的效果。

EditText t = (EditText )getView(R.id.myEditText); // Contains "OK"
Boolean isFalse = t.getText().equals("OK"); // will always return false.

需要进行比较

("OK").contentEquals(t.GetText()); 

8

CharSequence

CharSequence是一个接口,而不是一个实际的类。接口只是一组规则(方法),如果类实现了该接口,则必须包含这些规则。在Android中,CharSequence是各种文本字符串的总称。以下是一些常见类型:

(您可以在此处阅读有关这些之间差异的更多信息。)

如果您拥有CharSequence对象,则它实际上是实现CharSequence的类之一的对象。例如:

CharSequence myString = "hello";
CharSequence mySpannableStringBuilder = new SpannableStringBuilder();

拥有像CharSequence这样的通用类型的好处在于您可以使用单个方法处理多个类型。例如,如果我有一个以CharSequence作为参数的方法,我可以传递一个StringSpannableStringBuilder,它将处理任何一个。
public int getLength(CharSequence text) {
    return text.length();
}

字符串

你可以说一个String只是CharSequence的一种。然而,与CharSequence不同的是,它是一个实际的类,因此你可以从它创建对象。所以你可以这样做:

String myString = new String();

但是你不能这样做:

CharSequence myCharSequence = new CharSequence(); // error: 'CharSequence is abstract; cannot be instantiated

因为CharSequence只是String符合的规则列表,所以您可以这样做:

CharSequence myString = new String();

这意味着每当一个方法要求CharSequence时,将String传递给它是可行的。
String myString = "hello";
getLength(myString); // OK

// ...

public int getLength(CharSequence text) {
    return text.length();
}

然而,反之不成立。如果该方法需要一个String参数,你不能传递一个一般被认为是CharSequence的东西,因为它实际上可能是SpannableString或其他类型的CharSequence
CharSequence myString = "hello";
getLength(myString); // error

// ...

public int getLength(String text) {
    return text.length();
}

7
这几乎肯定是出于性能考虑。例如,想象一下一个解析器,需要遍历一个包含字符串的500k ByteBuffer。
有三种方法可以返回字符串内容:
1. 在解析时逐个字符构建String[]。这将花费明显的时间。我们可以使用==而不是.equals来比较缓存引用。
2. 在解析时构建一个包含偏移量的int[],然后在发生get()时动态构建String。每个String都是一个新对象,所以没有缓存返回值并使用==。
3. 在解析时构建CharSequence[]。由于没有存储新数据(除了字节缓冲区中的偏移量),因此解析比#1要快得多。在获取时,我们不需要构建String,因此获取性能等同于#1(比#2好得多),因为我们只返回对现有对象的引用。
除了使用CharSequence获得的处理收益外,您还通过不重复数据来减少内存占用。例如,如果您有一个包含3段文本的缓冲区,并且想要返回所有3段或单个段落,则需要4个Strings来表示此内容。使用CharSequence,您只需要一个包含数据的缓冲区和4个实现CharSequence的实例来跟踪起始位置和长度。

7
请提供参考文献。听起来像是随意猜测正在发生的事情。另外,我觉得你的论点不成立。有人可能只需将500k字节缓冲区作为字符串存储,并返回子字符串,这非常快速且更常见。 - kritzikratzi
7
从JDK7开始,在String上的substring()方法不再共享基础数组,也不是“非常快”。 它需要O(N)时间来处理子字符串的长度,并且每次调用时都会生成底层字符的副本(因此会产生大量垃圾)。 - BeeOnRope
@kritzikratzi 我认为更改的原因是,如果不进行复制,则原始字符串将在所有子字符串的生命周期内保留。鉴于子字符串通常只是原始字符串的一小部分,并且根据使用方式可能会无限期地持续存在,如果子字符串的使用时间比原始字符串长得多,这通常会导致更多的垃圾。一个有趣的替代方案可能是基于子字符串与父字符串大小的比率来确定是否复制,但您必须自己编写CharSequence实现。 - JAB
4
也许我没有理解错,但是这个答案是无意义的。根据定义,CharSequence 是一个接口,它没有任何你讨论的实现细节,因为它本身没有实现。 String 是几个实现 CharSequence 接口的具体类之一。所以 String 就是 CharSequence。您可以比较 String vs StringBuffer vs StringBuilder 的性能细节,但不能比较 CharSequence。写“使用 CharSequence 带来的处理收益”是没有意义的。 - Basil Bourque

0

CharSequence 是一个接口,String 实现了它。您可以实例化一个 String,但不能像 CharSequence 那样做,因为它是一个接口。您可以在官方 Java 网站中找到其他实现CharSequence的类。


-5

CharSequence是一个可读的char值序列,它实现了String。它有4个方法:

  1. charAt(int index)
  2. length()
  3. subSequence(int start, int end)
  4. toString()

请参考文档CharSequence documentation


6
CharSequence并不实现String接口,但反过来是正确的。 - seh
2
请删除这个无意义的回答。 - J. Doe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接