Download import Java.util.
Document related concepts
no text concepts found
Transcript
Capítulo 7 Índice 1. Objetivo de Certificación 6.2 - Sobrescribiendo hashCode() y equals() 1.1. Método toString() 1.2. Método equal() 1.3. Significado de no sobrescribir equals() 1.4. Implementando un método equals() 1.5. Contrato equals() 1.6. Sobrescribiendo hashCode 1.7. Entendiendo los HashCodes 1.8. Implementando hashCode() 1.9. El contrato hashCode() 2. Objetivo de Certificación 6.1 - Colecciones 2.1. Que hacer con una colección 2.2. Interfaces y clases del framework Collection 3. Objetivo de Certificación 6.5 - Usando el framework Collections 3.1. ArrayList Básicos 3.2. Autoboxing con Collections 3.3. Clasificando (sorting) Collections y Arrays 3.4. Sorting Collections 3.5. Interfaz Comparable 3.6. Sorting con Comparator 3.7. Sorting con la clase Arrays 3.8. Buscando en Arrays y Collections 3.9. Convirtiendo Arrays a Listas (y viceversa) 3.10. Usando Lists 3.11. Usando Sets 3.12. Usando Maps 3.13. Usando la clase PriorityQueue 3.14. Descripción Método para Arrays y Colecciones 3.15. Descripción Método para List, Set, Map, y Queue 4. Objetivo de Certificación 6.3 y 6.4 - Tipos genéricos 4.1. Manera legal de hacer colecciones 4.2. Genéricos y el legado de código 4.3. Mezclando colecciones genéricas y no genéricas 4.4. Polimorfismo y Genéricos 4.5. Métodos genéricos 4.6. Declaraciones genérico 4.7. Creando tu propia clase genérica 4.8. Creación de métodos genéricos 1. Objetivo de Certificación 6.2 Sobrescribiendo hashCode() y equals() 6.2 Distinguir entre redefiniciones correctas e incorrectas de los métodos hashCode y equals, y explicar la diferencia entre "==" y el método equal. Para el examen no necesitamos sabernos todos los métodos de un objeto, pero necesitaremos saber sobre los métodos que veremos a continuación en la siguiente tabla. Método Descripción boolean Decide si dos objetos son equals equivalentes de manera significativa. (Object obj) Llamado por garbage collector void cuando se da cuenta de que el objeto finalize() no puede ser referenciado. Devuelve un valor hashcode entero para un objeto, así que el objeto int puede ser usado en clases Collection hashcode() que use hashing, incluyendo Hashtable, HashMap y HashSet. final void Se despierta un hilo que está a la notify() espera de este objeto de bloqueo. Se despiertan todos los hilos que final void están a la espera de este objeto de notifyAll () bloqueo. Hace que el hilo actual tenga que final void esperar hasta que otro hilo llame a wait() notify() o notifyAll(). String Devuelve una representación en texto toString() de un objeto En el Capítulo 9 se estudia wait(), notify() y notifyAll(). El método finalize() lo hemos visto en el Capítulo 3. Así que en esta sección nos centraremos en los métodos toString(), hashCode() y equals(). 1.1. Método toString() Sobrescribir toString() cuando quieres simplemente ser capaz de leer algo significativo acerca de los objetos de tu clase. Un código llama al método toString() de tu objeto cuando quiere leer detalles útiles sobre tu objeto. Cuando tu pasas la referencia a un objeto al método System.out.println, por ejemplo, el método toString del objeto es llamado. Veamos un ejemplo: public class HardToRead { public static void main (String [] args) { HardToRead h = new HardToRead(); System.out.println(h); } } Ejecutando la clase HardToRead obtenemos: % Java HardToRead HardToRead@a47e0 La salida anterior es la que obtienes cuando no sobrescribes el método toString() de la clase Object. Como vemos la salida es el nombre de la clase seguida del símbolo @ y seguido de la representación hexadecimal sin signo del hashCode del objeto. Viendo esta salida deberías sobrescribir el método toString() en tus clases, por ejemplo: public class BobTest { public static void main (String[] args) { Bob f = new Bob("GoBobGo", 19); System.out.println(f); } } class Bob { int shoeSize; String nickName; Bob(String nickName, int shoeSize) { this.shoeSize = shoeSize; this.nickName = nickName; } public String toString() { return ("I am a Bob, but you can call me " + nickName + ". My shoe size is " + shoeSize); } } Esto es un poco mas legible: % Java BobTest I am a Bob, but you can call me GoBobGo. My shoe size is 19 1.2. Método equal() Aprendimos algo sobre el método equal() en capítulos anteriores, cuando vimos las clases de envoltura. Discutimos cómo comparar dos referencias a objeto utilizando el operador == evaluando a verdadero sólo cuando ambas referencias se refieren al mismo objeto. Vimos que la clase String y las clases de envoltura tienen sobrescrito los métodos equal() (heredado de la clase Object), de modo que podíamos comparar dos objetos diferentes (del mismo tipo) para ver si su contenido es equivalente de manera significativa. Cuando realmente necesitas saber si dos referencias son idénticas, usa "==". Pero si no queremos comparar las referencias si no los objetos en si, usa el método equal(). Para cada clase que escribas, debes decidir si tiene sentido comparar dos instancias iguales. Para algunas clases, podrías decidir que dos objetos nunca pueden ser iguales. Por ejemplo, imagina una clase Car que tiene variables de instancia como modelo, año, configuración (realmente no quieres que dos Car con iguales atributos sean tratados como el mismo). Si dos referencias hacen referencia del mismo Car, entonces sabrás que ambas hablan del mismo coche, y no de dos coches con los mismos atributos.Así que en el caso de Car es posible que no puedas necesitar, o querer sobrescribir el método equals(). De acuerdo, sabrás que no es el fin de la historia. 1.3. Significado de no sobrescribir equals() Si no sobrescribes los métodos equals() de una clase, no seras capaz de usar estos objetos como clave en un hashtable y probablemente no obtendrás conjuntos exactos, de tal manera que no hay concepto duplicados. El método equals() en la clase Object usa solo el operador "==" para las comparaciones, así que a menos que sobrescribas equals(), dos objetos serán considerados iguales solo si las dos referencias se refieren al mismo objeto. Veamos que significa que no se pueda usar un objeto como clave de un hashtable. Imagínate que tienes un Car, un Car especifico que quieres meter en un HashMap (tipo de hashtable que veremos mas adelante en este capítulo), de tal forma que puedas buscar un coche en particular y recibir el objeto Person que representa al propietario. . Así que añades la instancia Car como clave del HashMap (junto con un objeto Person que representa el valor). Pero ¿que sucede cuando quieres hacer una búsqueda?, querrás decirle a la colección HashMap, "aquí esta el coche, ahora dame el objeto Person que corresponde a dicho coche". Pero ahora estás en un problemas a menos que todavía tengas una referencia al objeto exacto que ha utilizado como clave cuando se añadió a la colección. En otras palabras, no puedes crear un objeto Car idéntico y usarlo para la búsqueda. La conclusión es: si quieres que los objetos de tu clase sean usados como claves para un hashtable (o como elemento en cualquier estructura de datos que use equivalencias para buscar y/o recuperar un objeto, entonces debes sobrescribir equals() de tal forma que dos instancias diferentes puedan ser consideradas la misma. Así que para el objeto Car deberíamos sobrescribir el método equals() para que se compare el valor único VIN (Número de identificación del vehículo) como la comparación base. De esta forma, puedes usar una instancia cuando añadas el Car a la colección y recrear una instancia idéntica cuando quieras hacer una búsqueda del objeto como clave de un hashtable. Sobrescribiendo el método equals() para Car también te permite la posibilidad de que más de un objeto representen a un único coche, que podría no ser seguro en tu diseño. Afortunadamente, el String y las clases de envoltura trabajan bien como claves en hashtable, ya que tienen el método equals() sobrescrito. Así que en lugar de utilizar la actual instancia de Car como la clave en el par Car/Propietario, podrías simplemente usar un String que represente el identificador único para el Car. De esta forma, nunca tendrás más de una instancia representando un coche especifico, pero podrás usar aún el coche, o al menos uno de los atributos del coche, como la clave de búsqueda. 1.4. Implementando un método equals() Vamos a decir que sobrescribes el método equals en tu clase, debería ser como este: public class EqualsTest { public static void main (String [] args) { Moof one = new Moof(8); Moof two = new Moof(8); if (one.equals(two)) { System.out.println("one and two are equal"); } } } class Moof { private int moofValue; Moof(int val) { moofValue = val; } public int getMoofValue() { return moofValue; } public boolean equals(Object o) { if ((o instanceof Moof) && (((Moof)o).getMoofValue() == this.moofValue)) { return true; } else { return false; } } } Veamos este código en detalle. En el método main() de EqualTest, crearemos dos instancias Moof, pasándole al constructor el mismo valor 8. Viendo al constructor de la clase Moof, lo que hace es asignar el valor a la variable de instancia moofValue. Ahora imagínate que decides que dos objetos Moof son iguales si tienen el mismo moofValue. Así que sobrescribes el método equals() y comparas los moofValues. Es así de sencillo. Pero vamos a desglosar que sucede en el método equals(). 1. public boolean equals(Object o) { 2. if ((o instanceof Moof) && (((Moof)o).getMoofValue()== this.moofValue)) { 3. return true; 4. } else { 5. return false; 6. } 7. } Lo primero de todo, debes observar todas las normas de sobrescritura y en la linea 1 de hecho estamos declarando una valida redefinición del método equals() que heredamos de Object. En la linea 2 es donde esta toda la acción. Lógicamente, debemos hacer dos cosas en orden para hacer una valida comparación de igualdad. Primero, asegurarnos que el objeto que estamos poniendo a prueba es del tipo correcto!. Se presenta polimorficamente como tipo Object, así que necesitamos hacer una prueba con instanceof. Ademas, todavía tienes que hacer el instanceof para estar seguro que podrías hacer el casting al argumento para el tipo correcto de tal forma que puedas acceder a sus método o variables en orden para hacer la comparación. Recordar, si el objeto no pasa el test instanceof, entonces obtendrás una excepción en tiempo de ejecución ClassCastException. Por ejemplo: public boolean equals(Object o) { if (((Moof) o).getMoofValue() == this.moofValue){ // the preceding line compiles, but it's BAD! return true; } else { return false; } } El casting (Moof)o fallara si "o" no hace referencia a algo que pasa el test IS-A Moof. Segundo, comparar los atributos que nos preocupan (en este caso moofValue). Sólo el desarrollador puede decidir lo que hace que dos instancias sean iguales. Hacemos el casting para indicarle al compilador que el método que queremos usar no es de Object si no de la clase Moof en particular. Si no hiciésemos el casting no podríamos compilar porque el compilador vería al objeto referenciado como un Object y Object no tiene el método getMoofValue(). Pero como dijimos anteriormente, solo con el casting también falla en tiempo de ejecución si el objeto referenciado por "o" no es casteable a Moof. Así que no olvides usar el la primera prueba instanceof. Aquí apreciamos el operador &&, si la prueba instanceof falla, nunca haremos el casting, de tal forma que nos aseguraremos que no haya fallo en tiempo de ejecución: if ((o instanceof Moof) && (((Moof)o).getMoofValue() == this.moofValue)) { return true; } else { return false; } Si nos fijamos en la especificación de la API de Java de la clase Object, encontraras que el método equals() es llamado un contrato especificado. Un contrato en Java es un conjunto de reglas que deberían ser seguidas si quieres proporcionar una implementación correcta como los demás esperan que sea. O ponerlo de otra forma, sin seguir el contrato, tu código todavía puede compilar y ejecutar, pero tu código puede romperse en tiempo de ejecución de alguna forma inesperada. Exam Watch 1.5. Contrato equals() Siguiendo estrictamente la documentación de Java, el contrato de equals() dice: Es reflexivo. Para cualquier referencia por ejemplo x, x.equals(x) debería devolver true. Es simétrico. Para cualquier par de referencias, por ejemplo x e y, si x.equals(y) es true si y solo si y.equals(x) devuelve true. Es transtivo. Para cualquier conjunto de referencias, por ejemplo 'x', 'y' y 'z', si x.equals(y) devuelve true y para y.equals(z) devuelve true, entonces z.equals(x) debe devolver true. Es consistente. Para cualquier pareja de referencias, por ejemplo x e y, hacer x.equals(y) consistentemente devuelve true o consistentemente devuelve false, ninguna información usada en comparaciones equals modifican al objeto. Para cualquier referencia no nula, por ejemplo x, x.equals(null) debe devolver false. No hemos visto el método hashCode(), pero equals() y hashCode están unidos por un contrato que especifica que si dos objetos son considerados iguales usando equals(), entonces deben obtener igual resultado con hashCode. Por lo tanto para estar absolutamente seguros, debes tener una regla, si sobrescribes equals(), sobrescribe hashCode también. Así que veamos hashCode. 1.6. Sobrescribiendo hashCode HashCode es usado normalmente para incrementar el rendimiento de las grandes colecciones de datos. El valor hashCode de un objeto es usado por algunas clases de colecciones. Aunque puedes pensar de esto que es un tipo de numero identificador (ID) de un objeto, no es necesariamente único. Las colecciones como HashMap y HashSet usan el valor hashCode de un objeto para determinar como debe ser almacenado en la colección y también sirve para localizar al objeto en la colección. Para el examen no necesitas conocer en profundidad detalles de como las clases Collection que usan "hashing" son implementadas, pero necesitas saber que colecciones lo usan (pero, todos tienen "hash" en el nombre). Debes también ser capaz de reconocer una adecuada o correcta implementación de hashCode(). Esto no significa ni legal ni eficiente. Es perfectamente legal tener un terrible método hashCode ineficiente en tu clase, siempre que no viole el contrato que se especifica en la documentación de la clase Object. Así que para el examen, si se te pide que selecciones un uso apropiado o correcto de hashCode, no pienses en legal o eficiente. 1.7. Entendiendo los HashCodes Para entender que es apropiado y correcto, tenemos que ver como usan algunas de las colecciones los hashCodes. Imagínate un conjunto de cubos alineados en el suelo. Tienen un trozo de papel con el nombre propio y coges el nombre y calculas un código entero para cada letra, por ejemplo A es 1, B es 2 y así sucesivamente añadiendo los valores numéricos a todas las letras. Un nombre propio siempre resulta el mismo código, veamos la siguiente figura. No introducimos nada al azar, simplemente tenemos un algoritmo que siempre se ejecuta de la misma manera dada una entrada especifica, la salida siempre sera idéntica para cualquier par de entradas idénticas. Ahora veremos la manera de usar este código para determinar cual es el cubo al que le tengo que colocar el papel (imagínate que cada cubo representa un código numérico diferente). Ahora imagina que alguien viene, te muestra un nombre y dice, "Por favor, recupere el pedazo de papel que coincida con este nombre." Así que tu buscas el nombre que te muestran y ejecutas el mismo algoritmo de generación de hashCode. El HashCode te dirá en que cubo deberías encontrar el nombre. Es posible que hayas encontrado algún fallo en nuestro sistema. Dos nombres diferentes podrían obtener el mismo resultado al aplicar el algoritmo. Por ejemplo, los nombre Amy y May tienen las mismas letras, así que el hashCode sera idéntico para ambos. Esto es aceptable, pero significa que cuando alguien te pregunte por la pieza de papel de Amy, todavía tienes que buscar a través de los cubos de lectura cada nombre hasta que nos encontramos con Amy en lugar de May. El hashCode solo te dice a que cubo debes ir, pero no como localizar el nombre una vez estés en el cubo. Exam Watch Por lo tanto, para la eficiencia, su objetivo es tener los papeles distribuidos uniformemente como sea posible en todos los cubos. Idealmente, puede ser que sólo tenga un nombre por cubo de manera que cuando alguien pida un documento se puede calcular simplemente el hashCode y sólo agarrar un papel del cubo correcto (sin tener que ir buscando a través de diferentes papeles en el cubo hasta que usted localice el exacto que está buscando). Los menos eficientes (pero aún funcionales) generadores de hashCode devolverían el mismo hashCode independientemente del nombre, a fin de que todos los papeles aterrizen en el mismo cubo, mientras que los otros estan vacíos. The bucket-clerk would have to keep going to that one bucket and flipping painfully through each one of the names in the bucket until the right one was found. And if that's how it works, they might as well not use the hashcodes at all but just go to the one big bucket and start from one end and look through each paper until they find the one they want. Este ejemplo de distribución a través de los cubos es similar a la manera en que se utilizan los hashcodes en las colecciones. Al poner un objeto en una colección que utiliza hashcodes, la colección utiliza el hashCode del objeto para decidir en qué cubo/ranura el objeto debe estar. Luego, cuando quieras buscar ese objeto (o, para un hashtable, recuperar el valor asociado con ese objeto), tienes que darle a la colección una referencia a un objeto que la colección compare con los objetos que posea. Mientras que el objeto (almacenado en la colección, al igual que un papel en el cubo) que estas intentando buscar tenga el mismo hashCode que el objeto que estas utilizando para la búsqueda , entonces el objeto se encontrara. Pero… y este es un Big One, imaginar qué pasaría si, volviendo a nuestro ejemplo de nombres, le enseño el cubo de trabajo un nombre y el código calculado sobre la mitad de las letras en el nombre en lugar de todos ellos. Ellos nunca encontrarían el nombre en el cubo porque no se buscaría en el cubo correcto! Ahora se puede ver por qué si dos objetos se consideran iguales, su hashcodes también debe ser igual? De lo contrario, nunca seras capaz de encontrar el objeto desde el método hashCode por defecto de la clase Object casi siempre viene con un número único para cada objeto, incluso si el método equals()es sobrescrito de tal manera que dos o más objetos se consideran iguales. No importa la igualdad de los objetos si sus hashcodes no reflejan esa igualdad. Por lo tanto, una vez más: si dos objetos son iguales, sus hashcodes debe ser iguales también. 1.8. Implementando hashCode() ¿Que comprobación hace realmente un algoritmo de hashCode? La gente tiene sus tesis sobre los algoritmos hash, desde el punto de vista de la informática, esto va más allá del alcance del examen. La parte que nos interesa aquí es la cuestión de si sigues el contrato. Y para seguir el contrato, piensa lo que haces en el método equals(). Tu comparas atributos. Debido a que la comparación casi siempre implica a los valores de las variables (Recuerda cuando vimos a dos objetos Moof y los considerabas iguales si sus variables int moofValues eran las mismas?). Tu implementación de hashCode () debe utilizar la misma variables. He aquí un ejemplo: class HasHash { public int x; HasHash(int xVal) { x = xval; } public boolean equals(Object o) { HasHash h = (HasHash) o; // Don't try at home without // instanceof test if (h.x == this.x) { return true; } else { return false; } } public int hashCode() { return (x * 17) ; } } Este método equals() dice que dos objetos son iguales si tienen el mismo valor x, así que los objetos con el mismo valor x tendrán que devolver idéntico hashcodes. Exam Watch Normalmente, veras que los métodos hashCode() hacen alguna combinación de XOR-ing con una variable instancia de la clase, junto con alguna multiplicación de un número primo. En cualquier caso, mientras que la meta es obtener una distribución amplia y aleatoria de los objetos, el contrato requiere solo que dos objetos iguales tengan idéntico hashCode. El examen no pretende hacer métodos hashCode() eficientes, pero si debe reconocer si funcionara o no funcionara (es decir, que el objeto sea encontrado en una colección hash). Ahora que sabemos que dos objetos iguales deben tener idéntico hashCode, ¿es cierto lo contrario?. ¿Dos objetos con idéntico hashcode tienen que ser considerados iguales?. Piénsalo, podrías tener multitud de objetos en el mismo cubo porque sus hashcodes son idénticos, pero a menos que ellos pasen la prueba equals(), no van a ser encontrados en la colección tan fácilmente. Esto seria el resultado de una implementación ineficiente. Es legal y correcto. pero lentoooo. Condición Requerido x.equals(y) == true x.hashCode() == y.hashCode() x.equals(y) == false x.hashCode() =! y.hashCode() x.hashCode() == y.hashCode() No requerido (pero permitido) x.equals(y) == true No requisitos hashCode() x.equals(y) == false 1.9. El contrato hashCode() Ahora viendo la fabulosa documentación de la API de Java para la clase Object, podemos presentar el contrato de hashCode(): Siempre que es invocado con el mismo objeto más de una vez durante la ejecución de una aplicación Java, el hashCode () debe devolver constantemente el mismo número entero, siempre y cuando la información utilizada para las comparaciones equals() no modifiquen el objeto. Este entero no es necesario que mantenga la coherencia de la ejecución en una aplicación a otra ejecución de la misma aplicación. Si dos objetos son iguales de acuerdo con el método equals(java.lang.Object), entonces se pide que el método hashCode() en cada uno de los dos objetos debe devolver el mismo resultado entero. No es necesario que si dos objetos son desiguales en función del método equals(Java.lang.Object), entonces se pide que hashCode () en cada uno de los dos objetos debe devolver como resultado dos enteros distintos. Sin embargo, el programador debe ser consciente de que la producción de distintos resultados enteros para los objetos puede mejorar el rendimiento de tablas hash. Y lo que esto significa para ti es ... Por lo tanto, echemos un vistazo a ¿qué otra cosa podría causar un método hashCode () falle. ¿Qué ocurre si se incluyera una variable transient en tu método hashCode ()? Si bien eso es legal (el compilador no se queja), en algunas circunstancias, un objeto que puso en una colección no se encontrara. Como sabemos, la serialización guarda un objeto para que pueda ser reanimado después de la desserailización de vuelta a la plena objectness. Pero peligro, recordar que las variables transient no se guardan cuando un objeto es serializado. Un mal escenario puede tener un aspecto como este: class SaveMe implements Serializable{ transient int x; int y; SaveMe(int xVal, int yVal) { x = xVal; y = yval; } public int hashCode() { return (x ^ y); // Legal, but not correct to // use a transient variable } public boolean equals(Object o) { SaveMe test = (SaveMe)o; if (test.y == y && test.x == x) { // Legal, not correct return true; } else { return false; } } } Esto es lo que podría suceder utilizando código como el ejemplo anterior: 1. Obtener algún estado de un objeto (asignar valores a sus variables de instancia). 2. Colocar el objeto en un HashMap, usando el objeto como un elemento clave. 3. Guardar el objeto a un archivo utilizando serialización sin alterar cualquiera de sus estado. 4. Recuperar el objeto del fichero a través de la desserialización. 5. Utilizar el objeto desserializado (traído de vuelta a la vida en el heap) para obtener el objeto del HashMap. Vaya. El objeto de la colección y el mismo objeto, supuestamente cuando vuelven a la vida ya no son idénticos. El objeto de la variable transient volverá con un valor por defecto y no con el valor que tenia la variable en el momento en que se salvó (o puesto en el HashMap). Por lo tanto, utilizando el código anterior SaveMe, si el valor de x es 9, cuando el caso se pone en el HashMap, desde entonces x se utiliza para el cálculo de la hashCode, cuando el valor de x cambie, el hashcode también cambia. Y cuando ese mismo ejemplo de SaveMe es traído de vuelta de desserialización, x == 0, independientemente del valor de x en el momento en que el objeto era serializado. Por lo tanto, el nuevo cálculo de hashCode dará un hashCode diferente, y el equals () fallara también ya que el método equals() usa x para determinar la igualdad de un objeto. Amazon.com debe tener miles de libros de algoritmos que tu puedes comprar. Pero con el tipo de programadores organizados que hay en la actualidad, es casi demasiado doloroso para considerar. El Framework Collections en Java, que tomó forma con la puesta en marcha de JDK 1,2 y fue expandido en la 1.4 y otra vez en Java 5, te da listas, conjuntos, mapas y colas para satisfacer la mayor parte de tus necesidades de codificación. Ellos han sido juzgados,probados y ajustados. Y cuando necesitas algo un poco más personalizado, el framework Collections en el paquete java.util es cargado con las interfaces y las utilidades. 2.1. Que hacer con una colección Hay unas cuantas operaciones básicas que normalmente usamos con las colecciones: En pocas palabras: las variables transient pueden realmente ser un lío con las implementaciones de equals() y hashCode(). Mantener las variables no transient o, si es que debe marcarse transient, no utilizarla entonces para determinar hashcodes o la igualdad. 2. Objetivo de Certificación 6.1 Colecciones 6.1 Dado un escenario diseñado, determinar que clases Collection y/o interfaces deberían ser usadas para una implementación apropiada al diseño, incluyendo el uso de la interfaz Comparable. ¿Puedes imaginarte escribir aplicaciones orientadas a objetos sin usar estructuras de datos hashTables o LinkedList? ¿Que tendrías que hacer cuando necesites mantener una lista ordenada de todos los miembros del club de fan de los Simpsons?. Obviamente podrías hacerlo tu mismo; Añadir un objeto a la colección. Eliminar objetos de una colección. Buscar un objeto (o grupo de ellos) en la colección. Recibir un objeto de la colección (sin eliminarlo). Iterar sobre la colección, observando los elementos (objetos) uno a uno. 2.2. Interfaces y clases del framework Collection Para el examen necesitaras saber que colección elegir basándote en un requisito declarado. Las interfaces que necesitamos saber para el examen (y para la vida en general) son las siete siguientes: Collection * Set *SortedSet List * Map *SortedMap Queue La concreta implementación de clases que necesitas saber para el examen son las 13 siguientes (hay otras, pero el examen no las cubre): Maps Sets HashMap HashSet HashTable TreeMap Utilitie s ArrayL PriorityQ Collecti ist ueue ons Lists LinkedHas Vector hSet Linked TreeSet List Queues Arrays LinkedHash Map No todas las colecciones en el framework Collection actualmente implementa la interfaz Collection. En otras palabras, no todas las colecciones pasan el test IS-A para Collection. Especialmente, ninguno de los Map clases e interfaces extiende de Collection. Así, mientras SortedMap, HashTable, HashMap, TreeMap y LinkedHashMap son todas consideradas como colecciones, ninguno en realidad extiende de Collection "con C mayúscula" (ver la siguiente figura) Para hacer la cosa un poco mas confusa, la palabra "collection" tiene sobrecargado su uso tres veces: collection (c minúscula), representa cualquiera de las estructuras de datos en las cuales los objetos son almacenados e iterados. Exam Watch Las colecciones se pueden agrupar en cuatro categorías: Lists: Listas de cosas (clases que implementan List). Sets: Cosas únicas (clases que implementan Set). Maps: Cosas que un único ID (clases que implementan Map). Queues: Las cosas organizadas por el orden en que se van a procesar. La siguiente figura ilustra la estructura de List, Set y Map Collection (C mayúscula), es actualmente la interfaz java.util.Collection de la cual extienden Set, List y Queue. (Así es ampliar, no implementar. No hay implementaciones directas de Collection.) Collections (C mayúscula y terminado en s), es la clase java.util.Collections que tiene un montón de métodos de utilidades static para usar con colecciones. Pero hay sub-categorías dentro de las categorías: Sorted *Unsorted * Ordered * Unordered Una implementación puede ser unsorted (sin clasificar) y unordered (desordenada), ordered(ordenada) pero unsorted o ordered (ordenada) y sorted(clasificada). Pero una implementación nunca puede ser sorted pero unordered, porque la clasificación (sorting) es un tipo especifico de ordenación (ordering), como veras en un momento. Por ejemplo, un HashSet es un conjunto "unordered y unsorted" mientras que LinkedHashList es un conjunto "ordered (pero no sorted)" que mantiene el orden en que los objetos son insertados. Quizás deberíamos ser explícitos acerca de la diferencia entre sorted (clasificados) y ordened (ordenados), pero primero tenemos que discutir la idea de iteración. Cuando piensas en iteración, puedes pensar en iteraciones en un array utilizando, por ejemplo, un bucle "for" para acceder a cada elemento de la matriz en orden ([0], [1], [2], y así sucesivamente). Iterar a través de una colección por lo general significa caminar a través de los elementos uno tras otro a partir del primer elemento. Aunque, a veces, incluso el concepto de primera es un poco extraño-en un Hashtable existe realmente no es un concepto de primero, segundo, tercero, y así sucesivamente. En un Hashtable, los elementos se colocan en un (aparentemente) caótico orden basado en la hashCode de la clave. Pero algo tiene que ir primero al iterar; por lo tanto, cuando usted iterar sobre un Hashtable, será de hecho un pedido. Pero en la medida de lo que usted puede decir, es completamente arbitraria y puede cambiar en formas aparentemente al azar, como la recogida de cambios. Ordered (Ordenado) Cuando una colección es ordenada, significa que puedes iterar sobre la colección en un especifico orden (no aleatorio). Una colección HashTable es no ordenada. Aunque el HashTable en si mismo tenga una lógica interna para determinar el orden (basado en hashcodes), no encontraras ningún orden cuando iteres sobre el HashTable. Un ArrayList, sin embargo, mantiene el orden establecido por el indice de posición del elemento (como un array). LinkedHashSet mantiene el orden establecido en la inserción, así que el último elemento en insertarse es el último elemento del LinKedHashSet (como oposición a un ArrayList donde puedes elegir el sitio donde insertar). Finalmente, hay algunas colecciones que mantienen un orden referido al orden natural de los elementos y las colecciones son entonces no sólo ordenas, sino también ordenadas. Veamos cómo funciona el orden natural de las colecciones ordenadas. Sorted Una colección ordenada significa que el orden en la colección es determinado en función de alguna regla o reglamento, conocido como el orden de clasificación. Un orden de clasificación no tiene nada que ver a que un objeto sea añadido a la colección o cuándo fue la última vez que se accedió a él o en qué "posición" se añadió. La clasificación se realiza basándose en las propiedades de los objetos mismos. Tu colocas los objetos en la colección, y la colección vera en qué orden ponerlos, basándonse en el orden de clasificación. Una colección que mantiene una orden (como cualquier lista, que utiliza el orden de inserción) no es realmente considerada ordenada. Por lo general, el orden de clasificación utilizado es algo que se llama el orden natural. ¿Qué significa eso? Sabemos cómo ordenar alfabéticamente-A viene antes de B, F viene antes de G, y así sucesivamente. Para una colección de objetos String, entonces, el orden natural es el alfabético. Para los objetos Integer, el orden natural es de valor numérico-1 antes de 2, y así sucesivamente. Y para objetos Foo, el orden natural es… um… no sabemos. No hay un orden natural para Foo a menos o hasta que el desarrollador de Foo proporcione, a través de una interfaz (Comparable) que defina como las instancias de una clase pueden ser comparadas entre sí. Si el desarrollador decide que los objetos Foo deben compararse utilizando el valor de alguna variable de instancia (por ejemplo, hay una llamada bar), entonces una colección ordenada ordenara los objetos Foo de acuerdo a las normas de la clase Foo sobre el uso de variable de instancia bar para determinar el orden. Por supuesto, la clase Foo también podría heredar un orden natural de una superclase en lugar de definir su propio orden, en algunos casos. Aparte del orden natural, tal como se especifica por la interfaz Comparable, también es posible definir otros, los diferentes órdenes de tipo utilizando otro interfaz: Comparator. Vamos a discutir cómo utilizar tanto Comparable como Comparator para definir el orden de clasificación más adelante en este capítulo. Pero por ahora, sólo tenga en cuenta que el orden de clasificación (incluidos orden natural) no es lo mismo que ordenar por inserción, por acceso o por índice. Ahora que sabemos acerca de ordenar y clasificar, vamos a ver cada una de las cuatro interfaces, y nosotros nos encargaremos de bucear en las implementaciones concretas de las interfaces. Interfaz List La interfaz List se preocupa por el índice. La única cosa que tiene List que los no-lists no tienen es un conjunto de métodos relacionados con el índice. Los principales métodos que incluyen son get (int index), indexOf (Object o), add(int index, Object obj), y así sucesivamente. Las tres implementaciones de List están ordenadas por índice de posición-una posición que tu determinas, ya sea mediante el establecimiento de un objeto en un determinado índice o añadiendo sin especificar la posición, en cuyo caso el objeto se añadirá al final. Las tres implementaciones de List se describen en las siguientes secciones. ArrayList Piensa en esto como un array expansible. Te permite la iteración y un rápido acceso aleatorio. Se trata de una colección ordenada (por índice), pero no clasificada. Es posible que desees saber que a partir de la versión 1.4, ahora ArrayList implementa la nueva interfaz RandomAccess-un marcador de interfaz (es decir, no tiene métodos) que dice: "esta lista soporta un rápido (por lo general, intervalo de tiempo constante) de acceso aleatorio". Elija esta en vez de LinkedList cuando necesites rapidez de iteración, pero no son tan susceptibles para estar haciendo un montón de inserciones y eliminaciones. Vector Vector esta desde los primeros días de Java; Vector y Hashtable son las dos colecciones originales, el resto se añadieron con Java 2 versiones 1.2 y 1.4. Un Vector es básicamente lo mismo que un ArrayList, pero los métodos de Vector son sincronizados para hilos de seguridad. Normalmente deseas utilizar ArrayList en lugar de Vector porque los métodos sincronizados afecta al rendimiento y tal vez no lo necesites. Y si necesitas hilos de seguridad, hay métodos de utilidad en la clase Collection que te pueden ayudar. Vector es la única clase que no sea ArrayList para implementar RandomAccess. LinkedList Un LinkedList se ordena por el índice de posición, como ArrayList, salvo que los elementos están doblemente vinculados el uno con el otro. Este vínculo te da nuevos métodos (más allá de lo que se obtienen de la interfa List) para añadir y eliminar desde el principio o desde el final, lo que lo convierte en una elección fácil para la implementación de una pila o cola. Tenga en cuenta que un LinkedList puede iterar más lento que un ArrayList, pero es una buena elección cuando necesitas una inserción y supresión rápida. A partir de Java 5, la clase LinkedList se ha mejorado para aplicar la interfaz java.util.Queue. Como tal, apoya ahora con los métodos de cola comunes: peek(), poll(), y offer(). Interfaz Set Un Set se preocupa por la peculariedad de que no permite duplicados. Su buen amigo el método equals() permite determinar si dos objetos son idénticos (en cuyo caso sólo puede estar uno en el set). Las tres implementaciones de Set se describen en la siguiente sección. HashSet Un HashSet es un Set sin clasificar y desordenado. Utiliza el hashCode del objeto para ser insertado, por lo que la aplicación más eficiente de hashCode() dará mejor velocidad de acceso. Usa esta clase cuando quieras una colección sin duplicados y no te importe el orden para iterar sobre él. *LinkedHashSet Un LinkedHashSet es una versión ordenada de HashSet que mantiene una doble lista de todos los elementos. Usa esta clase en lugar de HashSet cuando te preocupes por el orden de iteración. Al iterar a través de un HashSet el fin es imprevisible, mientras que un LinkedHashSet te permite iterar a través de los elementos en el orden en que fueron insertados. Exam Watch TreeSet El TreeSet es una de las dos colecciones ordenadas (el otro es TreeMap). Utiliza una estructura de árbol Red-Black y garantiza que los elementos estarán en orden ascendente, según el orden natural. Opcionalmente, puede construir un TreeSet con un constructor que te permite darle a la colección su propio reglamento por lo que el orden debería ser (en vez de confiar en el orden definidos por los elementos' class) mediante una Comparable o Comparator. Interfaz Map Map se preocupa por los identificadores únicos. Tu mapeas una clave única (el ID) a un valor específico, donde tanto la clave y el valor son, por supuesto, los objetos. Las implementaciones de Map te permiten hacer cosas como buscar un valor basado en la clave, pedir una colección de valores solo o pedir a una colección de tan sólo claves. Al igual que Sets, Maps confía en el método equals() para determinar si dos claves son las mismas o diferentes. HashMap El HashMap te da un Map sin clasificar y desordenado. Cuando necesitas un mapa y no te importa el orden (al iterar a través de él), entonces HashMap es el camino a elegir, los otros mapas añaden un poco más de riesgo. En caso de que las claves del mapa estén basadas en la clave del hashCode, por lo que, al igual que HashSet, la más eficiente implementación de hashCode()da mejor velocidad de acceso. HashMap permite una clave nula y múltiples valores nulos en una colección. HashTable Al igual que Vector, Hashtable ha existido desde tiempos prehistóricos de Java. Por diversión, no olvides tomar nota de incoherencia los nombres: HashMap vs Hashtable. ¿Dónde está la capitalización de t? Oh bien, no se espera que se escriba. De todas formas, al igual que Vector está sincronizada a la contraparte delgado, un ArrayList más moderna, Hashtable es la contraparte de sincronizada HashMap. Recuerda que tu no sincronizas una clase, de modo que cuando decimos que Vector y Hashtable están sincronizados, sólo significa que los principales métodos de la clase son sincronizados. Otra diferencia, sin embargo, es que, si bien HashMap te permite tener valores nulos, así como una nula clave, un Hashtable no te permiten tener nada que sea nulo. LinkedHashMap Al igual que su colección homóloga de Set, LinkedHashSet, la colección LinkedHashMap mantiene el orden de inserción (u opcionalmente, por el acceso). A pesar de que será algo más lento que HashMap para añadir y eliminar elementos, se puede esperar más rapidez con una iteración LinkedHashMap. TreeMap Probablemente puedas adivinar por ahora que un TreeMap es un Mapa ordenado. Y ya sabrás que por defecto, esto significa "ordenada por el orden natural de los elementos." Al igual que TreeSet, TreeMap te permite definir un orden personalizado (a través Comparator o Comparable) para poder construir un TreeMap, que especifica cómo los elementos deben compararse entre sí cuando están siendo ordenados. Interfaz Queue Un Queue esta diseñado para contener una lista de "to-dos" o cosas para ser procesadas de una determinada manera. Aunque otros órdenes son posibles, las colas son generalmente consideradas como FIFO (primero en entrar, primero en salir). Las colas soportan todos los métodos del estándar Collection y también añaden métodos para añadir y eliminar elementos de la cola. PriorityQueue Esta clase es nueva en Java 5. Desde la clase LinkedList se ha mejorado para implementar la interfaz Queue, las colas básicas pueden ser manejadas con un LinkedList. El propósito de un PriorityQueue es crear una "prioridad de entrada y una prioridad de salida" para la cola frente a una típica cola FIFO. Los elementos de un PriorityQueue están ordenados, por una ordenación natural (en cuyo caso los elementos que se ordenan primero se accede en primer lugar) o de acuerdo a un Comparator. En cualquier caso, los elementos de ordenación representan sus prioridades relativas. Exam Watch La siguiente tabla es un resumen de 11 de las 13 clases de colección que debes comprender para el examen: Class Ma Se Lis Ordere Sorted p HashMap HashTable TreeMap t x x t No No No x Sorted LinkedHashMa x p HashSet x TreeSet x LinkedHashSet x ArrayList x Vector x LinkedList x PriorityQueue d No Por orden natural o reglas de comparació n Por orden de inserció no No orden de último acceso No No Por orden natural o Sorted reglas de comparació n Por orden de No inserció n Por No indice Por No indice Por No indice Por orden Sorted to-do para escribir código que manipule un array por clasificación (sorting), realice una búsqueda binaria, o la conversión del array en una lista. Utilice las interfaces java.util. Comparator y java.lang.Comparable para afectar a la clasificación de las listas y arrays. Por otra parte, reconocer el efecto de la "ordenación natural" de primitivos de clases de envoltura y java.lang. String en la clasificación.. Hemos dado un alto nivel teórico en ver las principales interfaces y clases del framework Collections, ahora vamos a ver cómo funcionan en la práctica. 3.1. ArrayList Básicos La clase java.util.ArrayList es uno de los más usados comúnmente de todas las clases en el framework Collection. Es como una gran variedad de vitaminas. Algunas de las ventajas que ArrayList sobre los arrays son Pueden crecer dinámicamente. Proporcionan más poderos mecanismos de inserción y búsqueda que los arrays. Echemos un vistazo a usar un ArrayList que contiene Strings. Un objetivo de diseño clave del framework Collections fue ofrecer una rica funcionalidad a nivel de las principales interfaces: List, Set, y Map. En la práctica, usted normalmente quieren instanciar un ArrayList polimorficamente como este: List myList = new ArrayList(); 3. Objetivo de Certificación 6.5 Usando el framework Collections 6.5 Usar las capacidades del paquete java.util para escribir código que manipule una lista por clasificación (sorting), realice una búsqueda binaria, o realizar una conversión de una lista a un array. Utilice las capacidades del paquete java.util A partir de Java 5 querrás decir: List<String> myList = new ArrayList<String>(); Este tipo de declaración sigue los principios de "codificación a una interfaz" de la programación orientada a objetos y hace uso de los genéricos. Vamos a decir mucho más acerca de los genéricos más adelante en este capítulo, pero por ahora sólo sabemos que, a partir de Java 5, la sintaxis <String> es la forma en que tu declaras un tipo de colección. (Antes de Java 5 no había manera de especificar el tipo de una colección, y cuando vayamos a cubrir los genéricos, hablaremos de las implicaciones de la mezcla de colecciones de Java 5 (con tipo) y preJava 5 (sin tipo).) En muchos sentidos, ArrayList <string> es similar a un String [] en la medida en que declara un contenedor que puede contener sólo Cuerdas, pero es más potente que un String []. Echemos un vistazo a algunas de las capacidades que tiene un ArrayList: import java.util.*; public class TestArrayList { public static void main(String[] args) { List<String> test = new ArrayList<String>(); String s = "hi"; test.add("string"); test.add(s); test.add(s+s); System.out.println(test.size()); System.out.println(test.contains(42)); System.out.println(test.contains("hihi")); test.remove("hi"); System.out.println(test.size()); } } a un primitivo antes de que lo pudieses poner en una colección. Con Java 5, los primitivos todavía tienen que ser envueltos, pero el autoboxing se ocupa de él para usted. List myInts = new ArrayList(); // pre Java 5 declaration myInts.add(new Integer(42)); A partir de Java 5 podemos decir myInts.add(42); // autoboxing handles it! En este último ejemplo, todavía estamos añadiendo un objeto Integer a myInts (no es un int primitivo); es sólo que autoboxing maneja la envoltura para nosotros. 3.3. Clasificando (sorting) Collections y Arrays La clasificación (sorting) y la búsqueda de temas se han añadido al examen de Java 5. Ambas colecciones y arrays pueden ser ordenadas y se realizaran búsquedas utilizando métodos de la API. que produce: 3 false true 2 Observa que cuando declaró el ArrayList no le doy un tamaño. Luego fuimos capaces de pedirle el tamaño al ArrayList, hemos sido capaces de pedirle que si contenía objetos concretos, que eliminara un objeto en concreto situado en medio de el, y luego re-comprobado su tamaño. 3.2. Autoboxing con Collections En general, la colecciones pueden almacenar objetos, pero no primitivos. Antes de Java 5, un uso muy común para las clases de envoltura era proporcionar una manera de obtener una colección de primitivos. Antes de Java 5, tenias que envolver 3.4. Sorting Collections Vamos a empezar con algo sencillo como la clasificación de un ArrayList de Strings por orden alfabético. ¿Qué podría ser más fácil? Bueno, vamos a esperar mientras encuentras el método de la clase ArrayList ()… lo hice? De acuerdo, ArrayList no le otorga ninguna manera de clasificar su contenido, pero la clase java.util.Collections hace: import java.util.*; class TestSortl { public static void main(String[] args) { ArrayList<String> stuff = new ArrayList<String>(); // #1 stuff.add("Denver"); stuff.add("Boulder"); stuff.add("Vail") ; stuff.add("Aspen"); stuff.add("Telluride"); System.out.println("unsorted " + stuff); Collections.sort(stuff); // #2 System.out.println("sorted stuff); } } " + Esto produce algo como esto: unsorted [Denver, Boulder, Vail, Aspen, Telluride] sorted [Aspen, Boulder, Denver, Telluride, Vail] La línea 1 esta declarando un ArrayList de Strings y la línea 2 es la clasificación por orden alfabético del ArrayList. Vamos a hablar más sobre la clase Collections, junto con la clase Array en una sección posterior, por ahora vamos a seguir clasificación cosas. Vamos a imaginar que estamos construyendo la aplicación para la definitiva casa automatizada. Hoy estamos centrados en el centro de entretenimiento para el hogar, y más concretamente en el centro de control del DVD. Ya hemos recibido el archivo de E / S de software en el lugar para leer y escribir datos entre el dvdInfo.txt y las instancias de clase DVDInfo. A continuación se describen los aspectos fundamentales de esta categoría: class DVDInfo { String title; String genre; String leadActor; DVDInfo(String t, String g, String a) { title = t; genre = g; leadActor = a; } public String toString() { return title + " " + genre + " " + leadActor + "\n"; } // getters and setter go here } Aquí está el DVD de datos que esta en el fichero dvdinfo.txt: Donnie Darko/sci-fi/Gyllenhall, Jake Raiders of the Lost Ark/action/Ford, Harrison 2001/sci-fi/?? Caddy Shack/comedy/Murray, Bill Star Wars/sci-fi/Ford, Harrison Lost in Translation/comedy/Murray, Bill Patriot Games/action/Ford, Harrison En nuestra aplicación de la casa automatizada, queremos crear una instancia de DVDInfo para cada línea de datos que leemos del archivo dvdinfo.txt. Para cada instancia, vamos a analizar la línea de datos (recuerde string.split ()?) y rellenar las tres variables de instancia de DVDInfo. Por último, queremos poner todas las instancias de DVDInfo en un ArrayList. Imaginate que el método populateList() (abajo) hace todo esto. Aquí es una pequeña pieza de código de nuestra aplicación: ArrayList<DVDInfo> dvdList = new ArrayList<DVDInfo>(); populateList(); // adds the file data to the ArrayList System.out.println(dvdList); Es posible obtener resultados como éste: [Donnie Darko sci-fi Gyllenhall, Jake , Raiders of the Lost Ark action Ford, Harrison , 2001 sci-fi ?? , Caddy Shack comedy Murray, Bill , Star Wars sci-fi Ford, Harrison , Lost in Translation comedy Murray, Bill , Patriot Games action Ford, Harrison ] (Nota: Hemos sobrescrito el método toString para DVDInfo, así que cuando invocamos a println () en el ArrayList estamos invocando a toString () para cada instancia.) Ahora que tenemos el ArrayList, vamos a ordenarlo: Collections.sort(dvdlist); ¡Vaya!,Obtendremos algo como esto: TestDVD.java:13: cannot find symbol symbol : method sort(java.util.ArrayList<DVDInfo>) location: class java.util.Collections Collections.sort(dvdlist); ¿Qué pasa aquí? Sabemos que la clase Collection tiene un método sort(), sin embargo, este error implica que Collections no tiene un método sort() que pueda tomar un dvdlist. Esto significa que debe haber algo mal en el argumento que le estamos pasando (dvdinfo). Si ya has descubierto el problema, pensamos que lo hiciste sin la ayuda del mensaje de error que aparece arriba… ¿Por qué se nos permite clasificar instancias de String? Al mirar hacia arriba en la API de Collections a Collections.sort () tu primera reacción podría ser de pánico. Una vez más la sección de los genéricos le ayudará a leer que extraño método busca la firma. Si lees la descripción del argumento de un método sort(), verás que el método sort() toma una lista de argumentos, y que los objetos en la lista deben poner en práctica una interfaz llamada Comparable. Resulta que String implementa Comparable, y es por eso que fuimos capaces de ordenar una lista de cadenas usando el Collections.sort (). 3.5. Interfaz Comparable La interfaz Comparable es utilizada por el método Collections.sort () y el método java.utils.Arrays.sort () para ordenar las listas y arrays de objetos, respectivamente. Para implementar Comparable, una clase debe implementar un método único, compareTo (). He aquí una invocación de compareTo (): int x = thisObject.compareTo(anotherObject); El método compareTo () devuelve un entero con las siguientes características: Negativo -> Si thisObject <anotherObject Cero -> Si thisObject == anotherObject Positivo -> Si thisObject> anotherObject El método sort() utiliza el método compareTo () para determinar como la lista o el objeto array debe ser ordenada. Desde que llegue a aplicar compareTo () para sus propias clases, puede utilizar cualquiera criterio extraño que prefiera, para ordenar para ordenar las instancias de sus clases. Volviendo a nuestro ejemplo anterior para la clase DVDInfo, podemos tomar el camino más fácil y usar la implementación del método compareTo () de la clase String: class DVDInfo implements Comparable<DVDInfo> { // #1 // existing code public int compareTo(DVDInfo d) { return title.compareTo(d.getTitle()); // #2 } } En la línea 1 declaramos que la clase DVDInfo implementa Comparable de tal manera que los objetos DVDInfo pueden ser comparados con otros objetos DVDInfo. En la línea 2 implementamos compareTo () para comparar los titulos de los objetos DVDInfo. Dado que sabemos que los títulos son Strings, y que String implementa Comparable, esta es una forma fácil de clasificar nuestros objetos DVDInfo, por título. Antes de llegar los genéricos a Java 5, habrías tenido que implementar comparables algo como esto: class DVDInfo implements Comparable { // existing code public int compareTo(Object o) { // takes an Object rather // than a specific type DVDInfo d --- (DVDInfo)o; return title.compareTo(d.getTitle()); } } Esto sigue siendo legal, pero se puede ver que es doloroso y arriesgado, porque tiene que hacer un casting y necesitas verificar que el casting no fallara antes de intentarlo. Exam Watch Uniendo los códigos, nuestra clase DVDInfo ahora tiene este aspecto: class DVDInfo implements Comparable<DVDInfo> { String title; String genre; String leadActor; DVDInfo(String t, String g, String a) { title = t; genre = g; leadActor = a; } public String toString() { return title + " " + genre + " " + leadActor + "\n"; } public int compareTo(DVDInfo d) { return title.compareTo(d.getTitle()); } public String getTitle() { return title; } // other getters and setters } Ahora, cuando invocamos a Collections.sort (dvdlist); obtenemos: [2001 sci-fi ?? , Caddy Shack comedy Murray, Bill , Donnie Darko sci-fi Gyllenhall, Jake , Lost in Translation comedy Murray, Bill , Patriot Games action Ford, Harrison , Raiders of the Lost Ark action Ford, Harrison , Star Wars sci-fi Ford, Harrison ] 3.6. Sorting con Comparator Mientras que estuvimos viendo el método Collections.sort() habrás notado que existe una versión sobrecargada de sort() que toma una lista, y algo que se llama Comparator. La interfaz Comparator da la capacidad de ordenar una determinada colección de cualquier forma diferente. La interfaz Comparator puede usar para ordenar las instancias de cualquier clase, incluso las clases no puedes modificar-a diferencia de la interfaz Comparable, que te obliga a cambiar la clase cuyas instancias quieras ordenar. la interfaz Comparator también es muy fácil de implementar, tiene un solo método, compare (). He aquí una pequeña clase que se puede utilizar para ordenar una lista de instancias DVDInfo, por género. import java.util.*; class GenreSort implements Comparator<DVDInfo> { public int compare(DVDInfo one, DVDInfo two) { return one.getGenre().compareTo(two.getGenre()); } } El método Comparator.compare() devuelve un entero cuyo significado es el mismo que el del valor de retorno del método Comparable.compareTo() . En este caso estamos tomando ventaja de que al pedir compareTo() para hacer la comparación real de trabajo para nosotros. Aquí hay un programa de prueba que nos permite probar tanto nuestro código Comparable y nuestro nuevo código Comparator: import java.util.*; import java.io.*; populateList() needs this // public class TestDVD { ArrayList<DVDInfo> dvdlist = new ArrayList<DVDInfo>(); public static void main(String[] args) { new TestDVD().go(); } public void go() { populateList(); System.out.println(dvdlist); // output as read from file Collections.sort(dvdlist); System.out.println(dvdlist); // output sorted by title GenreSort gs = new GenreSort(); Collections.sort(dvdlist, gs) ; System.out.println(dvdlist); output sorted by genre } // public void populateList() { // read the file, create DVDInfo instances, and // populate the ArrayList dvdlist with these instances } } Ya has visto las dos primeras listas de salida, aquí está la tercera: [Patriot Games action Ford, Harrison , Raiders of the Lost Ark action Ford, Harrison , Caddy Shack comedy Murray, Bill , Lost in Translation comedy Murray, Bill , 2001 sci-fi ?? , Donnie Darko sci-fi Gyllenhall, Jake , Star Wars sci-fi Ford, Harrison ] Debido a que las interfces Comparable y Comparator son tan similares, en el examen intentaremos confundirte. Por ejemplo se podrá pedir la implementación de compareTo () en la interfaz Comparator. La siguiente tabla muetra las diferencias entre estas dos interfaces. java.lang.Comparable int objOne.compareTo(objTwo) Returns negative if obj One < objTwo zero if objOne == objTwo positive if objOne > objTwo Debes modificar la clase java.util.Comparator int compare(objone, objTwo) Igual que Comparable Crear una clase cuyas instancias quieras ordenar. Solo puede ser creada una secuencia de orden. Implementado frecuentemente en la API por: String, clases de envoltura, Date, Calendar separada de la clase cuyas instancias quiera ordenar. Muchas secuencias de orden pueden ser creadas. Significa que deben aplicarse para clasificar las instancias de terceras clases. Las clases Collections y Arrays proporcionan métodos que te permiten buscar un elemento especifico. Hay que aplicar las siguientes reglas cuando se busca dentro de un Array o una colección. 3.7. Sorting con la clase Arrays Hemos estado utilizando la clase java.util.Collections para ordenar las colecciones, ahora echemos un vistazo a la utilización de la clase java.util.Arrays para ordenar arrays. La buena noticia es que la clasificación de arrays de objetos es como la clasificación de las colecciones de objetos. El método Arrays.sort() es sobrescrito de la misma manera que el método Collections.sort(). Arrays.sort (arrayToSort) Arrays.sort (arrayToSort, comparador) Además, el método Arrays.sort() está sobrecargado un millón de veces para proporcionar un par de métodos para clasificar todo tipo de primitivos. Los métodos Arrays.sort () clasifican los tipos primitivos por orden natural. No te dejes engañar en la pregunta del examen que trate de un tipo primitivo usando Comparator. Por último, recordar que los métodos sort() de la clase Collections y las clases Arrays son métodos estáticos y que alteran los objetos que están clasificando, en lugar de devolver un objeto diferente clasificado. Exam Watch Las búsquedas se realizan utilizando el método binarySearch(). El éxito de las búsquedas devuelve el indice entero del elemento buscado. Las búsquedas sin éxito devuelven un índice entero que representa el punto de inserción. El punto de inserción es el lugar en el array/Collection donde el elemento debería ser insertado para mantener el array/Collection debidamente ordenado. ya que el método binarySearch() devuelve valores positivos y 0 indicando búsquedas exitosas, el método binarySearch() usa números negativos para indicar puntos de inserción. Dado que 0 es un resultado válido para una búsqueda con éxito, el primer punto de inserción es -1. Por lo tanto, el punto de inserción se representa como ((punto de inserción) -1). Por ejemplo, si el punto de inserción de una búsqueda está en el elemento 2, el punto de inserción que devolverá sera -3. La colección/array buscadas deben ser ordenadas antes de que puedas buscar. Si intentas buscar un array o Collection que no haya sido ordenado, los resultados de la búsqueda no serán predecibles. Si la colección / array que deseas buscar se ordena por orden natural, debe ser buscado en orden natural. (Esto se logra por no enviar un Comparator como argumento para el método binarySearch().) Si la colección / array que deseas buscar se ordena mediante Comparator, debe buscar utilizando la misma comparación, que se pasa como segundo argumento al método binarySearch (). Recuerda que Comparator no puede utilizarse cuando se busca arrays de primitivos. Observa el siguiente código que utiliza el método binarySearch(): 3.8. Buscando en Arrays y Collections import java.util.*; class SearchObjArray { public static void main(String [] args) { String [] sa = {"one", "two", "three", "four"}; Arrays.sort(sa); // #1 for(String s : sa) System.out.print(s + " "}; System.out.println("\none = " + Arrays.binarysearch(sa,"one")); // #2 System.out.println("now reverse sort"); ReSortComparator rs = new ReSortComparator(); // #3 Arrays.sort(sa,rs); for(String s : sa) System.out.print(s + " "); System.out.println("\none = " + Arrays.binarysearch(sa,"one")); // #4 System.out.println("one = " + Arrays.binarysearch(sa,"one",rs)); // #5 } static class ReSortComparator implements Comparator<String> { // #6 public int compare(String a, String b) { return b.compareTo(a); // #7 } } } Que produce algo como esto: four one three two one = 1 now reverse sort two three one four one = -1 one = 2 Esto es lo que sucede: Línea 1: Ordenar el array sa, alfabéticamente (el orden natural). Línea 2 Buscar ubicación del elemento "one", que es 1. Línea 3 Crea un Comparator de ejemplo. En la siguiente línea reordenaremos el array usando el Comparator. Línea 4 Intento buscar el array. No pasamos el Comparator al método binarySearch() que hemos utilizado para ordenar el array, así que tendremos una respuesta incorrecta (no definido). Línea 5 Buscar de nuevo, pasando el Comparator al método binarySearch(). Esta vez tenemos la respuesta correcta, 2 Línea 6 Definimos el Comparator; que está bien para quesea una clase interna. Línea 7 Al cambiar el uso de los argumentos en la invocación de compareTo(), obtenemos una clasificación invertida. Exam Watch 3.9. Convirtiendo Arrays a Listas (y viceversa) Hay un par de métodos que le permiten convertir arrays en listas y listas en arrays. Las clases Set y List tienen los métodos toArray() y la clase Arrays tiene un método llamado asList(). El método Arrays.asList() copia un array en una lista. La API dice, "Devuelve una lista de tamaño fijo lista respaldada por el array especificado." Cuando usas el método asList (), el array y la lista se unen y al actualizar uno de ellos, el otro se actualiza automáticamente. Echemos un vistazo: String [] sa = {"one", "two", "three", "four"}; List sList = Arrays.asList(sa); // make a List System.out.println("size " + sList.size()); System.out.println("idx2 " + sList.get(2)); sList.set(3,"six"); // change List sa[l] = "five"; // change array for(String s : sa) System.out.print(s + " "); System.out.println("\nsl[1] " + sList.get(1)); Esto produce size 4 idx2 three one five three six SL [1] five Observa que cuando imprimimos el estado final del array y la lista, ambos han sido actualizados con los demás cambios. Ahora echemos un vistazo al método toArray(). Hay dos tipos de método toArray(): uno que devuelve un nuevo objeto array y otro que utiliza el array que envía como el array de destino: List<Integer> iL = new ArrayList<Integer>(); for(int x=0; x<3; x++) iL.add(x); Object[] oa = iL.toArray(); create an Object array Integer[] ia2 = new Integer[3]; ia2 = iL.toArray(ia2); create an Integer array // // 3.10. Usando Lists Recuerde que las listas se suelen utilizar para mantener las cosas con algún tipo de orden. Puedes utilizar un LinkedList para crear una cola FIFO (primero en entrar, primero en salir). Puedes usar un ArrayList para hacer un seguimiento de los lugares visitados y en qué orden. Observa que en ambos ejemplos es perfectamente razonable suponer que podría ocurrir duplicados. Además, las listas te permiten anular manualmente la ordenación de elementos al añadir o eliminar elementos a través del elemento índice. Antes de Java 5, y el aumento del bucle for, la forma más común para examinar una lista "elemento a elemento" es el uso de un iterador. Un iterador es un objeto que está vinculado con una específica colección. Se te permite iterar a través de una colección paso a paso. Los dos métodos de Iterator que necesitas entender para el examen son: Boolean hasNext(): Devuelve true si existe al menos un elemento más en la colecció que está iterando. Invocando hasNext () no te mueves al siguiente elemento de la colección. object next(): Este método devuelve el siguiente objeto en la colección y te mueves al elemento después de que el elemento sea devuelto. Veamos un poco el código que utiliza un List y un Iterator: import java.util.*; class Dog { public String name; Dog(String n) { name = n; } } class ItTest { public static void main(String[1 args) { List<Dog> d = new ArrayList<Dog>(); Dog dog = new Dog("aiko"); d.add(dog); d.add(new Dog("clover")); d.add(new Dog("magnolia")); Iterator<Dog> i3 = d.iterator(); // make an iterator while (i3.hasNext()) { Dog d2 = i3.next(); // cast not required System.out.println(d2.name); } System.out.println("size " + d.size()); System.out.println("getl " + d.get(1).name); System.out.println("aiko " + d.indexOf(dog)); d.remove(2) ; Object[] oa = d.toArray(); for(Object o : oa) { Dog d2 = (Dog)o; System.out.println("oa " + d2.name); } } } Esto produce aiko clover magnolia size 3 getl clover aiko 0 oa aiko oa clover En primer lugar, utilizamos la sintaxis generica para crear el iterador (un Iterator de tipo Dog). Debido a esto, cuando se utiliza el método next(), no tenemos que hacer un casting a Dog al elemento devuelto por next(). Podríamos haber declarado el Iterator como esto: Iterator i3 = d.iterator(); iterator // Si insertas la siguiente línea de código obtendrás una salida como esta: Set s = new HashSet(); this code make an Pero entonces habría tenido que hacer un casting al elemento devuelto: Dog d2 = (Dog)i3.next(); El resto del código demuestra la utilización de los métodos size(), get(), indexOf() y toArray(). No debería haber sorpresas con estos métodos. Como última advertencia, recuerde que List es una interfaz! true true true false true a java.lang.Object@e09713 42 b Es importante saber que la orden de los objetos impresos en el segundo bucle for no es previsible: HashSets y LinkedHashSets no garantizan ningún orden. Asimismo, observa que la cuarta invocación de add() falla, ya que este, intentará insertar un duplicado (un String con el valor a) en el Set. Si insertas esta línea de código obtendrás algo como esto: Set s = new TreeSet(); this code 3.11. Usando Sets Recuerda que los Sets son usados cuando no quieres ningun duplicado en tu colección. Si intentas añadir un elemento que ya existe en un conjunto a ese conjunto , el elemento duplicado no será añadido, y el método add() devolvera false. Recuerda, HashSets tienden a ser muy rápido porque, como hemos hablado antes, usan hashcodes. También puede crear una TrecSet, que es un conjunto cuyos elementos estan ordenados. Debes tener precaución cuando utilizas un TreeSet (estamos a punto de explicar por qué): import java.util.*; class SetTest { public static void main(String [] args) { boolean[] ba = new boolean[5]; // insert code here ba[0] = s.add("a"); ba[1] = s.add(new Integer(42)); ba[2] = s.add("b") ; ba[3] = s.add("a") ; ba[4] = s.add(new Object()); for(int x=0; x<ba.length; x++) System.out.print(ba[x] + " "); System.out.println("\n"); for(Object o : s) System.out.print(o + " "); } } // insert // insert Exception in thread "main" java.lang.ClassCastException: java. lang.String at java.lang.Integer.compareTo(Integer.java:35) at Java.util.TreeMap.compare(TreeMap.java:1093) at java.util.TreeMap.put(TreeMap.java:465) at java.util.TreeSet.add(TreeSet.java:210) La cuestión es que siempre que quieras que una colección esta ordenada, sus elementos deben ser comparables entre sí. Recuerde que a menos que se especifique otra cosa, los objetos de diferentes tipos no son comparables entre sí. 3.12. Usando Maps Recuerda que cuando usas una clase que implementa Map, para cualquiera de las clase que uses como parte de la clave para el mapa debe sobrescribir los métodos hashCode() y equals(). (Bueno, sólo tienes que sobrescribirlo si estás interesado en recuperar cosas de el mapa. En serio, es legal utilizar una clase que no sobrescriba equals() y hashCode() como clave en un mapa; tu código se compilara y ejecutara, lo único que no encontrarás tus cosas.) Aquí hay algo de código que demuestra el uso de un HashMap: import java.util.*; class Dog { public Dog(String n) { name = n; } public String name; public boolean equals(Object o) { if( (o instanceof Dog) && (((Dog)o).name == name)) { return true; } else { return false; } } public int hashCode() {return name.length(); } } class Cat { } enum Pets {DOG, CAT, HORSE } class MapTest { public static void main(String[] args) { Map<Object, Object> m = new HashMap<Object, Object>(); m.put("kl", new Dog("aiko")); some key/value pairs m.put("k2", Pets.DOG); m.put(Pets.CAT, "CAT key"); Dog d1 = new Dog("clover"); let's keep this reference // add // m.put(d1, "Dog key"); m.put(new Cat(), "Cat key"); System.out.println(m.get("kl")); // #1 String k2 = "k2"; System.out.println(m.get(k2)); // #2 Pets p = Pets.CAT; System.out.println(m.get(p)); // #3 System.out.println(m.get(dl)); // #4 System.out.println(m.get(new Cat())); // #5 System.out.println(m.size()); // #6 } } que produce algo como esto: Dog@1c DOG CAT key Dog key null 5 Vamos a revisar la salida. El primer valor recibido es un objeto Dog (su valor variara). El segundo valor recuperado es un valor enum (DOG). El tercer valor recuperado es un String; observa que la clave es un valor enum. ¿Cuál es el hecho de que hayamos podido utilizar un enum como clave? La razón de esto es que enums tenga sobrescritos equals() y hashCode(). Y, si buscas en la API de la clase java.lang.Enum verás que, de hecho, estos métodos han sido sobrescritos. El cuarto resultado es un String. El punto importante acerca de esta salida es que la clave usada para recuperar el String se hizo de un objeto Dog. La quinta salida es nula. El punto importante aquí es que el método get() falla al encontrar el objeto Cat que se insertó antes. (La última línea de salida confirma que, efectivamente, el 5 clave/valor existe en el mapa.) ¿Por qué no encontramos la clave String Cat? ¿Por qué funciono al usar una instancia de Dog como una clave y cuando utilizamos una instancia de Cat como clave no? Es fácil ver que Dog sobrescribio equals() y hashCode(), mientras que Cat no. Echemos un vistazo rápido a los hashcodes. Hemos utilizado un formula hashCode increíblemente simplista en la clase Dog-el hashCode de un objeto Dog es la longitud del nombre de la instancia. Así que en este ejemplo la hashCode = 4. Vamos a comparar las dos siguientes hashCode () métodos: public int hashCode() {return name.length(); } // #1 public int hashCode() {return 4; } // #2 ¿Los dos hashcodes son legales? ¿Se van a recuperar con éxito objetos de un mapa? ¿Cual será más rápido? La respuesta a las dos primeras preguntas es Sí y Sí. Ninguno de estos hashcodes será muy eficiente (en realidad, ambos son increíblemente ineficientes), pero ambos son legales, y ambos funcionan. La respuesta a la última pregunta es que la primera hashCode será un poco más rápido que el segundo hashCode. En general, cuanto más singular hashcodes crea una fórmula, más rápida será la recuperación será. La primera fórmula hashCode generará un código diferente para cada nombre de longitud (por ejemplo, el nombre de Robert generará un hashCode y el nombre Benchley generará un hashCode diferente). La segunda fórmula hashCode siempre producen el mismo resultado, 4, por lo que será más lenta que la primera. Lo último por ver de los Maps es lo que ocurre cuando un objeto utilizado como elemento clave ha cambiado sus valores? Si añadimos dos líneas de código al final de la anterior MapTest.main (), d1.name = "magnolia" System.out.printIn(m.get(d1)); obtenemos algo como esto: Dog@4 DOG CAT key Dog key null 5 null El Dog que anteriormente se pudo encontrar ahora no se ha encontrado. Debido a que la variable Dog.name se utiliza para crear el hashCode, cambiando el nombre cambia el valor de la hashCode. Como última prueba para los hashcodes es determinar la salida para las siguientes líneas de código si se añaden al final de MapTest.main (): d1.name = "magnolia"; System.out.println(m.get(dl)); // #1 d1.name = "clover"; System.out.println(m.get(new Dog("clover"))); // #2 dl.name = "arthur"; System.out.println(m.get(new Dog("clover"})); // #3 Recuerda que el hashCode es igual a la longitud de la variable name. Al estudiar un problema como este, puede resultar útil pensar en las dos etapas de recuperación: 1. Usa el método hashCode() para encontrar el cubo corrector 2. Usa el método equals() para encontrar el objeto en el cubo En la primera llamada a get(), el hashCode es el 8 (magnolia) y debe ser 6 (trébol), por lo que la recuperación falla en el paso 1 y obtenemos null. En la segunda llamada a get(), los hashcodes son 6 en ambos, por lo que logra el paso 1. Una vez estamos en el cubo correcto (el "tamaño de name = 6" cubo), el método equals() es invocado, y desde el método equals() de Dog compara los nombres, equals() tiene éxito, y la salida es Dog key. En la tercera invocación de get(), la prueba hashCode es satisfactoria, pero la prueba equals() falla porque arthur no es igual que trébol. En una tabla más adelante haremos un resumen de los métodos Map con los que debes estar familiarizado para el examen. 3.13. Usando la clase PriorityQueue La última clase collection que tendrás que entender para el examen es PriorityQueue. A diferencia de las estructuras de cola básicas que son FIFO (primero en entrar, primeroen salir) por defecto, un PriorityQueue órdena sus elementos usando una prioridad definida por el usuario. La prioridad puede ser tan simple como una ordenación natural (en la que, por ejemplo, una entrada de 1 sería una prioridad más alta que una entrada de 2). Además, un PriorityQueue puede ser ordenado mediante Comparator, que te permite definir cualquier tipo de ordenación que desees. Las colas tienen unos métodos que no se encuentran en las interfaces de recogida: peek(), poll(), y offer(). import java.util.*; class PQ { static class PQsort implements Comparator<Integer> { // inverse sort public int compare(Integer one, Integer two) { return two - one; // unboxing } } public static void main(String[] args) { int[] ia= {1,5,3,7,6,9,8}; // unordered data PriorityQueue<Integer> pq1 = new PriorityQueue<Integer>(); // use natural order for(int x : ia) // load queue pq1.offer(x); for(int x : ia) // review queue System.out.print(pql.poll() + " "); System.out.println("") ; PQsort pqs = new PQsort(); // get a Comparator PriorityQueue<Integer> pq2 = new PriorityQueue<Integer>(10,pqs); // use Comparator for(int x : ia) // load queue pq2.offer(x); System.out.println("size " + pq2.size()); System.out.println("peek " + pq2.peek()); System.out.println("size " + pq2.size()); System.out.println("poll " + pq2.poll()); System.out.println("size " + pq2.size()); for(int x : ia) // review queue System.out.print(pql.poll() + " ") ; } } Este código produce algo como esto: 1356789 size 7 peek 9 size 7 poll 9 size 6 8 7 6 5 3 1 null Veamos esto en detalle. El primer bucle for itera a través del array "ia" y usa el método offer() para añadir elementos a la PriorityQueue llamado pq1. El segundo bucle for itera a través de pq1 utilizando el método poll(), que devuelve la máxima prioridad de entrada en pq1 Y elimina la entrada de la cola. Observa que los elementos se devuelve en el orden de prioridad (en este caso, el orden natural). A continuación, crearemos un Comparator-en este caso, un comparador que ordene los elementos en orden inverso al natural. Utilizamos este Comparador para construir una segunda PriorityQueue, pq2, y la cargaremos con el mismo array que hemos utilizado anteriormente. Por último, comprobamos el tamaño de pq2 antes y después de las llamadas a peek() y poll(). Esto confirma que peek() devuelve el elemento de mayor prioridad en la cola sin eliminarlo, y poll() devuelve el elemento de mayor prioridad y lo elimina de la cola. Por último, se revisa el resto de elementos en la cola. 3.14. Descripción Método para Arrays y Colecciones Para estas dos clases, ya hemos cubierto los métodos que pueden surgir en el examen. La siguiente tabla nos hace un resumen de los métodos que debemos conocer. (Nota: La sintaxis T[] se explicará más adelante en este capítulo, por ahora, pensar en el sentido de que es "cualquier array que no sea de primitivos.") Métodos de java.util.Arrays static List asList(T[]) static int binarySearch(Object [], key) static int binarySearch(primitive [], key) static int binarySearch(T[], key, Comparator) static boolean equals(Object[], Object[] ) static boolean equals(primitive[], primitive[] ) public static void sort(Object[] ) public static void sort(primitive[] ) Descripciones Convertir un array en una lista (y unirlos) Buscar en un array ordenado un valor dado, devolviendo un índice oel punto de inserción. Buscar un valor en un array ordenado con Comparator. Comparar dos arrays para ver si sus contenidos son iguales. Ordenar los elementos de un array usando la ordenación natural. Ordenar los elementos de public static void sort(T[], un array usando un Comparator) Comparator. public static String toString(Object[]) Crear un String con el public static String contenido de un array. toString(primitive[]) Métodos de java.util.Collections static int binarySearch(List, key) static int binarySearch(List, key, Comparator) static void reverse(List) static Comparator reverseOder() static Comparator reverseOder(Comparator) static void sort(List) static void sort(List, Comparator) Descripciones Buscar un valor dado en una lista ordenada, devolviendo un índice o el punto de inserción. Invertir el orden de los elementos de una lista. Devuelve un Comparator que ordena en orden inverso a la secuencia actual de orden de la colección. Ordena una lista ya sea usando el orden natural o un Comparator. boolean containsKey(object key) X boolean containsValue(object value) X 3.15. Descripción Método para List, Set, Map, y Queue Para estas cuatro interfaces, que ya hemos cubierto los métodos que pueden surgir en el examen. La siguiente tabla nos hace un resumen de los métodos de List, Set y Map que debemos conocer. Métodos boolean add(element) boolean add(index, element) boolean contains (object) List Set Map Descripciones Añadir un elemento. Para Lists, X X opcionalmente añade el elemento en un índice. Añadir un elemento. Para Lists, X opcionalmente añade el elemento en un índice. Buscar en una colección un X X objeto (u opcionalmente para los Maps object get(index) X object get(key) X int indexOf(object) X Iterator iterator() X X Set keyset() X put(key, value) X una clave), devuelve un booleano como resultado. Buscar en una colección un objeto (u opcionalmente para los Maps una clave), devuelve un booleano como resultado. Buscar en una colección un objeto (u opcionalmente para los Maps una clave), devuelve un booleano como resultado. Obtienes un objeto de una colección, usando un índice o una clave. Obtienes un objeto de una colección, usando un índice o una clave. Obtiene la localización de un objeto en una lista Obtiene un iterador para List o Set Devuelve un Set que contiene las claves de un Map Añadir una pareja clave/valor a un Map remove (index) remove (object) X X X remove (key) int size() object[] toArray() T[] toArray(T[]) X X X X X X Elimina un elemento mediante un índice o mediante el valor del elemento, o via clave. Elimina un elemento mediante un índice o mediante el valor del elemento, o via clave. Elimina un elemento mediante un índice o mediante el valor del elemento, o via clave. Devuelve el numero de elementos de una colección. Devuelve un array conteniendo los elementos de una colección. Para el examen, los métodos de PriorityQueue que son importantes que entendamos son offer() (que es similar a add()), peek() (que indica el elemento a la cabeza de la cola, pero no lo elimina), y poll() (que recupera el elemento a la cabeza de la cola y lo elimina). Exam Watch 4. Objetivo de Certificación 6.3 y 6.4 - Tipos genéricos 6.3 Escribir código que use las versiones genéricas de la API de Collections, en particular las interfaces Set, List y Map y las clases de implementación. Reconocer las limitaciones de los no genéricos y como refactorizar el código para usar las versiones genéricas. 6.4 Desarrollar código que haga un uso adecuado de los tipos de parámetros en declaraciones de clase/interfaz, variables de instancia, argumentos de métodos y tipos devueltos; y escribir métodos genéricos o métodos que hagan uso de tipos comodín y entender las similitudes y diferencias entre estos dos enfoques. Los arrays en Java han sido siempre un tipo seguro, un array declarado de tipo String (String []) no puede aceptar enteros (o ints), Dogs o cualquier cosa que no sea Strings. Pero recuerda que antes de Java 5 no había sintaxis para declarar un tipo de colección segura. Para hacer un ArrayList de Strings, deciamos, ArrayList myList = new ArrayList (); o, el equivalente polimórfico: List myList = new ArrayList (); No había sintaxis que te permitiera especificar que myList tendría Strings y sólo Strings. Y de ninguna manera especificar un tipo para el ArrayList, el compilador no podía forzarte a que pusieses sólo cosas del tipo especificado en la lista. A partir de Java 5, podemos usar los genéricos pero estos no son sólo para hacer colecciones de tipo seguro. Así, mientras que los genéricos no son sólo para las colecciones, pensar en colecciones como la abrumadora razón y motivación para añadir los genéricos al lenguaje. Y no fue una decisión fácil, ni ha sido totalmente bienvenida. Los genéricos vienen con un montón de equipaje-la mayoría de los cuales tu nunca veras ni te preocuparas, pero hay algunos errores que se presentan con una rapidez sorprendente. Vamos a cubrir la probabilidad de que aparezca en tu propio código y estas son también las cuestiones que necesitas saber para el examen. El mayor desafío para Sun en la adición de genéricos al lenguaje (y la razón principal, que les llevó tanto tiempo) fue la forma de tratar con código legado construido sin genéricos. Los ingenieros Java de Sun, evidentemente, no querían romper con todos los códigos ya existente en Java, por lo que tuvieron que encontrar una forma de clases Java donde ambos tipos, seguros (genérico) y no seguros (non-generic/pre-Java 5), de colecciones trabajasen juntos. Su solución no es la más amistosa, pero sí te permiten el uso de código mas viejo no genérico, así como la utilización de código genérico que juega con el código no genérico. Mientras puedas integrar código genérico de Java 5 con código legado no genérico, las consecuencias pueden ser desastrosas, y por desgracia, la mayoría de los desastres suceden en tiempo de ejecución, no en tiempo de compilación. Afortunadamente, sin embargo, la mayoría de los compiladores generan warnings para indicarte si estas utilizando colecciones inseguras (es decir, no genéricos). El examen de Java 5 abarca ambos pre-Java 5 (no genéricos) y el estilo de las colecciones de Java 5. 4.1. Manera legal de hacer colecciones Aquí vemos un ArrayList de pre-Java 5 con la intención de almacenar Strings. (Decimos "intención" porque eso es sobre todo lo que había buenas intenciones - para asegurarse de que el ArrayList contendría sólo Strings). List myList = new ArrayList (); // no puede declarar un tipo myList.add ( "Fred"); // OK, almacenara Strings myList.add (new Dog ()); // y almacenara también Dogs myList.add (new Integer (42)); // y enteros ... Una colección no genérica puede almacenar cualquier tipo de objeto! Una colección no genérica es muy feliz de almacenar cualquier cosa que NO sea un primitivo. Esto significa que el programador es el que debe tener cuidado al no tener manera de definir un tipo para una colección. Ya que una colección podía no almacenar nada, los métodos que obtienen objetos de la colección tenían como tipo de retorno el tipo java.lang.Object. Esto significa que para conseguir un String hacia falta realizar un casting: String s = (String) myList.get (0); Y ya que no podías garantizar que lo que estabas obteniendo era realmente un String (desde que se permitió no poner nada en la lista), el casting podía fallar en tiempo de ejecución. Por lo tanto, los genéricos se ocupan de ambos extremos (poner en la lista y obtener de la lista)forzando el tipo de tus colecciones. Vamos a actualizar la lista de cadenas: List <string> myList = new ArrayList <String> (); myList.add ( "Fred"); / / OK, almacenara String myList.add (new Dog ()); / / error de compilación! Perfecto. Eso es exactamente lo que queremos. Mediante el uso de la sintaxis genérica - lo que significa poner el tipo entre los simbolos < y > ,<String>, le estamos diciendo al compilador que esta colección sólo puede contener objetos String. El tipo entre < > se refiere a cualquiera de los "tipos parametrizados", "tipo de parámetro" o "tipo". En este capítulo, vamos a hacer referencia a las dos nuevas maneras. Así que, ahora que puedes garantizar lo que entra, también puede garantizar lo que sale, y esto significa que puedes deshacerte de los castings cuando se obtiene algo de la colección. En lugar de String s = (String) myList.get (0); // pre-genéricos, cuando un // String no está garantizado ahora podemos decir simplemente String s = myList.get (0); El compilador ya sabe que myList contiene sólo las cosas que se pueden asignar a una referencia String, por lo que ahora no hay necesidad de hacer un casting. Hasta el momento, parece bastante simple. Y con el nuevo bucle for, por supuesto puedes iterar sobre la lista con garantías de que es una lista de Strings: for (String s: myList) ( int x = s.length (); // No necesidad de hacer casting antes de llamar a un método de String! El // Compilador ya sabía "s" es una cadena procedentes de MyList ) Dog d = (Dog) getDogList().get(0) Y, por supuesto, puedes declarar un tipo de parámetro para el argumento de un método, que a su vez hace que el argumento de un tipo seguro de referencia: (El casting en este ejemplo se aplica a lo que viene del del método get() de List ; no estamos casteando que es lo que se devuelve del método getDogList (), que es un List.) Pero ¿qué pasa con el beneficio de una colección completamente heterogénea? En otras palabras, ¿y si te gusta el hecho de que antes de los genéricos pudieses hacer un ArrayList que podía almacenar cualquier tipo de objeto? void takeListOfstrings (List <string> strings) ( strings.add ( "foo"); / / no hay problema añadiendo un String ) El método anteriormente no compilar si hemos cambiado a List myList = new ArrayList (); // viejo estilo, no genéricos void takeListOfStrings (List <String> strings) ( strings.add (new Integer (42)); // NO! String es tipo seguro ) es casi idéntico a Los tipos devueltos pueden ser, evidentemente, declarados tipos seguros así como: Declarando una lista con un parámetro de tipo <Object> haces una colección que funciona casi de la misma forma que la original antes de Java 5, colecciones no genéricas-puede poner cualquier tipo de objeto en la colección. Verás un poco más tarde que las colecciones no genéricas y las colecciones de tipo <Object> no son del todo iguales. public list<Dog> getDogList () List <Dog> dogs = new ArrayList<Dog> (); // Más código para insertar dogs return dogs; ) El compilador te impedirá devolver algo no compatible con un List <Dog>. Y puesto que el compilador garantiza que sólo un tipo seguro Dog List se devolverá, a quienes piden el método no es necesario hacer un casting para obtener los objetos Dog de la lista: Dog d = getDogList().get(0); // Sabemos que estamos obteniendo un Dog Con pre-Java 5, no código genérico, el método getDogList() sería public List getDogList () ( List dogs = new HashSet (); // Código para agregar sólo objetos Dogs ... dedos cruzados ... return dogs; // una Lista de NADA va a trabajar aquí ) y la persona que llamase tendría que hacer un casting: List <Object> myList = new ArrayList <Object> (); //almacena cualquier tipo de objeto 4.2. Genéricos y el legado de código Lo que necesitas saber para el examen de los genéricos es el modo de actualización de código no genérico para que sea genérico. Ya sabes que hay que poner el tipo entre los signos (<>), inmediatamente después del tipo de colección tanto en la declaración de variables y la llamada al constructor, incluyendo cualquier lugar donde declares una variable (así que los argumentos y los tipos de retorno también). Un List pre- Java 5 puede tener sólo Integers: List myList = new ArrayList (); se convierte en List <Integer> myList = new ArrayList <Integer> (); y la lista que solo puede tener Strings public List changeStrings (ArrayList s){} a este: public List <String> changeStrings (ArrayList <string> s) {} Fácil. Y si hay código que utilizan la anterior versión no genérica y se lleva a cabo un casting para obtener las cosas, que no va a romper ningún código: Integer i = (Integer) list.get (0); // casting ya no es necesario, 4.3. Mezclando colecciones genéricas y no genéricas Ahora aquí es donde comienza lo interesante ... imagina que tenemos un ArrayList, de tipo Integer, y se lo estamos pasando al método de una clase cuyo código fuente no tenemos acceso. ¿Esto funcionara? // a Java 5 class using a generic collection import Java.util.*; public class TestLegacy { public static void main(String[] args) { List<Integer> myList = new ArrayList<Iriteger>(); // type safe collection myList.add(4); myList.add(6); Adder adder = new Adder(); int total = adder.addAll(myList) ; // pass it to an untyped argument System.out.println(total); } } El código viejo de la clase no genérica que queremos utilizar es este: import Java.util.*; class Adder { int addAll(List list) { // method with a non-generic List argument, // but assumes (with no guarantee) that it will be Integers Iterator it = list.iterator(); int total = 0; while (it.hasNext()) { int i = ((Integer)it.next()).intValue(); total + = i; } return total; } } Sí, esto funciona muy bien. Puedes mezclar correctamente código genérico con un código mas viejo no genérico, y todo el mundo está contento. En el ejemplo anterior, el método legado addAll() asumió que la lista se limitaba a números enteros, aunque cuando el código fue escrito, no había garantías de cumplirlo. Corresponde a los programadores tener cuidado. Desde el método addAll() no se hace nada salvo conseguir el Integer (utilizando un casting) de la lista y acceder a su valor, no hubo problemas. En este ejemplo, no hubo riesgo para la persona que llamo al código, pero el método legado podría haber fallado si la lista pasada no contenia nada pero enteros (lo que causaría un ClassCastException). Pero ahora imagina que llamas a un método legado que no lee un valor, pero añade algo nuevo al ArrayList? ¿Esto funcionara? import java.util.*; public class TestBadLegacy { public static void main(String[] args) { List<Integer> myList = new ArrayList<Integer>(); myList.add(4); myList.add(6); Inserter in = new Inserter(); in.insert(myList); // pass List<Integer> to legacy code } } class Inserter { // method with a non-generic List argument void insert(List list) { list.add(new Integer(42)); // adds to the incoming list } } Claro, este código funciona. Se compila y ejecuta. El método insert() pone un Integer en la lista que fue originalmente tipificada como <Integer>, por lo que no hay problema. Pero… ¿y si modificamos el método insert() a algo parecido a este: void insert(List list) { list.add(new String("42")); String in the list // put a // passed in } ¿Funcionara? Sí, por desgracia, sí! Ambos compilaran y ejecutaran. No hay excepción en tiempo de ejecución. Sin embargo, alguien acaba de meter un String en un ArrayList supuestamente de tipo seguro <Integer>. ¿Cómo puede ser eso? Recuerda, el antiguo código heredado se le permitía poner cualquier cosa (excepto primitivos) en una colección. Y con el fin de apoyar al código legado, Java 5 le permite a tu código más nuevo de tipo seguro hacer uso de código más antiguo (lo último que Sun quería hacer era pedir a varios millones de desarrolladores de Java modificar todos sus códigos existentes). Por lo tanto, el compilador de Java 5 se ve obligado a dejar que compiles tu nuevo código de tipo seguro a pesar de que su código invoque a un método de una clase más antigua que no tiene un tipo seguro de argumento. Sin embargo, sólo porque el compilador Java 5 permite que este código se compile no significa que tenga que ser feliz por eso. De hecho, el compilador le advertirá de que estas tomando un gran riesgo de enviar tu ArrayList <Integer> protegido a un método peligroso que puede alterar tu lista y ponerte Floats, Strings, o incluso Dogs. Cuando llamaste al método addAll() en el ejemplo anterior, no insertaste nada en la lista (simplemente sumaba los valores dentro de la colección), por lo que no hay riesgo para la persona que llama que su lista se modifique de alguna manera horrible. Se compiló y ejecuto bien. Pero en la segunda versión, con el método legado insert() que añade un String, el compilador genera una advertencia: javac TestBadLegacy.java Note: TestBadLegacy.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. Recuerda que las advertencias del compilador NO se consideran un fallo en la compilación. El compilador genera un fichero class perfectamente válido de la compilación, pero ha tenido la amabilidad de decirte estas palabras, "Yo seriamente espero que sepas lo que estas haciendo porque este código viejo no tiene el respeto (o incluso el conocimiento) de tu sintaxis <Integer> y puede hacer lo que sea con el ArrayList <Integer>. " Exam Watch Volviendo a nuestro ejemplo con el código legado que hace una inserción, tenga en cuenta que para ambas versiones del método insert() (una que añade un número entero y otra que añade un String) el compilador generara advertencias. El compilador no sabe si el método insert() esta añadiendo lo correcto (un Integer) o lo equivocado (un String). La razón por la que el compilador produce una advertencia se debe a que el método esta añadiendo algo a la colección! En otras palabras, el compilador sabe que hay una oportunidad en el método en la que podría añadir algo equivocado a la colección que el llamador piensa que es de tipo seguro. Exam Watch Hasta ahora, hemos analizado la forma en que el compilador genera advertencias si considera que existe una probabilidad de que tu colección de tipo seguro pueda ser perjudicada por código antiguo con tipos no seguros. Pero una de las preguntas que los desarrolladores se preguntan con frecuencia, "Bueno, seguro, lo compila, pero ¿por qué se ejecuta? ¿Por qué el código que inserta la cosa equivocada en mi lista funciona en tiempo de ejecución?" En otras palabras, ¿por qué dejar que la JVM permita a un viejo código almacenar un String en tu ArrayList <Integer>, sin ningún problema en absoluto? No hay excepciones, nada. Sólo un lugar tranquilo, detrás de las escenas, la total violación de tu tipo seguro puede no aparecer hasta el peor momento posible. Hay una gran verdad que necesitas saber para entender la razón por la que se ejecuta sin problemas -- la JVM no tiene ni idea de que tu ArrayList iba a almacenar únicamente Integers. La información de tipos no existe en tiempo de ejecución! Todo tu código genérico es estrictamente para el compilador. A través de un proceso llamado "Tipo de borrado," el compilador hace todas las comprobaciones en tu código genérico y después saca la información de tipos fuera de la clase bytecode. En tiempo de ejecución, TODOS los códigos de colecciones -tanto de código legado como los nuevos códigos de Java 5 donde se usan los genéricos-se ve exactamente igual que la preversión genérica de las colecciones. Ninguno de tu información de tipos existe en tiempo de ejecución. En otras palabras, incluso aunque escribas: recompilar con-xlint: sin marcar. Si lo haces, obtendrás algo como esto: List<Integer> myList = new ArrayList<Integer>(); List myList = new ArrayList(); javac -Xlint:unchecked TestBadLegacy.java TestBadLegacy.java:17: warning: [unchecked] unchecked call to add(E) as a member of the raw type java.util.List list.add(new String("42")); ^ 1 warning El compilador inserta incluso los castings por ti-los castings que tenias que hacer para obtener las cosas de un colección de pre-Java 5. Piensa en los genéricos estrictamente como una protección en tiempo de compilación. El compilador utiliza la información de tipo genérico (<tipo>) para asegurarse de que tu código no pone cosas equivocadas en una colección, y que no asignes lo que obtienes de una colección al tipo equivocado de referencia. Pero nada de esta protección existe en tiempo de ejecución. Esto es un poco diferente a los arrays, que le dan protección tanto en tiempo de compilación como en tiempo de ejecución. ¿Por qué lo hacen los genéricos de esta manera? ¿Por qué no hay información de tipo en tiempo de ejecución? Para apoyar código heredado. En tiempo de ejecución, las colecciones son colecciones al igual que los viejos tiempos. Lo que ganas con el uso de genéricos es la protección de compilación que garantice que no vas a poner algo equivocado en una colección tipificada y también elimina la necesidad de hacer un casting cuando obtienes algo, ya que el compilador ya sabe que sólo un Integer está saliendo de una lista de enteros. El hecho es, no necesitas protección en tiempo de ejecución… hasta que empieces a mezclar códigos genéricos y no genéricos, como lo hicimos en el ejemplo anterior. Entonces puedes tener los desastres en tiempo de ejecución. El único consejo que tenemos es prestar mucha atención a las advertencias del compilador: Cuando compilas con la opción Xlint:unchecked, el compilador te muestra exactamente qué método(s) podría estar haciendo algo peligroso. En este ejemplo, ya que el argumento lista no se ha declarado con un tipo, el compilador lo trata como código legal y no asume ningún riesgo de lo que el método ponga en la lista. En el examen, debes ser capaz de reconocer cuando se está compilando un código que producirá advertencias, pero aún compila. Y cualquier código que compile (incluso con advertencias)y funcione! Las violaciones de tipos no serán capturadas en tiempo de ejecución por la JVM, hasta que este tipo violaciónes se mezclen con tu código de alguna otra manera. En otras palabras, el acto de añadir un String a una lista <Integer> no fallará en tiempo de ejecución hasta que trates al String (que piensas que es un Integer) como un Integer. Tenga en cuenta, entonces, que el problema de poner la cosa equivocada en una colección tipificada (genérica) no aparece en el momento en que realmente hace el add() a la colección. Sólo se muestra más adelante, cuando tratas de usar algo de la lista y no coincide con lo que estabas esperando. Antes de Java 5, siempre se suponía que podrías obtener una cosa equivocada de una colección (ya que no todas eran de tipo seguro), por lo que se tomaban medidas defensivas adecuadas en el código. Una vez más, presta mucha atención a las advertencias del compilador, y estate preparado para ver temas como este aparecer en el examen. javac TestBadLegacy.java Note: TestBadLegacy.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. Exam Watch Esta advertencia del compilador no es muy descriptiva, pero la segunda nota indica que 4.4. Polimorfismo y Genéricos En el momento en que el compilador se hace con él, la JVM ve lo que siempre veía antes de Java 5 y los genéricos: Las colecciones genéricas te dan los mismos beneficios de tipos seguros que siempre has tenido con los arrays, pero hay algunas diferencias cruciales donde pueden pillarte si no estás preparado. La mayoría de estos tienen que ver con el polimorfismo. Ya has visto que el polimorfismo se aplica al tipo "base" de la colección: List<Integer> myList = new ArrayList<Integer>(); En otras palabras, fuimos capaces de asignar un ArrayList a una referencia List, porque List es un supertypo de ArrayList. Nada especial, por lo tanto, este asignamiento polimórfico funciona de la forma en que siempre trabaja Java, independientemente de la tipificación genérica. Pero ¿qué pasa con esto? class Parent { } class Child extends Parent { } List<Parent> myList = new ArrayList<Child>(); Piense por un minuto. Mantenga el pensamiento… No, no funciona. Hay una regla muy simple, la declaración del tipo de variables debe coincidir con el tipo de objeto que le pases. Si se declara "List<Foo> foo" entonces todo lo que asignes a la referencia foodebe ser del tipo genérico <Foo>. No un subtipo de <Foo>. Ni un supertype de <Foo>. <Foo> Solo. Las siguientes declaraciones son malas: List<object> myList = new ArrayList<JButton>(); // NO! List<Number> numbers = new ArrayList<Integer>(); // NO! // remember that Integer is a subtype of Number el polimorfismo se aplica aquí sólo al tipo "base". Y por "base", nos referimos al tipo de la clase collection-la clase que puede ser personalizada con un tipo. En este código, List<JButton> myList = new ArrayList<JButton>(); List y ArrayList son el tipo base y JButton es el tipo genérico. Por lo tanto, un ArrayList puede ser asignado a un List, pero una colección de <JButton> no puede ser asignada a una referencia de <Object>, aunque JButton es un subtipo de Object. La parte que se siente mal a la mayoría de los desarrolladores es que esta no es la forma en que trabaja con arrays, donde se te permite hacer esto, import java.util.*; class Parent { } class Child extends Parent { } public class TestPoly { public static void main(String[] args) { Parent[] myArray = new Child[3]; // yes } } lo que significa que estas autorizado a hacer esto Object[] myArray = new JButton[3]; // yes pero no esto: List<Object> list = new ArrayList<JButton>(); // NO! ¿Por qué son las normas para el tipificar arrays de las normas de tipificación genérica? Nos pondremos a resolverlo en un minuto. Por ahora, sólo graba en tu cerebro que el polimorfismo no funciona del mismo modo para los genéricos que para los arrays. Pero estas son buenas: List<JButton> myList = new ArrayList<JButton>(); // yes List<Object> myList = new ArrayList<Object>(); // yes List<Integer> myList = new ArrayList<Integer>(); // yes Hasta ahora todo va bien. Simplemente mantener el tipo genérico de la referencia y el tipo genérico del objeto al que se refiere idénticos. En otras palabras, 4.5. Métodos genéricos Si no estas familiarizado ya con los genéricos, es posible que te sientas muy incómodo con las consecuencias de lo anterior (asignaciones para tipos genéricos no polimórfico). ¿Y por qué no debes estar incómodo? Uno de los mayores beneficios del polimorfismo es que puedes declarar, por ejemplo, el argumento de un método de un tipo particular y en tiempo de ejecución puedes tener ese argumento haciendo referencia a cualquier subtipo incluidos los que uno no sabe la fecha en la que escribió el método con el argumento supertipo. Por ejemplo, imagine que un clásico (simplificado) polimorfismo, por ejemplo de una clase veterinario (AnimalDoctor) con un método checkup(). Y ahora mismo, tienes tres subtipos de Animal - Dog, Cat y Bird - y cada uno implementan el método abstract checkup() Animal: abstract class Animal { public abstract void checkup(); } class Dog extends Animal { public void checkup() { // implement Dog-specific code System.out.println("Dog checkup"); } } class Cat extends Animal { public void checkup() { // implement Cat-specific code System.out.println("Cat checkup"); } } class Bird extends Animal { public void checkup() { // implement Bird-specific code System.out.println("Bird checkup"); } } Olvida las colecciones/arrays por un momento, imagínate que la clase AnimalDoctor necesita tener un código que tome cualquier tipo de animal e invoque al método checkup() de Animal. Tratar de sobrecargar el método checkup() de la clase Animal para cada posible tipo de animal es ridículo y obviamente, no extensible. ¡Tendrias que cambiar la clase AnimalDoctor cada vez que añadieses un nuevo subtipo de Animal. De modo que en la clase AnimalDoctor, probablemente tengas un método polimórfico: public void checkAnimal(Animal a) { a.checkup(); // does not matter which animal subtype each // Animal's overridden checkup() method runs } Y por supuesto queremos que AnimalDoctor también tenga código que pueda tener arrays de Dogs, Cats o Birds. Una vez más, no queremos sobrecargar los métodos con arrays para cada subtipo de Animal, por lo que usamos polimorfismo en la clase AnimalDoctor: public void checkAnimals(Animal[] animals) { for(Animal a : animals) { a.checkup(); } } Aquí está el ejemplo completo con una prueba de polimorfismo de array que toma cualquier tipo de array animal (Dog [], Cat [], Bird []). import java.util.*; abstract class Animal { public abstract void checkup(); } class Dog extends Animal { public void checkup() { // implement Dog-specific code System.out.println("Dog checkup"); } } class Cat extends Animal { public void checkup() { // implement Cat-specific code System.out.println("Cat checkup"); } } class Bird extends Animal { public void checkup() { // implement Bird-specific code System.out.println("Bird checkup"); } } public class AnimalDoctor { // method takes an array of any animal subtype public void checkAnimals(Animal[] animals) { for(Animal a : animals) { a.checkup(); } } public static void main(String[] args) { // test it Dog[] dogs = (new Dog(), new Dog()}; Cat[] cats = (new Cat(), new Cat(), new Cat()); Bird[] birds = (new Bird()); AnimalDoctor doc = new AnimalDoctor(); doc.checkAnimals(dogs); the Dog[] doc.checkAnimals(cats); the Cat[] // pass // pass doc.checkAnimals(birds); // the Bird[] } } pass Esto funciona bien, por supuesto. Pero aquí está la razón por la que traemos este repaso - este enfoque no funciona del mismo modo con las colecciones de tipo seguro! En otras palabras, un método que toma, por ejemplo, un ArrayList <Animal> no será capaz de aceptar una colección de cualquier subtipo de Animal! Esto significa que ArrayList<Dog> no puede ser pasado a un método con un argumento de ArrayList<Animal>, aunque ya sepamos que esto funciona muy bien con los arrays. Es evidente que esta diferencia entre arrays y ArrayList es coherente con el polimorfismo en las reglas de asignación que ya estudiaste el hecho de que no se puede asignar un objeto de tipo ArrayList <JButton> a un List <Object>. Pero aquí es donde realmente comienzas a sentir el dolor de la distinción entre arrays y colecciones tipificadas. Sabemos que no funcionará correctamente, pero vamos a intentar modificar el código de AnimalDoctor para usar genéricos en lugar de arrays: public class AnimalDoctorGeneric { // change the argument from Animal[] to ArrayList<Animal> public void checkAnimals(ArrayList<Animal> animals) { for(Animal a : animals) { a.checkup(); } } public static void main(String[] args) { // make ArrayLists instead of arrays for Dog, Cat, Bird List<Dog> dogs = new ArrayList<Dog>(); dogs.add(new Dog()); dogs.add(new Dog()); List<Cat> cats = new ArrayList<Cat>(); cats.add(new Cat()); cats.add(new Cat()); List<Bird> birds = new ArrayList<Bird>(); birds.add(new Bird()); // this code is the same as the Array version AnimalDoctorGeneric doc = new AnimalDoctorGeneric(); // this worked when we used instead of ArrayLists doc.checkAnimals(dogs); // List<Dog> doc.checkAnimals(cats); // List<Cat> doc.checkAnimals(birds); // List<Bird> } } arrays send a send a send a Entonces, ¿qué ocurre? javac AnimalDoctorGeneric.Java AnimalDoctorGeneric.Java:51: checkAnimals(Java.util. ArrayList<Animal>) in AnimalDoctorGeneric cannot be applied to (java.util.List<Dog>) doc.checkAnimals(dogs); ^ AnimalDoctorGeneric.Java: 52: checkAnimals(java.util. ArrayList<Animal>) in AnimalDoctorGeneric cannot be applied to (java.util.List<Cat>) doc.checkAnimals(cats); ^ AnimalDoctorGeneric.Java:53: checkAnimals(java.util. ArrayList<Animal>) in AnimalDoctorGeneric cannot be applied to (java.util.List<Bird>) doc.checkAnimals(birds); ^ 3 errors El compilador nos para con los errores, no con advertencias. Simplemente no puedes asignar cada uno de los ArrayLists de los subtipos Animal (<Dog>, <Cat>, o <Bird>) a un ArrayList del supertipo <Animal>, que se declaró como tipo del argumento. Este es uno de los mayores errores de los programadores de Java que están tan familiarizados con el uso de polimorfismo con arrays, donde el mismo escenario (Animal [] se puede referir a Dog [], Cat [], o Bird []) funciona como cabría esperar. Así que tenemos dos problemas reales: 1.¿Porque no funciona? 2.¿Como llegar a su alrededor? Primero, ¿por qué no puedes hacerlo si funciona para los arrays? ¿Por qué no puedes pasar una ArrayList <Dog> a un método con un argumento ArrayList <Animal>? Llegaremos, pero primero demos un paso atras por un minuto y consideremos este escenario perfectamente legal: Animal[] animals = new Animal[3]; animals[0] = new Cat(); animals[1] = new Dog(); Parte del beneficio de declarar un array usando un supertipo más abstracto es que el array pueda almacenar objetos de múltiples subtipos del supertipo y, a continuación, se puede manipular el array asumiendo todo lo que puede responder a la interfaz Animal (en otras palabras, todo en el array puede responder a llamadas de método definidas en la clase Animal). Así que aquí, que estamos utilizando polimorfismo no para el objeto que el array referencia, sino más bien lo que el array puede ALMACENAR - en este caso, cualquiera de los subtipos de Animal. Puedes hacer lo mismo con los genéricos: List<Animal> animals = new ArrayList<Animal>(); animals.add(new Cat()); // OK animals.add(new Dog()); // OK Por lo tanto, esta parte funciona con ambos, arrays y colecciones genéricas-podemos añadir la instancia de un subtipo en un array o colección declarada con un supertipo. Puedes añadir Dogs y Cats a un array Animal (Animal []) o una colección de Animal (ArrayList <Animal>). Y con arrays, esto se aplica a lo que ocurre dentro de un método: public void addAnimal(Animal[] animals) { animals[0] = new Dog(); // no problem, any Animal works // in Animal [] } Por lo tanto, si esto es cierto, y si puedes poner Dogs en un ArrayList<Animal>, ¿por qué no se puede utilizar ese mismo tipo de método? ¿Por qué no puedes hacer esto? public void addAnimal(ArrayList<Animal> animals) { animals.add(new Dog()); // sometimes allowed... } Actualmente, puedes hacerlo bajo ciertas condiciones. El código anterior compilará bien si lo que pasas al método es también un ArrayList <Animal>. Esta es la parte en el que difiere de arrays, ya que en la versión array, puede pasar un Dog[] a un método que toma un Animal []. Lo único que le puede pasar a un método cuyo argumento es ArrayList<Animal> es un ArrayList <Animal>! (Asumiendo que no están tratando de pasar un subtipo de ArrayList, ya que-recordemos el tipo "base" puede ser polimórfico.) La cuestión esta ahi todavía, por lo tanto, ¿por qué es esto malo? ¿Y por qué es malo para ArrayList pero para arrays no? ¿Por qué no puedes pasar una ArrayList<Dog> a un argumento de ArrayList<Animal>? En realidad, el problema es igual de peligroso si estas usando arrays o una colección genérica. Es sólo que el compilador y la JVM se comportan de forma diferente para arrays que para colecciones genéricas. La razón es que es peligroso pasar una colección (array o ArrayList), de un subtipo a un método que tome una colección de un supertipo, se debe a que podrías añadir algo. Y eso significa que puedes añadir algo equivocado! Esto es probablemente la verdad evidente, pero sólo en caso (y reforzar), vamos a caminar a través de algunos escenarios. El primero de ellos es simple: public void foo() { Dog[] dogs = {new Dog(), new Dog()}; addAnimal(dogs); // no problem, send the Dog[] to the method } public void addAnimal(Animal[] animals) { animals[0] = new Dog(); // ok, any Animal subtype works } Esto no es ningún problema. Pasamos Dog[] al método, y añadió un Dog al array (que fue permitido ya que el parámetro del método era de tipo Animal[], que puede contener cualquier subtipo de Animal). Pero, ¿y si cambiamos el código para llamar a public void foo() { Cat[] cats = {new Cat(), new Cat()}; addAnimal(cats); // no problem, send the Cat[] to the method } y el método original se mantiene igual: public void addAnimal(Animal[] animals) { animals[0] = new Dog(); // Eeek! We just put a Dog // in a Cat array! } El compilador considera que es perfectamente posible añadir un Dog a un array Animal[], ya que un Dog puede ser asignado a una referencia Animal. El problema es, si se lo pasas a un array de un subtipo de Animal (Cat, Dog, o Bird), el compilador no lo sabe. El compilador no se da cuenta de que en alguna parte de la memoria heap hay un array de tipo Cat[], no Animal[] y que estas a punto de tratar de añadir un Dog. Para el compilador, la has pasado un array de tipo Animal, por lo que no tiene manera de reconocer el problema. Este es el escenario que estamos tratando de evitar, independientemente de si se trata de un array o un ArrayList. La diferencia es, el compilador te permite salirte con la tuya para arrays, pero no para las colecciones genéricas. La razón por la que el compilador no te deja pasar un ArrayList<Dog> a un método que toma un ArrayList<Animal>, es porque dentro del método el parámetro es de tipo ArrayList <Animal>, y significa que podrías poner cualquier tipo de Animal en el. No habría manera de que el compilador te impidiese poner un Dog en una lista que fue inicialmente declarada como <Cat>, pero ahora esta referenciando al parámetro <Animal>. Todavía tenemos dos preguntas… ¿cómo se puede conseguir a su alrededor y por qué la comprobación que el compilador hace te permite tomar ese riesgo para arrays, pero no para ArrayList (o cualquier otra colección genérica )? La razón por la que esta permitido en los arrays se debe a que existe una excepción en tiempo de ejecución (ArrayStoreException) que te impedirá poner mal el tipo de objeto en un array. Si envías un array Dog al método que toma un array Animal y añades sólo Dogs (Dog incluidos los subtipos, por supuesto) en el array ahora referenciado por Animal, no hay problema. Pero si intentas añadir un Cat al objeto que esta actualmente que es un array Dog, obtendrás la excepción. Pero no hay ninguna excepción equivalente para los genéricos, porque los tipos se borran! En otras palabras, en tiempo de ejecución la JVM conoce el tipo de arrays, pero no sabe el tipo de una colección. Toda la información de tipo genérico se elimina durante la compilación, por lo que cuando llega a la JVM, sencillamente no hay forma de reconocer el desastre de poner un Cat en un ArrayList<Dog> y viceversa. Por lo tanto, esto actualmente es código legal: public void addAnimal(List<Animal> animals) { animals.add(new Dog()); // this is always legal, // since Dog can // be assigned to an Animal // reference } public static void main(String[] args) { List<Animal> animals = new ArrayList<Animal>(); animals.add(new Dog()); animals.add(new Dog()); AnimalDoctorGeneric doc = new AnimalDoctorGeneric(); doc.addAnimal(animals); // OK, since animals matches // the method arg } Mientras la única cosa que le pases al método addAnimals (List <Animal>) sea un ArrayList <Animal>, el compilador estará contento-a sabiendas de que cualquier subtipo Animal que añada será válido (siempre puede añadir un Dog a una colección de Animal , Yada, Yada, Yada). Pero si intentas invocar addAnimal() con un argumento de cualquier otro tipo ArrayList, el compilador te va a detener, ya que en tiempo de ejecución la JVM no tendrá manera de detener la inserción de que un Dog a lo que fue creado como una colección Cat. Por ejemplo, este código que cambia el tipo genérico a <Dog>, pero sin cambiar el método addAnimal (), no compilara: public void addAnimal(List<Animal> animals) { animals.add(new Dog()); // still OK as always } public static void main(String[] args) { List<Dog> animals = new ArrayList<Dog>(); animals.add(new Dog()); animals.add(new Dog()); AnimalDoctorGeneric doc = new AnimalDoctorGeneric(); doc.addAnimal(animals); // THIS is where it breaks! } El compilador dice algo así como: javac AnimalDoctor.Generic.java AnimalDoctorGeneric.java:49: addAnimal(java.util.List<Animal>) in AnimalDoctorGeneric cannot be applied to (java.util. List<Dog>) doc.addAriimal (animals) ; ^ 1 error Tenga en cuenta que este mensaje es prácticamente el mismo que obtuvimos intentando llegar a invocar cualquier método con un argumento equivocado. Es decir que simplemente no puedes invocar addAnimal (List <Animal>) usando algo cuya referencia fue declarada como List <Dog>. (Es el tipo de referencia, no el tipo de objeto lo que importa-pero recuerda-el tipo genérico de un objeto siempre es el mismo que el tipo genérico declarado en la referencia. List <Dog> puede referirse únicamente a las colecciones que son subtipos de List , Pero que fueron instanciadas con el tipo genérico <Dog>.) Una vez más, recuerda que una vez dentro del método addAnimals(), todo lo que importa es el tipo de parámetro, en este caso, List<Animal>. Volvemos a la pregunta clave-¿cómo podemos obtener esto? Si el problema está relacionado sólo con el peligro de añadir la cosa equivocada a la colección, ¿qué pasa con el método checkup() que usen una colección como de sólo lectura? En otras palabras, ¿qué pasa con los métodos que invocan a métodos Animal en cada cosa en la colección, que funcionará independientemente de que tipo de ArrayList subtipo se pase? Y eso es una pista! Es el método add(), que es el problema, así que lo que necesitamos es una forma de decirle al compilador, "Hey, estoy usando la colección pasada para invocar métodos con los elementos-y prometo no añadir nada en la colección. " Y hay un mecanismo para decirle al compilador que puedes tomar cualquier subtipo genérico de la declaración del tipo de argumento porque no vas a poner nada en la colección. Y ese mecanismo es el comodín <?>. El método pasa de: public void addAnimal(List<Animal> animals) a public void addAnimal(List<? extends Animal> animals) Al decir <? extends Animal>, estamos diciendo, "Puedo asignar una colección que es un subtipo de List y con tipo Animal o cualquier cosa que extienda Animal. Y que no voy a añadir nada en la colección." Por lo tanto, por supuesto, el método addAnimal () anterior en realidad no compilar, incluso con el comodín, ya que ese método añade algo: public void addAnimal(List<? extends Animal> animals) { animals.add(new Dog()); // NO! Can't add if we // use <? extends Animal> } Obtendrás un error muy extraño que pudiera parecer algo como esto: javac AnimalDoctorGeneric.java AnimalDoctorGeneric.java:38: cannot find symbol symbol : method add(Dog) location: interface java.util.List<capture of ? extends Animal> animals.add(new Dog()); ^ 1 error que básicamente dice, "no se puede añadir un Dogaquí." Si cambiamos el método de forma que no añada nada, funcionara. Pero espere-hay más. En primer lugar, el significado de <? extends Animal> es que puedes tomar cualquier subtipo de Animal, sin embargo o un tipo que implementa la interfaz después de la palabra extends. En otras palabras, la palabra clave extends en el contexto de un comodín representa subclases e implementaciones de interfaz. No hay sintaxis <? implement Serializable>. Si desea declarar un método que tenga cualqueir cosa que sea de un tipo que implementa Serializable, usaras extends de este modo: public void addAnimal(List<? extends Animal> animals) { animals.add(new Dog()); // NO! Can't add if we // use <? extends Animal> } Esto parece extraño ya que nunca diras esto en la declaración de una clase Serializable porque es una interfaz, no una clase. Pero asi es la sintaxis. Una vez más-sólo hay una palara clave comodín que representa tanto a las implementaciones de interfaz como a las subclases. Y esta palabra clave es extends. Pero cuando lo ves, piensan "IS-A", como en algo que pase la prueba de instanceof. Sin embargo, hay otro escenario donde se puede utilizar un comodín Y aún añadir algo a la colección, pero de forma segura-la palabra clave super. Imaginate, por ejemplo, que declaras el método de esta manera: public void addAnimal(List<? super Dog> animals) { animals.add(new Dog()); // adding is sometimes OK with super } public static void main(String[] args) { List<Animal> animals = new ArrayList<Animal>(); animals.add(new Dog()); animals.add(new Dog()); AnimalDoctorGeneric doc = new AnimalDoctorGeneric(); doc.addAnimal(animals); // passing an Animal List } Ahora lo que dice con esta línea public void addAnimal(List<? super Dog> animals) es esencialmente, "Hey compilador, por favor, acepta cualquier List con un tipo genérico que es del tipo Dog, o un supertipo de Dogs. Nada más bajo en la arbol de herencia puede entrar, pero algo superior a Dog es correcto." Probablemente ya reconozcas por qué esto funciona. Si se pasa una lista de tipo Animal, entonces es perfectamente posible añadir un Dog a la misma. Si se pasa una lista de tipo de Dog, es perfectamente posible añadir un Dog a la misma. Y si pasas una lista del tipo Object, es aún válida para añadir un Dog a la misma. Cuando usas la sintaxis <? super…> sintaxis, estás diciendo que el compilador puede aceptar el tipo en el lado derecho de super o cualquiera de sus supertipos, ya que una colección declarada como cualquier supertipo de Dog será capaz de aceptar un Dog como un elemento. List <Object> puede tomar un Dog. List <Animal> puede tomar un Dog. Y List<Dog> puede tomar un Dog. Por lo tanto, cualquier pasando cualquiera de estos funcionara. Por lo tanto, la palabra clave super con la notación comodín te permitira tener una restringiao, pero aún posible forma de añadir a una colección. Por lo tanto, el comodín te da asignaciones polimórficas, pero con ciertas restricciones que no tiene para arrays. Rápida pregunta: estas dos lineas son idénticas? public void foo(List<?> list) { } public void foo(List<Object> list) { } Si hay una diferencia (y no estamos diciendo todavía existe), ¿qué es? Hay una gran diferencia. List<?>, que es el comodín <?> sin las palabras clave se extends o super, simplemente significa "cualquier tipo". Por lo tanto, eso significa que cualquier tipo de lista se le puede asignar al argumento. Esa podría ser una lista de <Dog>, <Integer>, <JButton>, <Socket>, lo que sea. Y utilizando el comodín, sin la palabra clave super (seguida por un tipo), significa que no puedes añadir nada más a la lista a que se refiere como la List<?>. List<Object> es completamente diferente de List<?>. List<Object> significa que el método puede tomar sólo un List<object>. No un List<Dog> ni un List<Cat>. Sin embargo, significa que usted puede agregar a la lista, ya que el compilador ha hecho ya la certeza de que estás pasando sólo válido List<object> al método. Sobre la base de las anteriores explicaciones, averiguar si el siguiente funcionara: import java.util.*; public class TestWildcards { public static void main(String[] args) { List<Integer> myList = new ArrayList<Integer>(); Bar bar = new Bar(); bar.doInsert(myList); } } class Bar { void doInsert(List<?> list) { list.add(new Dog()); } } Si no es así, ¿dónde está el problema? El problema está en el método list.add() dentro de doInsert(). El comodín <?> permite una lista de cualquier tipo que se pase al método, pero el método add() no es válido, por las razones que hemos explicado anteriormente (que se puede poner cosas equivocadas en la colección). Así que esta vez, la clase TestWildcards está muy bien, pero el Bar clase no compila porque lo hace un add() en un método que utiliza un comodín (sin super). ¿Qué ocurre si cambiamos el doInsert() por este método: public class TestWildcards { public static void main(String[] args) { List<Integer> myList = new ArrayList<Integer>(); Bar bar = new Bar(); bar.doInsert(myList); } } Ahora va a funcionar? Si no es así, ¿por qué no? Esta vez, la clase Bar, con el método doInsert (), compila bien. El problema es que el código TestWildcards está tratando de pasar una lista de <Integer> a un método que puede tomar sólo una lista <Object>. Por cierto, List<? extends Object> y List <?> son absolutamente idénticos! Ambos dicen, "Me puedo referir a cualquier tipo de objeto." Pero, como se puede ver, ninguno de ellos son las mismas que List<Object>. Una manera de recordar esto es que si ves el comodín (un signo de interrogación?), Significa "muchas posibilidades". Si no ves el signo de interrogación, entonces significa que solo soporta el <tipo> y absolutamente nada más. List<Dog> significa List<Dog> y no List<Beagle>, List<Poodle> o cualquier otro subtipo de Dog. Pero List<? extends Dog> podría significar List<Beagle>, List<Poodle>, y así sucesivamente. Por supuesto List<?> puede ser desde cualquier cosa… a todos. Tenga en cuenta que los comodines puede usarse sólo para declaraciones de referencia (incluyendo argumentos, variables, tipos de retorno, y así sucesivamente). No pueden ser utilizados como tipo de parametro cuando se crea una nueva colección tipificada. Piensa en ello-, mientras que una referencia puede ser abstracta y polimórfica, el objeto creado debe ser de un tipo específico. Tienes que cerrar el tipo cuando crees el objeto usando new. Hagamos un repsao antes de salir de hablar de los genéricos, observa las siguientes sentencias y averigua si compilará: 1) List <?> list = new ArrayList<Dog>(); 2) List <? extends Animal> aList = new ArrayList<Dog>(); 3) List <?> foo = new ArrayList <? extends Animal> (); 4) List <? extends Dog> cList = new ArrayList <Integer> (); 5) List <? super Dog> bList = new ArrayList <Animal> (); 6) List <? super Animal> dList = new ArrayList <Dog> (); Las respuestas correctas (las sentencias que compilan ) son 1, 2 y 5. Los tres que no compilan Sentencia: List <> foo = new ArrayList <? extends Animal> (); Problema: no puedes usar la notación comodín en la creación del objeto. Por lo tanto, el nuevo ArrayList <? extends Animal> () no compilara. Sentencia: List<? extends Dog> cList = new ArrayList <Integer> (); Problema: No puedes asignar una lista de Integer a una referencia que toma sólo Dog (incluidos los subtipos de perro, por supuesto). Sentencia: List<? super Animal> dList = new ArrayList <Dog> (); Problema: No puedes asignar un Dog a <? Super Animal>. La clase Dog es demasiado "baja" en la jerarquía de clases. Sólo <Animal> o <Object> habría sido legal. 4.6. Declaraciones genérico Hasta ahora, hemos hablado de cómo crear colecciones de tipo seguro, y la forma de declarar variables de referencia incluyendo los argumentos y tipos de retorno usando la sintaxis de genéricos. Pero aquí están algunas preguntas: ¿Cómo podemos saber que esta permitido/ supone que especificar un tipo de estas clases de colección? ¿Y el trabajo genérico funciona cualquier otras clases en la API? Y, por último, podemos declarar nuestras propias clases como los tipos genéricos? En otras palabras, podemos hacer una clase que exige que alguien pasa un tipo en cuando y declarar que ejemplifican? En primer lugar, el que, evidentemente, sabemos la respuesta - la API te dice que cuando un tipo parametrizados se espera. Por ejemplo, esta es la API de declaración de la interfaz java.util.List: public interface List<E> La <E> es un depósito para el tipo que le pases. La interfaz List se comporta como un genérico y al escribir tu código lo cambias de una lista genérica a una lista <Dog> O Lista <Integer>, y así sucesivamente. La E, por cierto, es sólo una convención. Cualquier identificador de Java valido funcionaria aquí, pero E significa "Elemento" y se usa cuando la plantilla es una colección. La otra convención principal es T (significa "tipo"), utilizados para las cosas que no son colecciones. Ahora que has visto la declaración de interfaz para la lista, ¿que piensas de un método add() como este? boolean add(E o) En otras palabras, cualquiera que sea E es cuando declaras la Lista, que es lo que puedes agregar a ella. Por lo tanto, imagínate este código: List<Animal> list = new ArrayList<Animal>(); El E en la API de List de repente tiene su forma de onda se derrumbó, y va desde lo abstracto <your tipo va aquí>, a una lista de los animales. Y si se trata de una lista de los animales, a continuación, añadir el () método de la lista, obviamente, debe comportarse de esta manera: boolean add(Animal a) Cuando usted mira a una API genéricos para una clase o interfaz, elegir un tipo de parámetros (Dog, JButton, incluso de objetos) y hacer un mental para buscar y reemplazar en cada caso E (o lo que sea identificador se usa como marcador de posición para el tipo de parámetro ). 4.7. Creando tu propia clase genérica Vamos a intentar hacer nuestra propia clase genérica, para tener una idea de cómo funciona y, a continuación vamos a ver unos cuantos detalles restantes de la sintaxis de genéricos. Imagina que alguien crea una clase Rental, que gestiona un grupo de items alquilables. public class Rental { private List rentalPool; private int maxNum; public Rental(int maxNum, List rentalPool) { this.maxNum = maxNum; this.rentalPool = rentalPool; } public Object getRental() { // blocks until there's something available return rentalPool.get(0); } public void returnRental(Object o) { rentalPool.add(o); } } Ahora imaginate que quieres hacer una subclase de Rental que se trata de alquilar coches. Usted podría empezar con algo como esto: import java.util.* ; public class CarRental extends Rental { public CarRental(int maxNum, List<Car> rentalPool) { super(maxNum, rentalPool); } public Car getRental() { return (Car) super.getRental(); } public void returnRental(Car c) { super.returnRental(c); } public void returnRental(Object o) { if (o instanceof Car) { super.returnRental(o); } else { System.out.println("Cannot add a non-Car"); // probably throw an exception } } } 1. Estas haciendo tu propio tipo de control en el método returnRental(). No puedes cambiar el tipo de argumento returnRental() para tomar un coche, ya que se trata de una sobrescritura (no una sobrecarga) del método de la clase de alquiler. (Sobrecargando quitaria la flexibilidad con polimórficos en Alquiler). 2. Realmente no quieres hacer subclases separadas para cada posible tipo de cosa alquilable (coches, ordenadores, zapatos de bolera, los niños, y así sucesivamente). Pero dada tu naturaleza brillante (mayor que este escenario inventado), rápidamente te das cuenta de que puede hacer que la clase Rental sea un tipo de clase genérica (una plantilla para cualquier tipo de cosa alquilable). (Dijimos inventado… ya que en realidad, puedes querer tener comportamientos diferentes para distintos tipos de cosas alquilables, pero incluso podría ser resuelto limpiamente a través de algún tipo de comportamiento de composición en contraposición a la herencia (mediante el patrón de diseño de estrategia, por ejemplo). Y no, los patrones de diseño no entran en el examen, pero debes leer nuestro libro de patrones de diseño. Así que aquí tiene su nueva y mejorada clase genérica Rental: // returns a T rentalPool.add(returnedThing); } } import java.util.*; public class RentalGeneric<T> { for the type kathy% javacl.5 RentalGeneric.java RentalGeneric.Java:38: cannot find symbol symbol : method add(Cat) location: interface java.util.List<Car> carList.add(new Cat("Fluffy")); ^ 1 error // "T" is // parameter private List<T> rentalPool; the class type for the // Use // List type private int maxNum; public RentalGeneric( int maxNum, List<T> rentalPool) { // constructor takes a // List of the class type this.maxNum = maxNum; this.rentalPool = rentalPool; } public T getRental() { // we rent out a T // blocks until there's something available return rentalPool.get(0); } public void returnRental(T returnedThing) { // and the renter Vamos a ponerlo a prueba: class TestRental { public static void main (String[] args) { //make some Cars for the pool Car cl = new Car(); Car c2 = new Car(); List<Car> carList = new ArrayList<Car>(); carList.add(cl); carList.add(c2); RentalGeneric<Car> carRental = new RentalGeneric<Car>(2, carList); // now get a car out, and it won't need a cast Car carToRent = carRental.getRental(); carRental.returnRental(carToRent) ; // can we stick something else in the original carList? carList.add(new Cat("Fluffy")); } } Hemos obtenido un error: Ahora tenemos una clase Rental que puede ser tipificada a lo que el programador desee, y el compilador hará cumplir. En otras palabras, funciona igual que las clases Collections. Veamos más ejemplos de sintaxis de genérico podría encontrar en la API o código fuente. Aquí hay otra clase simple que utiliza el tipo parametrizados de la clase de varias maneras: public class TestGenerics<T> { class type T anInstance; instance variable type T [] anArrayOfTs; array type // as the // as an // as an TestGenerics(T anInstance) { // as an argument type this.anInstance = anInstance; } T getT{} { // as a return type return anInstance; } } AnimalHolder<Integer> x = new AnimalHolder<Integer>(); // NO! } } 4.8. Creación de métodos genéricos Obviamente este es un uso ridículo de los genéricos, y de hecho verás genéricos sólo en contadas ocasiones fuera de las colecciones. Pero, lo que haces es necesario para comprender los distintos tipos de sintaxis genérico que podría encontrarse, por lo que vamos a seguir con estos ejemplos hasta que hayamos cubierto todas ellas. Puede utilizar más de un tipo parametrizado en una única definición de clase: public class UseTwo<T, X> { T one; X two; UseTwo(T one, X two) { this.one = one; this.two = two; } T getT() { return one; } X getX() { return two; } // test it by creating it with <String, Integer> public static void main (String[] args) { UseTwo<String, Integer> twos = new UseTwo<String, Integer> ("foo", 42); String theT = twos.getT(); // returns a String int theX = twos.getX{}; // returns Integer, unboxes to int } } Y puede utilizar una forma de notación comodín en una definición de clase, para especificar un rango (llamados "límites") para los tipos que se pueden utilizar para el tipo de parámetros: public class AnimalHolder<T extends Animal> { // use "T" instead // of "?" T animal; public static void main(String[] args) { AnimalHolder<Dog> dogHolder = new AnimalHolder<Dog>(); // OK Hasta ahora, cada ejemplo que hemos visto utiliza el parámetro de tipo clase-el tipo declarado con el nombre de clase. Por ejemplo, en la delcaración UseTwo <T,X> , hemos utilizado la T y X marcadores de posición a lo largo del código. Pero es posible definir un tipo parametrizados en un nivel más granular -- un método. Imagina que quieres crear un método que toma una instancia de cualquier tipo, como ejemplo un ArrayList de ese tipo, y añade la instancia al ArrayList. La clase en sí no tiene por qué ser genérica; básicamente sólo queremos un método de utilidad que le puede pasar a un tipo y que puede utilizar ese tipo para construir un tipo de seguridad en la recogida. Utilizando un método genérico, podemos declarar el método sin un tipo específico y, a continuación, obtener el tipo de información basada en el tipo del objeto pasado al método. Por ejemplo: import java.uti1.*; public class CreateAnArrayList { public <T> void makeArrayList(T t) { // take an object of an // unknown type and use a // "T" to represent the type List<T> list = new ArrayList<T>(); // now we can create the // list using "T" list.add(t); } } En el código anterior, si invocas al método makeArrayList () con una instancia Dog, el método se comportará como si se parecíese a este: public void makeArrayList(Dog t) { List<Dog> list = new ArrayList<Dog>(); list.add(t); } Y, por supuesto, si invocas al método con un Integer, entonces la T se sustituirá por Integer (no en el bytecode, recuerde - estamos describiendo la forma en que parece comportarse, no la forma en que realmente se realiza). La cosa más extraña sobre los métodos genéricos es que debes declarar el tipo de variable ANTES del tipo de retorno del método: public <T> void makeArrayList(T t) La <T> antes de void simplemente define que T esta antes de usarla como un tipo en el argumento. Debes declarar el tipo como que, a menos que el tipo se especifica para la clase. En CreateAnArrayList, la clase no es genérica, por lo que no hay parámetro de tipo de posición que podamos utilizar. Estas también libre de poner límites en el tipo que declares, por ejemplo, si deseas restringir el método makeArrayList() sólo a Números o sus subtipos (Integer, Float, etc) le diría: public <T extends Number> void makeArrayList(T t) Exam Watch Exam Watch Exam Watch Lo más probable es que el 98% de los genéricos que veas es simplemente declarando el tipo y el uso seguro de colecciones, incluyendo la de utilizarlos como argumentos. Pero ahora sabes mucho más (pero no por ello todo) de la forma de funcionar de los genéricos. Si esto es claro y fácil para ti, eso es excelente. Si es doloroso…… sólo sé que la adición de los genéricos en el lenguaje Java casi provocó una rebelión entre algunos de los más experimentados desarrolladores de Java.