Могу ли я выполнять итерацию через NodeList, используя для каждого в Java?
Я хочу выполнить итерацию через NodeList
с использованием цикла for-each в Java. Я работаю с циклом for и циклом do-while, но не для каждого.
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());
}
Ответы
Ответ 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();
}
}
}
После добавления этого класса утилиты в проект и добавления static
import
для метода XmlUtil.asList
к исходному коду вы можете использовать его следующим образом:
for(Node n: asList(dom.getElementsByTagName("year"))) {
…
}
Ответ 2
Я знаю, что уже поздно на вечеринку, но...
Начиная с Java-8, вы можете написать решение @RayHulha еще более кратко, используя лямбда-выражение (для создания нового Iterable
) и метод по умолчанию (для Iterator.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 -> {
// ....
});
Ответ 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
Поскольку NodeList
- это просто интерфейс, вы можете создать класс, который будет реализовывать как NodeList
, так и Iterable
, чтобы перебирать его.
Ответ 5
Добавление счастливой маленькой версии kotlin для sience:
fun NodeList.forEach(action: (Node) -> Unit) {
(0 until this.length)
.asSequence()
.map { this.item(it) }
.forEach { action(it) }
}
Затем можно использовать его с nodeList.forEach { do_something_awesome() }
Ответ 6
NodeList
не реализует Iterable
, поэтому вы не можете использовать его с расширенным циклом for
.
Ответ 7
Если текущий элемент DOM удален (через JavaScript) при повторении NodeList (созданного из getElementsByTagName() и, возможно, других), элемент исчезнет из 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(...)
Ответ 8
Проверенное решение очень полезно, но здесь я поделюсь улучшенным решением, основанным на действительном, это также поможет вам выполнить итерации, но при этом будет простым в использовании и безопасным:
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.
Ответ 9
В org.apache.commons.collections4.iterators.NodeListIterator
и com.sun.xml.internal.ws.util.xml.NodeListIterator
есть готовые к использованию или скопированные реализации итераторов.