5 cosas que usted no sabía sobre...la API Java Collections, Parte 1

Personalice y extienda Java Collections

La API Java™ Collections es mucho más que un reemplazo de arreglos, aunque no es un mal lugar para comenzar. Ted Neward ofrece cinco consejos para hacer más con Colecciones, incluyendo una base sobre personalización y extensión de la API Java Collections.

Ted Neward, Director, Neward & Associates

Ted NewardTed Neward es el director de Neward & Associates, donde hace consultoría, es mentor, enseña y hace presentaciones sobre Java, .NET, XML Services y otras plataformas. Vive en Seattle, Washington.



26-11-2012

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
import java.util.*;

public class ArrayToList
{
    public static void main(String[] args)
    {
        // This gives us nothing good
        System.out.println(args);
        
        // Convert args to a List of String
        List<String> argList = Arrays.asList(args);
        
        // Print them out
        System.out.println(argList);
    }
}

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
// Person.java
import java.util.*;

public class Person
    implements Iterable<Person>
{
    public Person(String fn, String ln, int a, Person... kids)
    {
        this.firstName = fn; this.lastName = ln; this.age = a;
        for (Person child : kids)
            children.add(child);
    }
    public String getFirstName() { return this.firstName; }
    public String getLastName() { return this.lastName; }
    public int getAge() { return this.age; }
    
    public Iterator<Person> iterator() { return children.iterator(); }
    
    public void setFirstName(String value) { this.firstName = value; }
    public void setLastName(String value) { this.lastName = value; }
    public void setAge(int value) { this.age = value; }
    
    public String toString() { 
        return "[Person: " +
            "firstName=" + firstName + " " +
            "lastName=" + lastName + " " +
            "age=" + age + "]";
    }
    
    private String firstName;
    private String lastName;
    private int age;
    private List<Person> children = new ArrayList<Person>();
}

// App.java
public class App
{
    public static void main(String[] args)
    {
        Person ted = new Person("Ted", "Neward", 39,
            new Person("Michael", "Neward", 16),
            new Person("Matthew", "Neward", 10));

        // Iterate over the kids
        for (Person kid : ted)
        {
            System.out.println(kid.getFirstName());
        }
    }
}

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
public class ReverseIterator
{
    public static void main(String[] args)
    {
        Person ted = new Person("Ted", "Neward", 39,
            new Person("Michael", "Neward", 16),
            new Person("Matthew", "Neward", 10));

        // Make a copy of the List
        List<Person> kids = new ArrayList<Person>(ted.getChildren());
        // Reverse it
        Collections.reverse(kids);
        // Display it
        System.out.println(kids);
    }
}

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
class MyCollections
{
    public static <T> List<T> reverse(List<T> src)
    {
        List<T> results = new ArrayList<T>(src);
        Collections.reverse(results);
        return results;
    }
}

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 Collection que 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
public interface SortedCollection<E> extends Collection<E>
{
    public Comparator<E> getComparator();
    public void setComparator(Comparator<E> comp);
}

Es casi decepcionante escribir una implementación de esta nueva interfaz:

Listado 6. ArraySortedCollection
import java.util.*;

public class ArraySortedCollection<E>
    implements SortedCollection<E>, Iterable<E>
{
    private Comparator<E> comparator;
    private ArrayList<E> list;
        
    public ArraySortedCollection(Comparator<E> c)
    {
        this.list = new ArrayList<E>();
        this.comparator = c;
    }
    public ArraySortedCollection(Collection<? extends E> src, Comparator<E> c)
    {
        this.list = new ArrayList<E>(src);
        this.comparator = c;
        sortThis();
    }

    public Comparator<E> getComparator() { return comparator; }
    public void setComparator(Comparator<E> cmp) { comparator = cmp; sortThis(); }
    
    public boolean add(E e)
    { boolean r = list.add(e); sortThis(); return r; }
    public boolean addAll(Collection<? extends E> ec) 
    { boolean r = list.addAll(ec); sortThis(); return r; }
    public boolean remove(Object o)
    { boolean r = list.remove(o); sortThis(); return r; }
    public boolean removeAll(Collection<?> c)
    { boolean r = list.removeAll(c); sortThis(); return r; }
    public boolean retainAll(Collection<?> ec)
    { boolean r = list.retainAll(ec); sortThis(); return r; }
    
    public void clear() { list.clear(); }
    public boolean contains(Object o) { return list.contains(o); }
    public boolean containsAll(Collection <?> c) { return list.containsAll(c); }
    public boolean isEmpty() { return list.isEmpty(); }
    public Iterator<E> iterator() { return list.iterator(); }
    public int size() { return list.size(); }
    public Object[] toArray() { return list.toArray(); }
    public <T> T[] toArray(T[] a) { return list.toArray(a); }
    
    public boolean equals(Object o)
    {
        if (o == this)
            return true;
        
        if (o instanceof ArraySortedCollection)
        {
            ArraySortedCollection<E> rhs = (ArraySortedCollection<E>)o;
            return this.list.equals(rhs.list);
        }
        
        return false;
    }
    public int hashCode()
    {
        return list.hashCode();
    }
    public String toString()
    {
        return list.toString();
    }
    
    private void sortThis()
    {
        Collections.sort(list, comparator);
    }
}

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.


Descargar

DescripciónNombretamaño
Sample code for this articlej-5things2-src.zip10KB

Recursos

Aprender

Comentar

Comentarios

developerWorks: Ingrese

Los campos obligatorios están marcados con un asterisco (*).


¿Necesita un IBM ID?
¿Olvidó su IBM ID?


¿Olvidó su Password?
Cambie su Password

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


La primera vez que inicie sesión en developerWorks, se creará un perfil para usted. La información en su propio perfil (nombre, país/región y nombre de la empresa) se muestra al público y acompañará a cualquier contenido que publique, a menos que opte por la opción de ocultar el nombre de su empresa. Puede actualizar su cuenta de IBM en cualquier momento.

Toda la información enviada es segura.

Elija su nombre para mostrar



La primera vez que inicia sesión en developerWorks se crea un perfil para usted, teniendo que elegir un nombre para mostrar en el mismo. Este nombre acompañará el contenido que usted publique en developerWorks.

Por favor elija un nombre de 3 - 31 caracteres. Su nombre de usuario debe ser único en la comunidad developerWorks y debe ser distinto a su dirección de email por motivos de privacidad.

Los campos obligatorios están marcados con un asterisco (*).

(Por favor elija un nombre de 3 - 31 caracteres.)

Al hacer clic en Enviar, usted está de acuerdo con los términos y condiciones de developerWorks.

 


Toda la información enviada es segura.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=90
Zone=tecnologia Java
ArticleID=847013
ArticleTitle=5 cosas que usted no sabía sobre...la API Java Collections, Parte 1
publish-date=11262012