recorrer - ¿Puedo iterar a través de una NodeList usando for-each en Java?
java foreach array (8)
Añadiendo la versión feliz de little kotlin para sience:
fun NodeList.forEach(action: (Node) -> Unit) {
(0 until this.length)
.asSequence()
.map { this.item(it) }
.forEach { action(it) }
}
Uno puede usarlo con nodeList.forEach { do_something_awesome() }
Quiero iterar a través de NodeList
usando un bucle for-each en Java. Lo tengo trabajando con un bucle for y un bucle do-while pero no para cada uno.
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());
}
Como NodeList
es solo una interfaz, podría crear una clase que implementaría tanto NodeList
como Iterable
, para poder iterar a través de ella.
La solución para este problema es directa, y afortunadamente debe implementarla solo una vez.
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();
}
}
}
Una vez que haya agregado esta clase de utilidad a su proyecto y haya agregado una import
static
para el método XmlUtil.asList
a su código fuente, puede usarlo así:
for(Node n: asList(dom.getElementsByTagName("year"))) {
…
}
La solución validada es muy útil, pero aquí comparto una solución mejorada basada en la válida, esto te ayuda a iterar también, pero es fácil de usar y seguro:
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();
}
}
}
Uso:
for (Node inner : XMLHelper.getChildNodes(node)) { ... }
Gracias @Holger.
Sé que es tarde para la fiesta, pero ...
Desde Java-8 puedes escribir la solución de @ RayHulha aún más concisamente usando la expresión lambda (para crear un nuevo Iterable
) y el método predeterminado (para 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++);
}
};
}
y luego usarlo así:
NodeList nodeList = ...;
for (Node node : iterable(nodeList)) {
// ....
}
o equivalentemente así:
NodeList nodeList = ...;
iterable(nodeList).forEach(node -> {
// ....
});
Si se elimina el elemento DOM actual (a través de JavaScript) mientras se itera una NodeList (creada a partir de getElementsByTagName () y tal vez otros), el elemento desaparecerá de NodeList. Esto hace que la iteración correcta de NodeList sea más complicada.
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);
}
}
Entonces new IteratableNodeList(doc.getElementsByTagName(elementType)). stream().filter(...)
así: new IteratableNodeList(doc.getElementsByTagName(elementType)). stream().filter(...)
new IteratableNodeList(doc.getElementsByTagName(elementType)). stream().filter(...)
O bien: new IteratableNodeList(doc.getElementsByTagName(elementType)).forEach(...)
NodeList
no implementa Iterable
, por lo que no puede usarlo con el bucle for
mejorado.
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();
}
};
}
};
}