Está en la página 1de 5

Genericos

Los genéricos se han añadido a Java. Antes de los genéricos, se tenia un cast para todos los objetos que
se leia de una colección. Si alguien accidentalmente inserta un objeto del tipo incorrecto, el cast podría
fallar en tiempo de ejecución.

Con los genéricos, se indica al compilador qué tipos de objetos están permitidos en cada colección. El
compilador inserta cast para usted de forma automática y le dice que en tiempo de compilación si se
intenta insertar un objeto del tipo incorrecto. Esto da lugar a programas que son a la vez más seguro y
más claro, pero estos beneficios vienen con complicaciones.

Tema 23: No utilice tipos abiertos en el nuevo código

En primer lugar, algunos términos. Una clase o interfaz cuya declaración tiene uno o más tipos de
parámetros es una clase o interfaz generica. Por ejemplo, a partir de la versión 1.5, la interfaz de la lista
tiene un parámetro de tipo único, E, lo que representa el tipo de elemento de la lista. Técnicamente, el
nombre de la interfaz es ahora List<E> (léase "lista de E"), pero la gente suele llamarlo la lista de tipo.
Las clases y las interfaces genéricas son conocidos colectivamente como tipos genéricos.

Cada tipo genérico define un conjunto de tipos de parámetros, que consisten en el nombre de clase o
interfaz seguido por una lista entre corchetes de ángulo de parámetros de tipo real que corresponde a los
parámetros de tipo genérico de tipo formal.

Por ejemplo, List <String> (léase "lista de String") es un tipo de parámetros que representan una lista
cuyos elementos son de tipo String. (String es el tipo de parámetro actual que corresponde al parámetro
de tipo formal E.)

Por último, cada tipo genérico define un tipo abierto, que es el nombre del tipo genérico que se utiliza sin
ningún tipo de parámetros reales que acompañan. Por ejemplo, el tipo abierto correspondientes a
List<E> es List. Los tipos abiertos se comportan como si toda la información de tipo genérico fueron
borrados de la declaración de tipo. Para todos los propósitos prácticos, el tipo abierto List se comporta del
mismo modo que la interfaz de tipo List que antes los genéricos se han añadido a la plataforma.

Antes de la versión 1.5, esto habría sido un ejemplo de una declaración de colección:

Si accidentalmente puso una moneda en su colección sellos, la inserción errónea compila y se ejecuta sin
errores:

No es un error hasta que recupere la moneda de la colección sellos:

Como se ha mencionado en este libro, vale la pena descubrir los errores lo más pronto posible después
de que se hacen, a ser posible en tiempo de compilación. En este caso, no se descubre el error en tiempo
de ejecución hasta que, mucho después de que ha pasado, y en el código que está muy lejos de el código
que contiene el error. Una vez que vea la ClassCastException, usted tiene que buscar a través de la base
de código en busca de la invocación del método que puso a la moneda en la colección sellos. El
compilador no puede ayudarle, porque no puede entender el comentario que dice: "Contiene casos sólo
sello."

Con los genéricos, se reemplaza el comentario con una declaración de tipo de mejora de la colección que
le dice al compilador de la información que estaba escondido previamente en el comentario:
A partir de esta declaración el compilador sabe que los sellos deben contener sólo las instancias del sello
y de las garantías que éste sea el caso, asumiendo que su base de código completo se compila con un
compilador de la versión 1.5 o posterior y compila todo el código sin emitir (o suprimir, véase el punto 24 )
ninguna advertencia. Cuando los sellos se declara con un tipo de parámetros, la inserción errónea genera
un mensaje de error en tiempo de compilación que le dice exactamente lo que está mal:

Como beneficio adicional, usted ya no tiene que poner cast de forma manual al retirar los elementos de
las colecciones. El compilador inserta casts invisibles para usted y garantías de que no se producirá un
error (suponiendo, de nuevo, que todo el código se compila con un compilador genérico y no producen o
suprimen las advertencias). Esto es cierto si se utiliza un bucle for-each (artículo 46):

O un ciclo tradicional for:

Si bien la perspectiva de querer insertar una moneda en una colección de sellos puede parecer
exagerado, el problema es real. Por ejemplo, es fácil imaginar a alguien poner java.util.Date en una
colección que se supone que contienen sólo las instancias java.sql.Date. Como se señaló anteriormente,
todavía es legal el uso de los tipos de colección y otros tipos genéricos sin el suministro de parámetros de
tipo, pero no debe hacerlo. Si utiliza tipos abiertos, se pierden todos los beneficios de la seguridad y la
expresividad de los genéricos.

Teniendo en cuenta que usted no debe usar los tipos abiertos, ¿por qué los diseñadores de lenguajes lo
permiten? Para proporcionar compatibilidad. La plataforma Java estaba a punto de entrar en su segunda
década, cuando se introdujeron los genéricos, y había una enorme cantidad de código Java en la
existencia que no usaron genéricos. Se consideró fundamental que todos los de este código siguen
siendo legales y compatibles con el nuevo código que hace uso de genéricos. Tenía que ser legal para
pasar instancias de tipos de parámetros a los métodos que fueron diseñados para su uso con los tipos
ordinarios, y viceversa. Este requisito, conocido como la compatibilidad de la migración, la llevó a la
decisión de apoyar a los tipos abiertos.

Mientras que usted no debe usar los tipos abiertos como List en un nuevo codigo, que está bien de utilizar
los tipos que son parámetros para permitir la inserción de objetos arbitrarios, como List<Object>. ¿Qué
es exactamente la diferencia entre la lista de tipo abierta y el tipo de parámetros List<Object>? En
términos generales, el primero ha optado por la comprobación de tipos genéricos, mientras que el
segundo ha dicho explícitamente que el compilador que es capaz de contener objetos de cualquier tipo.
Mientras que usted puede pasar un List<String> a un parámetro de tipo List, no se puede pasar a un List
<Object>. Hay reglas de subtipos para los genéricos, y List<String> es un subtipo de tipo abierto List, pero
no de parámetros tipo List<Object> (artículo 25). Como consecuencia, se pierde la seguridad si se usa
tipos abiertos como List, pero no si se utiliza un tipo parámetrizado como List<Objeto>.

Para concretar esto, considere el siguiente programa:


Este programa se compila, pero debido a que utiliza la lista de tipo abierta, se obtiene una advertencia:

Y, en efecto, si se ejecuta el programa, se obtiene una ClassCastException cuando el programa intenta


convertir el resultado de la invocación strings.get (0) en un String.

Este es un cast generado por el compilador, por lo que normalmente garantizan el éxito, pero en este
caso nos ignoró una advertencia del compilador y pagó el precio.

Si reemplaza la lista de tipo abierto con el tipo parametrizado List<Object> en la declaración unsafeAdd y
tratar de volver a compilar el programa, usted encontrará que ya no compila. Aquí está el mensaje de
error:

Usted puede tener la tentación de utilizar un tipo abierto para una colección cuyo tipo de elemento es
desconocido y no importa. Por ejemplo, suponga que desea escribir un método que toma dos conjuntos y
devuelve el número de elementos que tienen en común. He aquí cómo usted puede escribir como un
método si eran nuevos genéricos:

Este método funciona, pero utiliza los tipos abiertos, que son peligrosos. Desde la versión 1.5, Java ha
proporcionado una alternativa segura conocida como tipos de comodín sin límites. Si desea utilizar un tipo
genérico, pero usted no sabe ni le importa el tipo de parámetro actual, puede utilizar un signo de
interrogación en su lugar. Por ejemplo, el tipo comodín sin límites para el tipo genérico Set<E> es Set<?>
(léase "conjunto de algún tipo"). Es el tipo de parámetros más generales Set, capaz de contener cualquier
conjunto. Así es como el método numElementsInCommon se ve con tipos de comodín sin límites:

¿Cuál es la diferencia entre el tipo Set<?> sin límites comodín y el tipo abierto Set? ¿Los signos de
interrogación realmente mejoran algo? Por no extenderme el punto, pero el tipo comodín es seguro y el
tipo de abierto no lo es. Usted puede poner cualquier elemento en una colección con un tipo abierto,
facilmente corrompe los tipos invariantes de colecciones (como se demuestra por el método unsafeAdd
en la página 112), no se puede poner cualquier elemento (que no sean nulos) en una colección <?>. Si lo
intenta, se generará un mensaje de error en tiempo de compilación como ésta:

Es cierto que este mensaje de error deja mucho que desear, pero el compilador ha hecho su trabajo,
evitando que se corrompan los tipos invariantes de la colección.

No sólo no se puede poner cualquier elemento (que no sean nulos) en un Collection <?>, pero no se
puede asumir nada sobre el tipo de los objetos que salga. Si estas restricciones son inaceptables, puede
utilizar métodos genéricos (artículo 27) o delimitadas tipos comodín (artículo 28).
Hay dos pequeñas excepciones a la regla que no debe usar los tipos abiertos en el nuevo código, los
cuales derivan del hecho de que la información de tipo genérico se borra en tiempo de ejecución (artículo
25). Debe utilizar tipos abiertos en los literales de clase. La especificacion no permiten el uso de tipos
parámetrizados (aunque sí permite tipos array y tipo primitivo). En otras palabras, List.class, String [].
class, y int.class son legales, pero List<String>. clase y List <?>. clase no lo son.
La segunda excepción a la norma se refiere al operador instanceof. Dado que la información de tipo
genérico se borra en tiempo de ejecución, es ilegal utilizar el operador instanceof sobre los tipos de
parámetros distintos tipos de comodín sin límites. El uso de tipos de comodín sin límites en lugar de los
tipos abiertos no afecta el comportamiento del operador instanceof de ninguna manera. En este caso, los
soportes de ángulo y signos de interrogación son sólo ruido. Esta es la mejor forma de utilizar el operador
instanceof con tipos genéricos:

Tenga en cuenta que una vez que haya determinado o es un Set, debe convertir en el comodín de tipo
Set <?>, no el tipo abierto Set. Este es un cast marcado, por lo que no hará una advertencia del
compilador.

En resumen, el uso de tipos abiertos puede dar lugar a excepciones en tiempo de ejecución, así que no
los uso en el nuevo código. Ellos se proporcionan sólo para la compatibilidad e interoperabilidad con el
código heredado que es anterior a la introducción de los genéricos. En una revisión rápida, Set <Object>
es un tipo de parámetros que representan un conjunto que puede contener objetos de cualquier tipo, Set
<?> es un tipo comodín que representa un conjunto que puede contener sólo los objetos de algún tipo
desconocido, y Set es un tipo abierto, que opta por el sistema de tipo genérico. Los dos primeros son
seguros y el último no lo es.

Para una rápida referencia, los términos introducidos en este tema (y unos pocos presentó otra parte de
este capítulo) se resumen en la siguiente tabla:

También podría gustarte