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.