为啥Java中的String类没有实现Iterable?

Posted

技术标签:

【中文标题】为啥Java中的String类没有实现Iterable?【英文标题】:Why doesn't the String class in Java implement Iterable?为什么Java中的String类没有实现Iterable? 【发布时间】:2011-02-15 21:10:14 【问题描述】:

许多 Java 框架类实现 Iterable,但 String 没有。迭代 String 中的字符是有意义的,就像可以迭代常规数组中的项目一样。

String 没有实现Iterable 有什么原因吗?

【问题讨论】:

遍历字符串的 char 数组的问题在哪里? (strInput.ToCharArray) Tim: String#toCharArray 创建一个包含字符串字符副本的数组。即使它有效,它也会增加不必要的开销来迭代字符。 @jambjo Iterator<Character> 开销会更少??? @Tom:视情况而定,Iterator 的开销可能比 toCharArray 小很多 @Tom:正如我所说,这取决于情况:如果您有一个长字符串并使用枚举器仅获取几个条目,那会好得多。极端样本:例如如果您有一个 1GB 的字符串并使用枚举器来获取前 100 个字符 100 次,那么在枚举器的情况下您基本上会有 10,000 次访问,但是当使用 toCharArray 时,您将拥有 100 个字符串副本,仅此一项就会导致 5,000,000,000 次访问和您仍然需要迭代,因此它将是 10,000 对 5,000,010,000。很清楚哪个更好,不是吗(是的,这是一个构造的极端情况) 【参考方案1】:

我不确定为什么在 2020 年仍未实现,我的猜测是字符串在 Java 中得到了很多特殊处理(编译器重载了 + 运算符以用于字符串连接、字符串文字、字符串常量存储在一个公共池中,等等),这个功能可能比看起来更难实现(或者它可能会搞砸太多事情,从实现者的角度来看是值得付出努力的)。

另一方面,实现与此接近的东西并不是太多的工作。我想在我的一个项目中使用它,所以我编写了这些简单的类:

public class CharIterable implements Iterable<Character> 
  public CharIterable(CharSequence seq) 
    this.seq = seq;
  

  @Override
  public Iterator<Character> iterator() 
    return new CharIterator(seq);
  

  private final CharSequence seq;


public class CharIterator implements Iterator<Character> 
  public CharIterator(CharSequence sequence) 
    this.sequence = sequence;
  

  @Override
  public synchronized boolean hasNext() 
    return position < sequence.length();
  

  @Override
  public synchronized Character next() 
    return sequence.charAt(position++);
  

  /**
   * Character sequence to iterate over
   */
  private final CharSequence sequence;

  /**
   * Current position of iterator which is the position of the item that
   * will be returned by @link #next().
   */
  private int position = 0;

有了这些我可以做到:

for (Character c: new CharIterable("This is a test")) 
  \\ do something with c

现在对于这样一个简单的事情来说,这看起来很多,但它允许将字符串视为可迭代的字符数组,并与旨在处理事物集合(列表、集合等)的方法透明地工作。

【讨论】:

【参考方案2】:

确实没有一个好的答案。 Java 中的迭代器特别适用于离散项(对象)的集合。您会认为实现CharSequenceString 应该是离散字符的“集合”。相反,它被视为恰好由字符组成的单个实体。

在 Java 中,迭代器似乎只真正应用于集合而不是字符串。没有理由这样做(据我所知,您可能不得不与 Gosling 或 API 编写者交谈);这似乎是惯例或设计决定。事实上,没有什么阻止 CharSequence 实现Iterable

也就是说,您可以像这样遍历字符串中的字符:

for (int i = 0; i < str.length(); i++) 
  System.out.println(str.charAt(i));

或者:

for(char c : str.toCharArray()) 
  System.out.println(c);

或者:

"Java 8".chars().forEach(System.out::println);

还请注意,您不能就地修改字符串的字符,因为字符串是不可变的。 String 的可变伴侣是 StringBuilder(或旧的 StringBuffer)。

编辑

根据这个答案的 cmets 进行澄清。我试图解释一个可能的理由为什么String 上没有迭代器。我并不是想说这是不可能的。事实上,我认为CharSequence 实现Iterable 是有意义的。

String 提供CharSequence,如果只是在概念上,它与String 不同。 String 通常被认为是一个单一的实体,而 CharSequence 正是这样:一个字符序列。在字符序列(即CharSequence)上使用迭代器是有意义的,但不仅仅是在String 本身上。

正如 Foxfire 在 cmets 中正确指出的那样,String 实现了 CharSequence 接口,因此在类型方面,StringCharSequence。从语义上讲,在我看来它们是两个独立的东西——我可能在这里很迂腐,但是当我想到 String 时,我通常认为它是一个恰好由字符组成的单一实体。考虑数字序列1, 2, 3, 4 和数字1234 之间的区别。现在考虑字符串abcd 和字符序列a, b, c, d 之间的区别。我试图指出这种差异。

在我看来,问为什么String 没有迭代器就像问为什么Integer 没有迭代器以便您可以迭代各个数字。

【讨论】:

当然将字符串视为字母的集合并非完全没有先例,并且在“有意义”的情况下争论它似乎有点虚假。 “字符串并不是真正的离散字符的“集合”。”。嗯,它是。事实上,它甚至实现了 CharSequence,这正是:离散字符的有序集合! 你可以在 C# 中做foreach (char c in s),很漂亮! @Vivin:CharSequence 是一个 INTERFACE(完全是 Iterable)。因此,实现接口的是 String 本身。它不是从字符串创建的。 @Vivin:那么恕我直言,您应该尝试将原始问题回答为:“为什么 CharSequence 不实现 Iterable”。 (这当然在技术上仍然意味着“为什么 String 不实现 Iterable”)【参考方案3】:

使 String 实现 Iterable 的主要原因之一是启用简单的 for(each) 循环,如上所述。因此,不让 String 实现 Iterable 的一个原因可能是天真的实现固有的低效率,因为它需要对结果进行装箱。但是,如果生成的迭代器(由 String.iterator() 返回)的实现是最终的,编译器可以对其进行特殊处理并生成免于装箱/拆箱的字节码。

【讨论】:

【参考方案4】:

他们只是忘了这样做。

【讨论】:

你有这个断言的证据吗?在我看来,这似乎更可能是因为 String 早于 Iterable 接口(Strings presumable 可以追溯到 Java 1.0,Iterable 可以追溯到 Java 1.5),并且一旦语言说明符习惯于不将 String 视为集合之一,他们继续这样对待它。【参考方案5】:

无论如何,我的同事 Josh Bloch 强烈希望将此功能添加到 Java 7:

for (char c : aString) ...

for (int codePoint : aString) ...

这将是循环遍历字符和逻辑字符(代码点)的最简单方法。它不需要让String 实现Iterable,这将强制发生拳击。

如果没有该语言功能,就不会为这个问题提供真正好的答案。而且他似乎很乐观地认为他能做到这一点,但我不确定。

【讨论】:

可惜没有成为 Java 7 的项目代币。 如果他们有朝一日计划这样做,请确保它适用于任何继承 CharSequence 的对象,而不是仅适用于 String @akuhn 既没有进入 Java 8 也没有进入 Java 9... RIP。 @JoaaoVerona 不是直接的,就像让 CharSequence 或 String 实现 Iterable 一样。但是 Java 8 使用(默认)方法 chars()codePoints() 扩展了 CharSequence 接口,它们返回一个 IntStream。该接口有一个forEach(IntConsumer action) 方法,这是下一个最好的方法。你可以写"test".chars().forEach(c -&gt; ...),它与for循环没有太大区别。我怀疑没有 String 或 CharSequence 实现 Iterable 的一个原因是您可以迭代其字符或其代码点。一个重要的区别。【参考方案6】:

Iterable 是什么? Iterable&lt;Integer&gt; 最有意义,其中每个元素代表一个 Unicode 代码点。当我们有toCharArray 时,即使Iterable&lt;Character&gt; 也会变得缓慢且毫无意义。

【讨论】:

【参考方案7】:

原因很简单:字符串类比Iterable要老很多。

显然没有人愿意将接口添加到 String(这有点奇怪,因为它确实实现了基于完全相同的想法的 CharSequence)。

但是它会因为 Iterable 返回一个对象而有些不便。所以它必须包装每个返回的 Char。

编辑:作为比较:.Net 确实支持枚举 String,但在 .Net 中 Iterable 也适用于本机类型,因此不需要像 Java 中那样需要包装。

【讨论】:

“将 Iterable 添加到 String 类使其性能不佳”,有道理;但是没有人因为它太旧而将 Itreable 添加到 String 类中,这似乎有点奇怪。你能再解释一下吗? 字符串早在 Iterable 之前就已经存在了。因此,您必须稍后添加接口。虽然这是可能的,但在某些极端情况下,它可能是一个突破性的变化。考虑到 String 的使用频率,这可能被认为是有风险的。这只是猜测。我不知道这些考虑是否真的影响了这个决定。但这似乎很有可能。 我不认为将Iterable(或任何类型)添加到String 是一项重大更改。你不能继承String(谢天谢地)。 @Tom:在 99.9% 的情况下肯定不会。但是很容易构建可能会崩溃的案例(例如,反映在接口上)。考虑到基本上每个应用程序都在某个地方使用字符串,这仍然可能是一个原因。 任何被破坏的代码都应该被破坏。我认为我可以肯定地说这不是考虑的原因。【参考方案8】:

如果你真的想在这里迭代:

String str = "***";

for (char c: str.toCharArray())
     //here you go

【讨论】:

-1 抱歉,我看不出这个答案与所提问题有什么关系。 一个问题可能是 toCharArray 创建了一个新数组。所以这是非常低效的。 @Helper:字符串是不可变的。但是返回的数组不是。并且更改数组不得影响字符串。所以它确实制作了一个完整的副本。 +1 - 对于小字符串,创建一个 char[] 与创建一个迭代器大致一样昂贵 - 它是一个对象分配(以及少量的内存初始化和复制)。随着字符串变长,内存初始化/复制开销变得很大,但仍远不及对每个字符进行装箱。

以上是关于为啥Java中的String类没有实现Iterable?的主要内容,如果未能解决你的问题,请参考以下文章

JAVA中,为啥object对象中的equals方法比较的是同一,而String对象比较的是相等?

java中的string类中内容一旦被初始化就不能改变为啥还有replace方法呢?

为啥 java String类 中的valueOf(Object obj) 空时返回的是"null"而不是null

为啥包含 main 方法的类没有实例化并且在 Java 中仍然可以?

为啥 java 7 中没有 Files.readAllLines(String path)? [关闭]

java 里为啥有些类在生成对象的时候需要复写一些方法呢?这是怎么回事?