【问题标题】:Can I iterate through a NodeList using for-each in Java?我可以在 Java 中使用 for-each 遍历 NodeList 吗?
【发布时间】: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 库不提供简单和通用的迭代器这一事实是一个“细节”,但看起来像是反对这些东西的另一个论据(即使库的选择可能比这一点更复杂)。

标签: java xml dom


【解决方案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();
            }
          };
        }
      };
    }
    

    【讨论】:

    【解决方案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}")}
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2021-06-04
                • 2020-04-05
                • 2013-04-10
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多