51

NodeList我想在 Java 中使用 for-each 循环遍历一个。我让它与一个 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());
}
4

11 回答 11

51

这个问题的解决方法是直截了当的,幸运的是你只需要实现一次。

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();
    }
  }
}

将此实用程序类添加到项目中并在源代码中添加static importforXmlUtil.asList方法后,您可以像这样使用它:

for(Node n: asList(dom.getElementsByTagName("year"))) {
  …
}
于 2013-10-25T13:31:13.777 回答
22

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

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 -> {
    // ....
});
于 2018-01-08T15:45:17.283 回答
11
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();
        }
      };
    }
  };
}
于 2015-09-25T12:03:39.260 回答
7

为 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() }

于 2017-09-09T15:09:56.733 回答
5

由于NodeList只是一个接口,您可以创建一个同时实现NodeListand的类Iterable,以便遍历它。

于 2013-10-25T12:03:05.803 回答
3

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

于 2013-10-25T12:01:13.443 回答
3

org.apache.commons.collections4.iterators.NodeListIterator在和中有准备使用或复制粘贴迭代器实现com.sun.xml.internal.ws.util.xml.NodeListIterator

于 2019-06-21T12:05:27.170 回答
1

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

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。

于 2018-06-22T10:47:50.527 回答
1

可以使用 Java8 流来迭代 NodeList。

NodeList filterList = source.getChildNodes();

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


});
于 2020-06-03T11:22:53.160 回答
1

如果当前 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(...)

于 2016-03-03T04:03:13.723 回答
0

我要感谢 @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}")}
}
于 2021-01-25T15:50:30.237 回答