W pewnym projekcie, miałem mały problem związany z przejrzystością kodu – mianowicie w kodzie przewijały się różnego rodzaju strategie przechodzenia po elementach:
- kolekcje (java.util.Collection) – for-each
- enumeracja (java.util.Enumeration) – for z licznikiem i metoda hasMore elements
- listy node’ów (org.w3c.dom.NodeList) – for z licznikiem i metoda getLength wyznaczająca koniec
Dodatkowym problemem zaciemniającym kod w przypadku ostatniego elementu jest fakt, że metoda NodeList.item zwraca ZAWSZE node’a, nawet jeżeli użytkownik WIE, że wszystkie node’y będą typu Element i tak musi je sobie zrzutować.
Rozwiązaniem problemu było dodanie metody szablonowej, która na wejsciu przyjmie obiekt, po ktorym będzie sobie chodzić iterator i będzie go zwracać, no to do dzieła:
package pl.bedkowski.util;
import java.util.Enumeration;
import java.util.Iterator;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class IterGenerator {
public static <T> Iterable<T> iter(final Enumeration<T> e) {
return new Iterable<T>() {
public Iterator<T> iterator() {
return new Iterator<T>() {
public boolean hasNext() {
return e.hasMoreElements();
}
public T next() {
return e.nextElement();
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
}
Tutaj jak widać sprawa była prosta – interfejs Enumeration jest szablonem, więc zamiana jednego na drugi odbyła się w miarę bezboleśnie. Z NodeListą już nie pójdzie tak łatwo, no ale do dzieła
public static <T extends Node> Iterable<T> iter(final NodeList e, Class<T> clazz) {
final int size = e.getLength();
return new Iterable<T>() {
public Iterator<T> iterator() {
return new Iterator<T>() {
private int current = 0;
public boolean hasNext() {
return current < size;
}
@SuppressWarnings("unchecked")
public T next() {
return (T) e.item(current++);
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
};
}
Jak widać w tym przypadku już nie jest tak pięknie, potrzeba dodać adnotację, żeby ukryć warninga, ale przynajmniej użytkownik nie musi sobie tym zaprzątać głowy. Dodajmy jeszcze małe ułatwienie:
public static Iterable<Node> iter(final NodeList e) {
return iter(e, Node.class);
}
I teraz tylko jakiś przykładzik:
import static pl.bedkowski.util.IterGenerator.iter;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class Example {
public static void main(String[] args) {
NodeList list = null; // wiem, wiem NPE, ale nie mam zadnej list pod reka
for(int i=0;i<list.getLength();i++) {
Node n = list.item(i);
System.out.println(n.getLocalName());
}
// teraz wykorzsytamny nasz iter
for(Node n : iter(list)) {
System.out.println(n.getLocalName());
}
//teraz gdy wiemy, ze lista zawiera same obiekty typu Element
for(int i=0;i<list.getLength();i++) {
Element e = (Element) list.item(i);
System.out.println(e.getTagName());
}
// z wykorzytaniem itera
// teraz wykorzsytamny nasz iter
for(Element n : iter(list, Element.class)) {
System.out.println(n.getTagName());
}
}
}
Jak widać pracy wcale nie trzeba włożyć dużo a korzyść spora