Java:如何从字符串获取Iterator<Character>

34

我需要从一个 String 对象中获得一个 Iterator<Character>。Java中是否有提供此功能的可用函数,还是我必须自己编写代码?


请参阅https://dev59.com/7nI_5IYBdhLWcg3wF_B3 - rogerdpack
12个回答

32

一个选项是使用Guava


One option is to use Guava.
ImmutableList<Character> chars = Lists.charactersOf(someString);
UnmodifiableListIterator<Character> iter = chars.listIterator();

这将产生一个由给定字符串支持的不可变字符列表(无需复制)。

如果您最终需要自己完成此操作,我建议不要像其他示例一样公开Iterator的实现类。相反,我建议创建自己的实用程序类并公开一个静态工厂方法:

public static Iterator<Character> stringIterator(final String string) {
  // Ensure the error is found as soon as possible.
  if (string == null)
    throw new NullPointerException();

  return new Iterator<Character>() {
    private int index = 0;

    public boolean hasNext() {
      return index < string.length();
    }

    public Character next() {
      /*
       * Throw NoSuchElementException as defined by the Iterator contract,
       * not IndexOutOfBoundsException.
       */
      if (!hasNext())
        throw new NoSuchElementException();
      return string.charAt(index++);
    }

    public void remove() {
      throw new UnsupportedOperationException();
    }
  };
}

3
谢谢您的提示。我猜我的问题的答案是“不”。 - Albert
1
@Esko:这在另一个答案中提到过,我猜那个答案已经被删除了...他需要一个特定的Iterator<Character> - ColinD
2
@muffin:Iterator 不是 Iterable(而且 Character 不是 CharSequence)。 - ColinD
@ColinD 是的,我刚意识到我的错误。这里,把我的赞给你。(哇!回复好快啊) - muffin
2
但请注意,如果您的字符串具有代理Unicode字符(即需要多个Java字符才能表示的字符),则通过char进行迭代可能不是您想要的。示例代码在此处:https://gist.github.com/EmmanuelOga/48df70b27ead4d80234b#file-iteratecodepoints-java-L90 - Emmanuel Oga
显示剩余2条评论

18

这个功能不存在,但实现它非常简单:

class CharacterIterator implements Iterator<Character> {

    private final String str;
    private int pos = 0;

    public CharacterIterator(String str) {
        this.str = str;
    }

    public boolean hasNext() {
        return pos < str.length();
    }

    public Character next() {
        return str.charAt(pos++);
    }

    public void remove() {
        throw new UnsupportedOperationException();
    }
}

这个实现可能已经是尽可能高效的了。


14
for (char c : myString.toCharArray()) {

}

2
这在循环中可以工作,但是char[]仍然无法分配给Iterable<Character> - O. R. Mapper
4
由于字符串是不可变的,而数组则不是,所以需要创建一个副本。 - mhsmith

4

从其他答案中借鉴,这可能是最好的直接实现(如果您不打算使用guava)。

/**
 * @param string
 * @return list of characters in the string
 */
public static List<Character> characters(final String string) {
return new AbstractList<Character>() {
        @Override
    public Character get(int index) {
            return string.charAt(index);
        }

        @Override
    public int size() {
            return string.length();
        }
    };
}

2
CharacterIterator it = new StringCharacterIterator("abcd"); 
// Iterate over the characters in the forward direction 
for (char ch=it.first(); ch != CharacterIterator.DONE; ch=it.next())
// Iterate over the characters in the backward direction 
for (char ch=it.last(); ch != CharacterIterator.DONE; ch=it.previous()) 

2
那不是一个 Iterator<Character>,而我只想在这里使用这样的东西。 - Albert
这个怎么样:http://www.java2s.com/Code/JavaAPI/java.util/implementsIteratorCharacter.htm - virgium03
2
@virgium03:实现IterableIterator两个接口是一个非常糟糕的想法,不应该这样做。 - ColinD
@virgium03:大部分都是这样的。但是这个在标准库里吗? - Albert
好的,我不知道它是否在标准库中实现了,但您可以创建自己的类,就像示例中一样。 - virgium03
2
@virgium03,您的示例代码存在严重问题。在iterator()方法中返回this是完全错误的,会违反契约。 - whiskeysierra

2

简短回答:不行,你必须编码。

详细回答: List和Set都有一个用于获取Iterator的方法(还有一些其他的集合类,但可能不是你要找的)。List和Set接口是集合框架的一部分,只允许添加/删除/迭代对象,如Character或Integer(而不是char或int等基本类型)。Java 1.5中有一个名为自动装箱的特性,它将隐藏基本类型到对象的转换,但我不建议使用它,在这种情况下也无法提供你想要的功能。

另一种选择是在自己的类中包装字符串。

implements Iterator<Character>

但这可能需要比它值得的更多的工作。

这里有一个代码片段可以实现你想要的:

String s = "";
List<Character> list = new ArrayList<Character>(s.length());
for (int i = 0; i < s.length(); i++) {
    // note that Character.valueOf() is preferred to new Character()
    // you can omit the Character.valueOf() method
    // and Java 1.5+ will auto-box the primitive into an Object
    list.add(Character.valueOf(s.charAt(i)));
}
Iterator<Character> iterator = list.iterator();

ListSet没有实现Iterator - whiskeysierra
@Willi,你说得对,List和Set接口都是Collection的子接口,而Collection包含了.interator()方法。如果你仔细看,这是通过扩展包含私有子类的抽象类来实现的,这些子类确实实现了Iterator,可以迭代List或Set中的数据。我应该说明List和Set都提供了获取Iterator的方法。 - cyber-monk
@Albert,这个解决方案有点低效,但使用迭代器的主要原因之一是它允许我们在迭代期间从底层集合中“删除元素并保证良好的语义”,而这个解决方案支持这一特性。Guava ImmutableList返回的迭代器实际上是一个UnmodifiableIterator http://guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/common/collect/UnmodifiableIterator.html,当调用remove()方法时会抛出异常。这引出了一个问题,既然不需要删除,为什么要使用迭代器呢? - cyber-monk
@cyber-monk:Iterator合约明确规定不支持remove()。此外,没有其他替代方案可以使用。 - Albert
@Willi,说得好,Collection接口实现了Iterable,AbstractList、AbstractMap等中的私有子类实现了Iterator。 - cyber-monk
显示剩余2条评论

1

使用Java 8或更新版本,您可以使用流式处理功能。通过chars()方法,您可以访问IntStreamIntStream支持iterator()方法,该方法返回一个OfInt迭代器。OfInt实现了Iterator<Integer>

String str = "foobar";
OfInt ofit = str.chars().iterator();
Iterator<Integer> it = ofit;

这不是一个完美的答案,因为你要求的是Iterator<Character>。

顺便说一句:使用str.codePoints(),你也可以访问code point IntStream。


1
你提到了codePoints()。codePoints()的一个重要方面是它允许你处理代理字符。请参见这个答案 - Hawkeye Parker

1

没有直接的方法。不过编码并不难:

public static Iterator<Character> gimmeIterator(final String x) {
        Iterator<Character> it = new Iterator<Character>() {
            String str = x == null ? "" : x;
            int pos = -1; // last read
            public boolean hasNext() {  return(pos+1 <  str.length());  }
            public Character next() { pos++;  return str.charAt(pos);       }
            public void remove() {
                throw new UnsupportedOperationException("remove unsupported for this iterator");
            }
        };  
        return it;
    }

1

如果你不想使用Guava,而是想要一个真正的java.util.Iterator,那么可以借助于Apache Commons Lang来实现这一点。

private static Iterator<Character> iterator(String string) {
    return Arrays.asList(ArrayUtils.toObject(string.toCharArray())).iterator();
}

0
这种感觉很糟糕,但你可以使用带有空字符串分隔符的Scanner。
Scanner scanner = new java.util.Scanner(myInput).useDelimiter("");

Scanner实现了Iterator,所以scanner现在是一个长度为1的字符串Iterator,非常接近。

继续讲述(非常?)糟糕的事情,在Java 8中,您可以这样做来简洁地迭代字符:

for (String s: (Iterable<String>)() -> scanner) { 
  char c = s.charAt(0);
  System.out.println(c); 
}

关于为什么() -> scanner有效(以及为什么它可能是危险的,尽管在这种情况下不是),请参见解释如何将此lambda分配给Iterable


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