Trabajando con colecciones Autor: Ing. Mariela Gutierrez Trabajando con colecciones Trabajando con colecciones Una colección es un objeto que representa un conjunto de objetos. Las colecciones nos permiten modelar relaciones uno a muchos (1..n). La jerarquía de colecciones La interface Collection Tiene métodos comunes a todas las colecciones que nos permiten: Agregar un elemento Quitar un elemento Averiguar si un elemento está contenido en la colección Averiguar la cantidad de elementos de la colección etc. Algunas observaciones sobre las colecciones: 1. Collection es una interface, no una clase, por lo tanto no puede instanciarse. Trabajando con colecciones 2. Todos los métodos de Collection reciben por parámetro y retornan Object, por lo que al sacar un elemento de la colección será necesario castearlo para trabajar con él. 3. Usar Collection no garantiza el orden. 4. Para recorrer una colección será necesario utilizar su iterator. Los iterators reciben los mensajes: boolean hasNext(). Que retorna true si existe el siguiente elemento. Object next(). Retorna el siguiente elemento Iterator it = this.getEmpleados().iterator(); while ( it.hasNext() ) { Empleado empl = (Empleado) it.next(); System.out.println( epl.getNombre() ); } Otras colecciones Interface List Una lista es una colección indexada por un número que representa la posición. Trabaja como una pila: se agregan elementos al final y se elimina siempre el primer elemento. Interface Set Un set Collection sin elementos repetidos implementada por HashSet y TreeSet. Set no agrega nada a la interface Collection, pero el contrato se modifica (me asegura que mi colección no va a tener elementos duplicados). Igualdad / Identidad Las implementaciones de Collection necesitan poder comparar dos elementos por igualdad, por ejemplo, para saber si un elemento está en una colección (método contains). Las implementaciones de Set validan duplicados basándose en la igualdad de sus elementos. Dos objetos son idénticos cuando son el mismo objeto. Si dos variables a y b referencian al mismo objeto entonces a == b se cumple. Por defecto, dos objetos son iguales cuando son el mismo objeto, pero nosotros podemos cambiar la implementación del método equals para ciertos objetos del dominio, de manera que Trabajando con colecciones si tengo dos personas con el mismo número de documento, quizás me interese tratarlas como personas “iguales”: public boolean equals(Object objeto) { if(! objeto instanceof Persona) { return false; } Persona persona = (Persona) objeto; return this.getDocumento().equals(persona.getDocumento()); } De la misma manera conviene redefinir los métodos: toString( ) para visualizar información representativa de un objeto, y hashCode( ) un número (clave hash) que devuelve cada objeto, que sigue las siguientes reglas: si una clase redefine equals, también debe redefinir el hashCode; si dos objetos son iguales, deben devolver el mismo hashCode, por lo que el hashCode no sirve para identificar unívocamente un objeto: si un campo no se usa para comparar igualdad, tampoco debe ser usado en el método hashCode. Un ejemplo de redefinición de hashCode: //Clase Persona public Documento getDocumento(){ return this.documento; } public int hashCode(){ return this.getDocumento().hashCode(); } Si el equals se define en base al documento, es importante que el hashCode también. Comparando objetos La interfaz Comparable nos permite relacionar objetos para poder darles un ordenamiento natural. Si por ejemplo decimos que las personas implementan la interfaz Comparable esto Trabajando con colecciones significa que debemos implementar el método compareTo. El contrato de este método nos dice que debe devolver: un número negativo si el objeto receptor es menor que el objeto recibido como argumento un número positivo si el objeto receptor es mayor que el objeto recibido como argumento, o cero si ambos son iguales. Para comparar dos personas por edad: public int compareTo(Object objeto){ Persona persona = (Persona) objeto; this.compararPorEdad(persona); } private int compararPorEdad(Persona persona) { if ( persona.getEdad() == this.getEdad() ) return 0; if( persona.getEdad() > this.getEdad() ) { return -1; } else { return 1; } } public int getEdad(){ return this.edad; } El mayor problema de esta forma de comparación es que nos permite una única forma de comparar personas. ¿Y si ahora necesito comparar las personas en forma alfabética por apellido? La interface Comparator me permite definir más formas de ordenamiento. public class ComparadorPersonasPorEdad implements Comparator { public int compare(Object obj1, Object obj2) { per1 = (Persona) obj1; Trabajando con colecciones per2 = (Persona) obj2; if( per2.getEdad() == per1.getEdad() ) return 0; if( per2.getEdad() > per1.getEdad() ) { return -1; } else { return 1; } } } public class ComparadorPersonasPorNombre implements Comparator { public int compare(Object obj1, Object obj2) { per1 = (Persona) obj1; per2 = (Persona) obj2; return per1.getApellido().compareTo(per2. getApellido()); } } Ordenando colecciones En Java, la mayoría de las utilidades básicas ya están incluidas en la máquina virtual. Existen clases que agrupan varias operaciones útiles de un tópico particular. En este caso vamos a trabajar con la clase java.util.Collections. Esta clase contiene muchos métodos de clase o estáticos, para trabajar con colecciones. Por supuesto esta clase tiene un método para ordenar colecciones de elementos, y es eso lo que nos ocupa ahora. static void sort(List list) Según la documentación: Sorts the specified list into ascending order, according to the natural ordering of its elements. Este método puede ordenar cualquier colección que implemente la interface java.util.List, y ese orden natural se refiere al orden que indica el método compareTo de la interface Comparable. Trabajando con colecciones Pero, ¿qué hacemos si queremos ordenar las personas también por orden alfabético? En la clase Collections existe el método: static void sort(List list, Comparator c) Sorts the specified list according to the order induced by the specified comparator. ¿Qué es el segundo parámetro del método? java.util.Comparator es otra interface que se usa para comparar objetos, y que pide a las clases que la implementan los siguientes métodos: int compare(Object o1, Object o2) Compares its two arguments for order. boolean equals(Object obj) Indicates whether some other object is "equal to" this Comparator. El método equals( Object obj ) no nos interesa, es el que como en la clase java.lang.Object determina si dos objetos son el mismo o no, el que nos interesa es el otro, compare(Object o1, Object o1). El funcionamiento es el mismo que el visto anteriormente en el interface comparable, solo que en esta ocasión se pasan los dos objetos a comparar como argumentos. El resultado será: negativo si o1 < o2 cero si o1 = o2 positivo si o1 > o2 Podríamos hacer que nuestras clases implementasen java.util.Comparator, pero entonces no habríamos avanzado mucho desde el ejemplo anterior, es decir, estaríamos limitados a un orden, por lo que lo normal será escribir distintas clases que implementen este interface para después usar la que necesitemos en cada momento. Para ordenar nuestra clase Persona escribiremos dos clases, una para ordenarlos por apellido, y otra por edad. import java.util.Comparator; Trabajando con colecciones class NombreComparator implements Comparator { public int compare(Object o1, Object o2) { Persona per1 = (Persona) o1; Persona per2 = (Persona) o2; return per1.getNombre().compareTo(per2.getNombre()); } public boolean equals(Object o) { return this == o; } } import java.util.Comparator; class EdadComparator implements Comparator { public int compare(Object o1, Object o2) { Persona per1 = (Persona) o1; Persona per2 = (Persona) o2; return per1.getEdad() - per2.getEdad(); } public boolean equals(Object o) { return this == o; } } Ahora sólo nos queda usarlos: Collections.sort(lista, new NombreComparator()); Collections.sort(lista, new EdadComparator()); Otros métodos útiles de la clase Java.util.Collections static Object max(Collection coll) Returns the maximum element of the given collection, according to the natural ordering of its elements. Trabajando con colecciones static Object max(Collection coll, Comparator comp) Returns the maximum element of the given collection, according to the order induced by the specified comparator. static Object min(Collection coll) Returns the minimum element of the given collection, according to the natural ordering of its elements. static Object min(Collection coll, Comparator comp) Returns the minimum element of the given collection, according to the order induced by the specified comparator. Para mayor información sobre estos y otros métodos de la clase Collections, ver http://download.oracle.com/javase/1.4.2/docs/api/java/util/Collections.html. La biblioteca Commons-Collections ¿Qué es una biblioteca o library? En la mayoría de los sistemas operativos actuales, se ofrece una cantidad de código para simplificar la tarea de programación. Este código toma la forma, normalmente, de un conjunto de bibliotecas dinámicas que las aplicaciones pueden llamar cuando lo necesiten. Pero la Plataforma Java está pensada para ser independiente del sistema operativo subyacente, por lo que las aplicaciones no pueden apoyarse en funciones dependientes de cada sistema en concreto. Lo que hace la Plataforma Java, es ofrecer un conjunto de bibliotecas estándar, que contiene mucha de las funciones reutilizables disponibles en los sistemas operativos actuales. Las bibliotecas de Java tienen tres propósitos dentro de la Plataforma Java.: 1. Al igual que otras bibliotecas estándar, ofrecen al programador un conjunto bien definido de funciones para realizar tareas comunes, como manejar listas de elementos u operar de forma sofisticada sobre cadenas de caracteres (Strings). 2. Además, las bibliotecas proporcionan una interfaz abstracta para tareas que son altamente dependientes del hardware de la plataforma destino y de su sistema operativo. Tareas tales como manejo de las funciones de red o acceso a archivos, suelen depender fuertemente de la funcionalidad nativa de la plataforma destino. En el caso concreto anterior, las bibliotecas java.net y java.io implementan el código nativo internamente, y ofrecen una interfaz estándar para que aplicaciones Java puedan ejecutar tales funciones. Trabajando con colecciones 3. Finalmente, no todas las plataformas soportan todas las funciones que una aplicación Java espera. En estos casos, las bibliotecas bien pueden emular esas funciones usando lo que esté disponible, o bien ofrecer un mecanismo para comprobar si una funcionalidad concreta está presente. En general, las bibliotecas tienen la extensión .jar y se agregan al proyecto en el classpath. En el lenguaje de programación Java se entiende por Classpath una opción admitida en la línea de órdenes o mediante variable de entorno que indica a la Máquina Virtual de Java dónde buscar paquetes y clases definidas por el usuario a la hora de ejecutar programas. La biblioteca commons-collections pertenece al conjunto de bibliotecas del proyecto Apache Software Foundation (http://commons.apache.org/). El proyecto Commons de Apache Software Foundation El objetivo de Commons es proporcionar componentes Java reutilizables y de código abierto. Es un lugar para la colaboración y participación, donde los desarrolladores de la comunidad Apache trabajan juntos en proyectos que serán utilizados por otros proyectos de Apache y por los usuarios de Apache. Los desarrolladores harán un esfuerzo para asegurar que sus componentes tienen un mínimo de dependencias en otras bibliotecas de software , por lo que estos componentes se pueden implementar fácilmente. Además, los componentes mantendrán sus interfaces lo más estable posible, para que los usuarios de Apache, así como otros proyectos de Apache, puede aplicar estos componentes sin tener que preocuparse por los cambios en el futuro. La biblioteca commons-collections puede descargarse desde http://commons.apache.org/collections/download_collections.cgi. Algunos métodos útiles de Commons-Collections Los métodos que nos interesan en este momento se encuentran en la clase CollectionUtils: Método Collect static Collection collect(Collection inputCollection, Transformer transformer) Este método retorna una colección donde cada elemento es el elemento correspondiente en la colección parámetro transformado por el Transformer dado. Trabajando con colecciones ¿Qué es un Transformer? Transformer es una interface cuyo contrato tiene un único método: java.lang.Object transform(java.lang.Object input), que transforma un objeto. Un ejemplo, aumentar un 20% el sueldo a todos los empleados de la compañía: Como Transformer es una interfaz, podemos implementarla a través de una clase anónima (ya incluida en la biblioteca, definiendo el método transform() que forma parte del contrato. La conveniencia de utilizar una clase anónima se da cuando quiero definir un comportamiento muy específico que no amerita tener un class porque ese comportamiento sólo tiene sentido en ese contexto. public void aumentarSueldo() { Collection nuevaColeccionDeEmpleados = CollectionUtils.collect(this.getEmpleados(), new Transformer() { public Object transform(Object arg0) { Empleado e = (Empleado)arg0; //Casteo al Object a Empleado e.setSueldo( e.getSueldo() * 1.2f); //Sumo un 20% al sueldo de todos los empleados return e; } }); this.setEmpleados(nuevaColeccionDeEmpleados); } Existe un método que tiene la misma funcionalidad pero modificando la misma colección: static void transform(java.util.Collection collection, Transformer transformer) public void aumentarSueldo() { CollectionUtils.collect(this.getEmpleados(), new Transformer() { public Object transform(Object arg0) { Empleado e = (Empleado)arg0; //Casteo al Object a Empleado e.setSueldo( e.getSueldo() * 1.2f); //Sumo un 20% al sueldo de todos los empleados return e; Trabajando con colecciones } }); } Método Select static Collection select(Collection inputCollection, Predicate predicate) Este método devuelve una nueva colección sólo con los elementos que cumplen la condición dada por el Predicate indicado. ¿Qué es un Predicate? Es una interface cuyo contrato es el siguiente: boolean evaluate(java.lang.Object object) Un ejemplo es seleccionar aquellos empleados que cobran un sueldo mayor que $8000. public void empleadosConMejorSueldo() { Collection mejoresPagados = CollectionUtils.select(this.getEmpleados(), new Predicate() { public boolean evaluate(Object arg0) { Empleado e = (Empleado)arg0; //Casteo al Object a Empleado return e.getSueldo() > 8000; //Evalúo si es mayor a 8000 } }); } Existe un método posee la misma funcionalidad pero modificando la misma colección: static void filter(java.util.Collection collection, Predicate predicate) Método forAllDo static void forAllDo(java.util.Collection collection, Closure closure) Ejecuta el Closure dado a cada elemento de la colección. ¿Qué es un Closure? Es una interface cuyo contrato es el siguiente: Trabajando con colecciones void execute(java.lang.Object input) Por ejemplo, imprimir la nómina de empleados: public void nominaEmpleados() { CollectionUtils.select(this.getEmpleados(), new Predicate() { public void execute(Object arg0) { Empleado e = (Empleado)arg0; //Casteo al Object a Empleado System.out.println( e.getNombre( ) ); //Imprimo el nombre del empleado } }); } Para mayor información sobre los métodos de la clase CollectionUtils y de otras clases de la biblioteca commons-collections, ver http://commons.apache.org/collections/apidocs/index.html.