la API Java Collections, Parte 1 Personalice y extienda Java Collections
Ted Neward Publicado en 26-11-2012 FacebookTwitterLinked InGoogle+
La API Java Collections llegó a muchos desarrolladores Java como un muy
necesitado reemplazo del array Java estándar y todas sus falencias. La asociación de Colecciones, principalmente con ArrayList no es un error, pero hay mucho más que las Colecciones para aquellos que siguen buscando. Acerca de esta serie ¿Así que usted considera que sabe acerca de programación Java? El hecho es que la mayoría de los desarrolladores no profundizan en la plataforma Java, y apenas aprenden lo necesario para realizar su trabajo. En esta serie, Ted Neward profundiza hacia el núcleo de la funcionalidad de la plataforma Java para descubrir pequeños datos conocidos que pueden ayudarle a resolver incluso los desafíos de programación más complicados. De forma similar, aunque Map (y su implementación frecuentemente seleccionada, HashMap) son excelentes para realizar pares nombre-valor o clave-valor, no existe razón para que usted se limite a estas herramientas ya conocidas. Es posible reparar gran cantidad de código propenso a errores con la API adecuada, o incluso don la Colección adecuada. Este segundo artículo de la serie 5 cosas es el primero de varios dedicados a Colecciones, dado que son un elemento bastante central de lo que hacemos en la programación Java. Comenzaré con una mirada a las formas más rápidas (aunque posiblemente no las más comunes) de hacer las cosas de todos los días, como intercambiar Array por List. Después de ello, ahondaremos en información menos conocida, como la escritura de una clase personalizada de Colecciones y la extensión de la API Java Collections. 1. Las Colecciones se imponen sobre los arreglos Desarrolle habilidades de este tema Este contenido es parte de un knowledge path progresivo para avanzar en sus habilidades. Vea Conviértase en desarrollador Java Los desarrolladores principiantes en tecnología Java pueden no saber que los arreglos se incluyeron originalmente en el lenguaje para refutar las críticas al desempeño de los desarrolladores C++ a comienzos de los años 90. Bien, hemos recorrido un largo camino desde entonces, y las ventajas en desempeño de los arrays generalmente se quedan cortas cuando se comparan con las de las bibliotecas de Java Collections. Volcar el contenido de arry en una cadena de caracteres, por ejemplo, requiere de la iteración de todo el array y de la concatenación del contenido en una String; mientras que, las implementaciones de Colecciones tienen todas una implementación toString() viable. Con excepción de casos raros, es una buena práctica convertir cualquier array que llegue a usted en una colección, tan rápido como le sea posible. Esto formula entonces la pregunta, ¿cuál es la forma más fácil para esta conversión? Resulta ser que la API Java Collections lo facilita, como se muestra en el Listado 1: Listado 1. ArrayToList 1 2 import java.util.*; 3 4 public class ArrayToList { 5 public static void main(String[] args) 6 { 7 // This gives us nothing good 8 System.out.println(args); 9 10 // Convert args to a List of String List<String> argList = Arrays.asList(args); 11 12 // Print them out 13 System.out.println(argList); 14 } 15 } 16 Note que la List retornada no se puede modificar, por lo que los intentos de añadir nuevos elementos a ella arrojarán una UnsupportedOperationException. Y, dado que Arrays.asList() usa un parámetro varargs para los elementos a añadir a la List, también es posible crearlo para crear fácilmente Listas fuera de objetos new. 2. La iteración es ineficiente No es poco común desear mover el contenido de una colección (particularmente una que haya sido elaborada a partir de un array) hacia otra colección, o remover una colección pequeña de objetos de una más grande. Es posible verse tentado(a) a simplemente iterar la colección y a añadir o remover cada elemento a medida que se encuentra, pero no lo haga. Iterar, en este caso, tiene grandes desventajas: Puede ser ineficiente cambiar el tamaño de la colección con cada adición o eliminación. Existe una pesadilla potencial por concurrencia en adquirir un bloqueo, efectuar la operación y liberar el bloqueo cada vez. Existe la condición de actualización causada por otros hilos que golpean su colección mientras se lleva a cabo la adición o eliminación. Es posible evitar estos problemas utilizando addAll oremoveAll para pasar la colección que contiene los elementos que usted desea añadir o remover. 3. Para hacer un bucle en cualquier Iterable La mejora para bucle, una de las mayores mejoras añadidas al lenguaje Java en Java 5, eliminó la última barrera para trabajar con Java Collections. Antes, los desarrolladores tenían que obtener manualmente un Iterator, usar next() para obtener el objeto al que se apuntaba desde el Iterator y verificar si había más objetos disponibles usando hasNext(). Después de Java 5, tenemos la libertad de usar una variante for-loop que maneja todo lo anterior de forma silenciosa. En realidad, esta mejora funciona con cualquier objeto que implemente la interfaz Iterable , no solo Collections. El Listado 2 muestra un enfoque para crear una lista de hijos de un objeto Person disponible, como en unIterator. En lugar de manejar una referencia hacia la List interna (lo cual permitiría a los interlocutores externos a Person añadir niños a su familia — algo que a muchos padres no les agradaría), el tipo Person implementa Iterable. Este enfoque también hace posible que el bucle mejorado pase por cada uno de los hijos. Listado 2. Mejora de bucle: Muéstreme sus hijos 1 // Person.java 2 import java.util.*; 3 4 public class Person 5 implements Iterable<Person> { 6 public Person(String fn, String ln, int a, Person... kids) 7 { 8 this.firstName = fn; this.lastName = ln; this.age = a; 9 for (Person child : kids) 10 children.add(child); } 11 public String getFirstName() { return this.firstName; } 12 public String getLastName() { return this.lastName; } 13 public int getAge() { return this.age; } 14 15 public Iterator<Person> iterator() { return children.iterator(); } 16 17 public void setFirstName(String value) { this.firstName = value; } public void setLastName(String value) { this.lastName = value; } 18 public void setAge(int value) { this.age = value; } 19 20 public String toString() { 21 return "[Person: " + 22 "firstName=" + firstName + " " + 23 "lastName=" + lastName + " " + "age=" + age + "]"; 24 } 25 26 private String firstName; 27 private String lastName; 28 private int age; 29 private List<Person> children = new ArrayList<Person>(); } 30 31 // App.java 32 public class App 33 { 34 public static void main(String[] args) 35 { 36 Person ted = new Person("Ted", "Neward", 39, 37 new Person("Michael", "Neward", 16), new Person("Matthew", "Neward", 10)); 38 39 // Iterate over the kids 40 for (Person kid : ted) 41 { 42 System.out.println(kid.getFirstName()); 43 } } 44 } 45 46 47 48 49 50 51 Utilizar Iterable tiene algunas desventajas obvias cuando se hace modelaje de dominio, dado que solo una colección de objetos puede ser soportada tan "implícitamente" mediante el método iterator() . Sin embargo, para casos en los que la colección de hijos es obvia, Iterable hace que la programación contra el tipo de dominio sea mucho más fácil y más obvia. 4. Algoritmos clásicos y personalizados ¿Alguna vez ha deseado recorrer una Collection, pero en reversa? Allí es donde un algo ritmo clásico de Java Collections es útil. Los hijos de Person en elListado 2 de arriba están listados en el orden en que se recorrieron; pero, ahora usted desea listarlos en el orden invertido. Si bien es posible escribir otro bucle para insertar cada objeto en una nueva ArrayList en el orden opuesto, la codificación podría crecer tediosamente después de la tercera o cuarta vez. Es aquí donde el algoritmo poco utilizado del Listado 3 entra en escena: Listado 3. ReverseIterator 1 2 public class ReverseIterator 3 { 4 public static void main(String[] args) { 5 Person ted = new Person("Ted", "Neward", 39, 6 new Person("Michael", "Neward", 16), 7 new Person("Matthew", "Neward", 10)); 8 9 // Make a copy of the List 10 List<Person> kids = new ArrayList<Person>(ted.getChildren()); // Reverse it 11 Collections.reverse(kids); 12 // Display it 13 System.out.println(kids); 14 } 15 } 16 La clase Collections tiene cierto número de estos "algoritmos", métodos estáticos que se implementan para tomarCollections como parámetros y proporcionar comportamientos independientes de la implementación sobre la colección como un todo. Y aún más, los algoritmos presentes en la clase Collections ciertamente no son la última palabra en un gran diseño de API — por ejemplo, prefiero métodos que no modifiquen el contenido (de la Collection ingresada) directamente. Así que es bueno que sea posible escribir algoritmos personalizados usted mismo(a), como el que se muestra en el Listado 4: Listado 4. ReverseIterator simplificado 1 class MyCollections 2 { 3 public static <T> List<T> reverse(List<T> src) 4 { 5 List<T> results = new ArrayList<T>(src); 6 Collections.reverse(results); return results; 7 } 8 } 9 5. Extienda la API Collections El algoritmo personalizado de arriba muestra un punto final sobre la API Java Collections: que siempre se pretendió para que se extendiera y modificara para adaptarse a los propósitos específicos de los desarrolladores. Así, por ejemplo, digamos que usted hubo necesidad de la lista de hijos en la clase Person siempre se ordenara por edad. Aunque usted pudo haber escrito código para ordenar los hijos una y otra vez (usando el método Collections.sort , tal vez), habría sido mucho mejor tener una clase Collectionque los ordenara por usted. De hecho, es posible que usted ni siquiera se haya preocupado por preservar el orden en que los objetos se insertaban en Collection (lo cual es la principal justificación para una List). Tal vez usted solo desea mantenerlos en un orden clasificado. Ninguna claseCollection dentro de java.util cumple estos requisitos, pero escribir una es trivial. Todo lo que es necesario hacer es crear una interfaz que describa el comportamiento abstracto que la Collection debería proporcionar. En el caso de una SortedCollection, la intención es completamente conductual. Listado 5. SortedCollection 1 public interface SortedCollection<E> extends Collection<E> 2 { 3 public Comparator<E> getComparator(); 4 public void setComparator(Comparator<E> comp); } 5 Es casi decepcionante escribir una implementación de esta nueva interfaz: Listado 6. ArraySortedCollection 1 import java.util.*; 2 3 public class ArraySortedCollection<E> implements SortedCollection<E>, Iterable<E> 4 { 5 private Comparator<E> comparator; 6 private ArrayList<E> list; 7 8 public ArraySortedCollection(Comparator<E> c) 9 { this.list = new ArrayList<E>(); 10 this.comparator = c; 11 } 12 public ArraySortedCollection(Collection<? extends E> src, Comparator<E> c) 13 { 14 this.list = new ArrayList<E>(src); this.comparator = c; 15 sortThis(); 16 } 17 18 public Comparator<E> getComparator() { return comparator; } 19 public void setComparator(Comparator<E> cmp) { comparator = cmp; sortThis(); 20 21 public boolean add(E e) { boolean r = list.add(e); sortThis(); return r; } 22 public boolean addAll(Collection<? extends E> ec) 23 { boolean r = list.addAll(ec); sortThis(); return r; } 24 public boolean remove(Object o) 25 { boolean r = list.remove(o); sortThis(); return r; } 26 public boolean removeAll(Collection<?> c) { boolean r = list.removeAll(c); sortThis(); return r; } 27 public boolean retainAll(Collection<?> ec) 28 { boolean r = list.retainAll(ec); sortThis(); return r; } 29 30 public void clear() { list.clear(); } 31 public boolean contains(Object o) { return list.contains(o); } 32 public boolean containsAll(Collection <?> c) { return list.containsAll(c); } public boolean isEmpty() { return list.isEmpty(); } 33 public Iterator<E> iterator() { return list.iterator(); } 34 public int size() { return list.size(); } 35 public Object[] toArray() { return list.toArray(); } 36 public <T> T[] toArray(T[] a) { return list.toArray(a); } 37 38 public boolean equals(Object o) { 39 if (o == this) 40 return true; 41 42 if (o instanceof ArraySortedCollection) 43 { 44 ArraySortedCollection<E> rhs = (ArraySortedCollection<E>)o; return this.list.equals(rhs.list); 45 } 46 47 return false; 48 } 49 public int hashCode() 50 { return list.hashCode(); 51 } 52 public String toString() 53 { 54 return list.toString(); 55 } 56 private void sortThis() 57 { 58 Collections.sort(list, comparator); 59 } 60 } 61 62 63 64 65 66 67 68 69 70 Esta implementación 'rápida y poco decente', escrita sin optimizaciones en mente, obviamente soportaría algo de trabajo adicional. Pero el punto es que la API Java Collections nunca pretendió dar la última palabra sobre todas las cosas relacionadas con colecciones. Esta necesita y fomenta las extensiones. Ciertamente, algunas extensiones serán de la variedad "trabajo pesado", como las introducidas en java.util.concurrent. Pero otras serán tan simples como escribir un algoritmo personalizado o una extensión simple hacia una clase Collection existente. Extender la API Java Collections puede parecer algo abrumador, pero una vez que comience a hacerlo encontrará que no es tan difícil como usted pensó. En conclusión Así como Java Serialization, la API Java Collections contiene bastantes rincones y ranuras sin explorar — que es por lo cual no hemos terminado con este tema. El siguiente artículo de la serie 5 cosas le proporcionará cinco maneras adicionales para hacer mucho más con la API Java Collections.