我可以在 Java 中使用 for-each 遍历 NodeList 吗?

Posted

技术标签:

【中文标题】我可以在 Java 中使用 for-each 遍历 NodeList 吗?【英文标题】:Can I iterate through a NodeList using for-each in Java? 【发布时间】:2013-11-04 12:51:26 【问题描述】:

我想在 Java 中使用 for-each 循环遍历 NodeList。我让它与一个 for 循环和一个 do-while 循环一起工作,但不是 for-each。

NodeList nList = dom.getElementsByTagName("year");
do 
    Element ele = (Element) nList.item(i);
    list.add(ele.getElementsByTagName("MonthId").item(0).getTextContent());
    i++;
 while (i < nList.getLength());

NodeList nList = dom.getElementsByTagName("year");

for (int i = 0; i < nList.getLength(); i++) 
    Element ele = (Element) nList.item(i);
    list.add(ele.getElementsByTagName("MonthId").item(0).getTextContent());

【问题讨论】:

您不能对 NodeList 使用 foreach 循环,因为它没有实现 Iterable 接口。唯一的选择是使用 nodeList.getLength 来使用 for 或 while 循环。 docs.oracle.com/javase/7/docs/api/org/w3c/dom/NodeList.html 虽然这与您提出的问题无关,但我会回避使用 Java 标准库中的 w3c 内容。 IMO 这是一团糟,那里有更好的 XML 解析库。 +Jazzepi 我知道这是老话题,但您建议使用哪个 XML 解析库?注意这个 w3c 库不提供简单和通用的迭代器这一事实是一个“细节”,但看起来像是反对这些东西的另一个论据(即使库的选择可能比这一点更复杂)。 【参考方案1】:

解决此问题的方法很简单,而且,幸运的是您只需实施一次。

import java.util.*;
import org.w3c.dom.*;

public final class XmlUtil 
  private XmlUtil()

  public static List<Node> asList(NodeList n) 
    return n.getLength()==0?
      Collections.<Node>emptyList(): new NodeListWrapper(n);
  
  static final class NodeListWrapper extends AbstractList<Node>
  implements RandomAccess 
    private final NodeList list;
    NodeListWrapper(NodeList l) 
      list=l;
    
    public Node get(int index) 
      return list.item(index);
    
    public int size() 
      return list.getLength();
    
  

一旦您将此实用程序类添加到您的项目中,并为您的源代码中的XmlUtil.asList 方法添加了static import,您就可以像这样使用它:

for(Node n: asList(dom.getElementsByTagName("year"))) 
  …

【讨论】:

【参考方案2】:

我知道聚会迟到了,但是... 从 Java-8 开始,您可以使用 lambda 表达式(用于创建新的Iterable)和默认方法(用于Iterator.remove)更简洁地编写@RayHulha's solution:

public static Iterable<Node> iterable(final NodeList nodeList) 
    return () -> new Iterator<Node>() 

        private int index = 0;

        @Override
        public boolean hasNext() 
            return index < nodeList.getLength();
        

        @Override
        public Node next() 
            if (!hasNext())
                throw new NoSuchElementException();
            return nodeList.item(index++); 
        
    ;

然后像这样使用它:

NodeList nodeList = ...;
for (Node node : iterable(nodeList)) 
    // ....

或类似这样:

NodeList nodeList = ...;
iterable(nodeList).forEach(node -> 
    // ....
);

【讨论】:

干净简单!谢谢.. +1 展示了如何使用它。 return () -&gt; IntStream.range(0, nodeList.getLength()) .mapToObj(nodeList::item) .iterator();【参考方案3】:
public static Iterable<Node> iterable(final NodeList n) 
  return new Iterable<Node>() 

    @Override
    public Iterator<Node> iterator() 

      return new Iterator<Node>() 

        int index = 0;

        @Override
        public boolean hasNext() 
          return index < n.getLength();
        

        @Override
        public Node next() 
          if (hasNext()) 
            return n.item(index++);
           else 
            throw new NoSuchElementException();
            
        

        @Override
        public void remove() 
          throw new UnsupportedOperationException();
        
      ;
    
  ;

【讨论】:

从这里:odoepner.wordpress.com/2012/07/13/… 因为没有代码描述而投反对票【参考方案4】:

为 sience 添加快乐的小 kotlin 版本:

fun NodeList.forEach(action: (Node) -> Unit) 
    (0 until this.length)
            .asSequence()
            .map  this.item(it) 
            .forEach  action(it) 

然后可以将它与nodeList.forEach do_something_awesome() 一起使用

【讨论】:

您可以使用NamedNodeMapattributesNode 做同样的事情 我不知道 Kotlin(一些 Scala),但您不需要在示例用法中输入 it 吗? 确实很难在文档中搜索it,但它是局部变量的简写,而不是写map element -&gt; this.item(element) ,可以简单地写map this.item(it) 【参考方案5】:

由于NodeList 只是一个接口,您可以创建一个实现NodeListIterable 的类,以便对其进行迭代。

【讨论】:

【参考方案6】:

NodeList 没有实现Iterable,因此您不能将它与增强的for 循环一起使用。

【讨论】:

【参考方案7】:

org.apache.commons.collections4.iterators.NodeListIteratorcom.sun.xml.internal.ws.util.xml.NodeListIterator 中有现成可用或复制粘贴的迭代器实现。

【讨论】:

【参考方案8】:

如果当前 DOM 元素在迭代 NodeList(从 getElementsByTagName() 或其他方法创建)时被删除(通过 javascript),则该元素将从 NodeList 中消失。这使得 NodeList 的正确迭代更加棘手。

public class IteratableNodeList implements Iterable<Node> 
    final NodeList nodeList;
    public IteratableNodeList(final NodeList _nodeList) 
        nodeList = _nodeList;
    
    @Override
    public Iterator<Node> iterator() 
        return new Iterator<Node>() 
            private int index = -1;
            private Node lastNode = null;
            private boolean isCurrentReplaced() 
                return lastNode != null && index < nodeList.getLength() &&
                       lastNode != nodeList.item(index);
            

            @Override
            public boolean hasNext() 
                return index + 1 < nodeList.getLength() || isCurrentReplaced();
            

            @Override
            public Node next() 
                if (hasNext()) 
                    if (isCurrentReplaced()) 
                        //  It got removed by a change in the DOM.
                        lastNode = nodeList.item(index);
                     else 
                        lastNode = nodeList.item(++index);
                    
                    return lastNode;
                 else 
                    throw new NoSuchElementException();
                
            

            @Override
            public void remove() 
                throw new UnsupportedOperationException();
            
        ;
    

    public Stream<Node> stream() 
        Spliterator<Node> spliterator =
            Spliterators.spliterator(iterator(), nodeList.getLength(), 0);
        return StreamSupport.stream(spliterator, false);
    

然后像这样使用它: new IteratableNodeList(doc.getElementsByTagName(elementType)). stream().filter(...)

或者: new IteratableNodeList(doc.getElementsByTagName(elementType)).forEach(...)

【讨论】:

【参考方案9】:

经过验证的解决方案非常有用,但在这里我分享一个基于有效解决方案的改进解决方案,这也有助于您进行迭代,但易于使用且安全:

public class XMLHelper 
    private XMLHelper()  

    public static List<Node> getChildNodes(NodeList l) 
        List<Node> children = Collections.<Node>emptyList();
        if (l != null && l.getLength() > 0) 
            if (l.item(0) != null && l.item(0).hasChildNodes()) 
                children = new NodeListWrapper(l.item(0).getChildNodes());
            
        
        return children;
    

    public static List<Node> getChildNodes(Node n) 
        List<Node> children = Collections.<Node>emptyList();
        if (n != null && n.hasChildNodes()) 
            NodeList l = n.getChildNodes();
            if (l != null && l.getLength() > 0) 
                children = new NodeListWrapper(l);
            
        
        return children;
    

    private static final class NodeListWrapper extends AbstractList<Node> implements RandomAccess 
        private final NodeList list;
        NodeListWrapper(NodeList l) 
            list = l;
        
        public Node get(int index) 
            return list.item(index);
        
        public int size() 
            return list.getLength();
        
    

用法:

 for (Node inner : XMLHelper.getChildNodes(node))  ... 

感谢@Holger。

【讨论】:

【参考方案10】:

可以使用 Java8 流来迭代 NodeList。

NodeList filterList = source.getChildNodes();

IntStream.range(0, filterList.getLength()).boxed().map(filterList::item).forEach(node -> 


);

【讨论】:

不错!但是boxed().map可以换成mapToObj来提高效率。【参考方案11】:

我要感谢 @Calin 对 Kotlin 代码的启发,但我想更进一步,能够在一行中按类型和子类过滤 NodeList 内容

fun <T : Node> NodeList.forEach(clazz : KClass<T>, vararg nodeType: Short, action: (T) -> Unit) 
    (0 until this.length).asSequence().map  this.item(it) 
        .filter  nodeType.isEmpty() || nodeType.contains(it.nodeType)  
        .filter  clazz.isInstance(it) .map  clazz.java.cast(it) 
        .forEach  action(it) 


// original variant without any filtering, used for node's attributes

fun NamedNodeMap.forEach(action: (Node) -> Unit) 
    (0 until this.length).asSequence().map  this.item(it) 
        .forEach  action(it) 

使用示例:

xmlDoc.childNodes.forEach(Element::class, Node.ELEMENT_NODE) 
    println("tag $it.tagName with attributes: ") // 'it' is an Element here
    it.attributes.forEach  attr -> println("$attr.nodeName - $attr.nodeValue")

【讨论】:

以上是关于我可以在 Java 中使用 for-each 遍历 NodeList 吗?的主要内容,如果未能解决你的问题,请参考以下文章

Java遍历Map对象

Java中怎么遍历map中value值

java中map的常用遍历方法都有哪些?

2优先使用for-each循环

Java中遍历ConcurrentHashMap的四种方式

For-Each循环