Sunteți pe pagina 1din 31

Los medicamentos genricos en el lenguaje de programacin Java Gilad Bracha 05 de julio 2004 Contenido 1 Introduccin 2 2 Definicin de los genricos

simples 3 3 Genricos y subtipificacin 4 4 comodines 5 4.1 Comodines Limitado. . . . . . . . . . . . . . . . . . . . . . . . . . . 6 5 Mtodos genricos 7 6 Interoperabilidad con cdigo heredado 10 6.1 Utilizacin de cdigo heredado en cdigo genrico. . . . . . . . . . . . . . . . . . 10 6.2 Erasure y la traduccin. . . . . . . . . . . . . . . . . . . . . . . . . 12 6.3 Uso genricas del cdigo en el cdigo heredado. . . . . . . . . . . . . . . . . . 13 7 La letra pequea 14 7.1 Una clase genrica es compartida por todos sus invocaciones. . . . . . . . . . . . 14 7.2 Los yesos y instanceof. . . . . . . . . . . . . . . . . . . . . . . . . . 14 7.3 Arrays. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 8 literales de clase como fichas de tipo en tiempo de ejecucin 16 9 Ms diversin con comodines 18 9.1 Captura de comodn. . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 10 Legado Convertir cdigo a utilizar medicamentos genricos 20 11 Agradecimientos

23 1
Pgina 2

1 Introduccin JDK 1.5 introduce diversas extensiones para el lenguaje de programacin Java. Uno de estos es la introduccin de los genricos. Este tutorial est destinado a introducir a los genricos. Usted puede estar familiarizado con construcciones similares de otros idiomas, sobre todo C + + plantillas. Si es as, pronto ver que hay semejanzas y diferencias importantes. Si usted no est familiarizado con las construcciones de mirar-uno-tanto de otros lugares, mucho mejor, se puede empezar de nuevo, sin desaprender conceptos errneos. Los genricos le permiten abstracto sobre los tipos. El ejemplo ms comn son conlos tipos de envase, como por ejemplo los de la jerarqua de la coleccin. Aqu hay un uso tpico de este tipo: MyIntList lista = new LinkedList (); / / 1 myIntList.add (new Integer (0)) / / 2 Entero x = (nmero entero) myIntList.iterator () next ();. / / 3 El elenco de la lnea 3 es un poco molesto. Por lo general, el programador sabe lo que tipo de datos se ha colocado en una lista en particular. Sin embargo, el reparto es esencial. La compilador slo puede garantizar que un objeto le ser devuelto por el iterador. A fin de garantizar la asignacin a una variable de tipo Integer es seguro el tipo, el elenco se requiere. Por supuesto, el elenco no slo introduce el desorden. Tambin se introduce la posibilidad de un error de tiempo de ejecucin, ya que el programador puede estar equivocado. Qu pasa si los programadores en realidad podra expresar su intencin, y la marca de una lista como limita a contener un tipo de datos en particular? Esta es la idea central detrs de los genricos. Aqu es una versin del fragmento de programa dado anteriormente con los medicamentos genricos: <Integer> MyIntList lista = new LinkedList <Integer> () / / 1 '

myIntList.add (new Integer (0)) / / 2 ' Entero x = myIntList.iterator () next ();. / / 3 ' Aviso de la declaracin de tipo para la myIntList variable. Se especifica que esto no es slo una lista arbitraria, pero hay una lista de <Integer> Integer, lista escrita. Podemos decir que la lista es una interfaz genrica que toma un parmetro de tipo - en este caso, Integer. Tambin especificamos un parmetro de tipo cuando se crea el objeto de lista. La otra cosa que prestar atencin es que el reparto se ha ido en la lnea 3. Ahora, se podra pensar que todo lo que hemos logrado es mover el desorden alrededor. En lugar de una fuerza a entero en la lnea 3, tenemos entero como un parmetro de tipo en la lnea 1. Sin embargo, hay una diferencia muy grande aqu. El compilador ahora se puede comprobar el tipo de la correccin del programa en tiempo de compilacin. Cuando decimos que se declara myIntList con <Integer> tipo de lista, esto nos dice algo acerca de la myIntList variable, que es verdad donde quiera y cuando se utiliza, y el compilador se lo garantizamos. En Por el contrario, el reparto nos dice algo que el programador cree que es cierto en un solo punto en el el cdigo. El efecto neto, sobre todo en programas grandes, se mejora la legibilidad y la robustez. 2
Pgina 3

2 Definicin de los genricos simples Aqu est un pequeo extracto de las definiciones de la lista de interfaces y Iterator en paquete java.util edad: interfaz pblica <E> Lista { void add (E x); Iterator iterator <E> (); } interfaz pblica <E> Iterator { E next (); booleano hasNext ();

} Todo esto debe estar familiarizado, a excepcin de las cosas entre parntesis angulares. Esos son los declaraciones de los parmetros de tipo formal de la lista de interfaces y Iterator. Los parmetros de tipo se pueden utilizar en la declaracin genrica, ms o menos donde deber utilizar los tipos ordinarios (aunque hay algunas restricciones importantes, vase la seccin 7). En la introduccin, hemos visto las invocaciones de la lista de declaracin de tipo genrico, como <Integer> lista. En la invocacin (normalmente se llama un tipo parametrizado), todo ocurrerencias de los parmetros de tipo formal (E en este caso) se sustituyen por el tipo real argumento (en este caso, Integer). Uno podra imaginar que <Integer> lista representa una versin de la Lista E, donde se ha sido uniformemente sustituidos por entero: pblica IntegerList interfaz { void add (nmero entero x) Iterador <Integer> iterador (); } Esta intuicin puede ser til, pero tambin es engaoso. Es til, porque el tipo de lista de parmetros <Integer> tiene de hecho mtodos que se parecen a esta expansin. Es engaoso, porque la declaracin de un genrico en realidad nunca se expandi en de esta manera. No hay varias copias del cdigo: no en su origen, no en el sistema binario, no en disco y no en la memoria. Si usted es un programador de C + +, vas a entender que este es muy diferente a una C + + plantilla. Una declaracin de tipo genrico se compila una vez por todas, y se convirti en una sola archivo de clase, al igual que una clase ordinaria o declaracin de interfaz. Los parmetros de tipo son anlogos a los parmetros normales utilizadas en los mtodos o constructors. Al igual que un mtodo tiene parmetros formales valor que describen los tipos de

valores que opera en una declaracin genrica tiene parmetros formales de tipo. Cuando un mtodo es invocado argumentos, reales son sustituidos por los parmetros formales, y el mtodo el cuerpo se evala. Cuando una declaracin genrica se invoca, los argumentos de tipo real son sustituidos por los parmetros de tipo formal. Una nota sobre las convenciones de nombres. Le recomendamos que utilice conciso (un solo carcter si es posible), sin embargo los nombres evocadores de los parmetros de tipo formal. Lo mejor es evitar la disminucin de 3
Pgina 4

caracteres en minsculas en los nombres, por lo que es fcil distinguir los parmetros formales de tipo de las clases ordinarias y las interfaces. Muchos tipos de contenedores uso E, para el elemento, como en los ejemplos anteriores. Vamos a ver algunos convenios adicionales en ejemplos posteriores. 3 Genricos y subtipificacin Vamos a probar nuestra comprensin de los genricos. Es el siguiente fragmento de cdigo legal? Ls lista <String> = new ArrayList <String> (); / / 1 Lista <Objeto> lo = ls / / 2 La lnea 1 es sin duda legal. La parte ms difcil de la cuestin es la lnea 2. Esto se reduce a la pregunta: es una lista de cadenas de una lista de objetos. Instinto de la mayora de las personas es responder a: "Seguro". Bueno, eche un vistazo a las siguientes lneas: lo.add (nuevo Object ()); / / 3 String s = ls.get (0); / / 4: intenta asignar un objeto a una cadena! Aqu hemos alias ls y lo. Acceso a ls, una lista de cadenas, a travs de la LO alias, que Puede insertar objetos arbitrarios en ella. Como resultado de ls no se sostiene slo cadenas ms, y cuando tratamos de sacar algo de l, tenemos una sorpresa desagradable. El compilador de Java que evitar que esto suceda, por supuesto. La lnea 2 har un error de compilacin tiempo.

En general, si Foo es un subtipo (subclase o subinterfaz) de Bar, y G es un declaracin de tipo genrico, no es el caso de que G <foo> es un subtipo de G <Bar>. Esta es probablemente la cosa ms difcil que usted necesita para aprender acerca de los genricos, porque va en contra de nuestras intuiciones profundas. El problema con el que la intuicin es que asume que las colecciones no cambian. Nuestro instinto toma estas cosas que son inmutables. Por ejemplo, si el departamento de vehculos de motor suministra una lista de los controladores a la cenSUS oficina, esto parece razonable. Creemos que una <controlador> lista es una lista de <person>, asumiendo que Driver es un subtipo de la persona. De hecho, lo que se est pasando es una copia del registro de los conductores. De lo contrario, la Oficina del Censo podra agregar nuevas personas que se no los controladores en la lista, corrompiendo los registros del DMV. Con el fin de hacer frente a este tipo de situaciones, es til tener en cuenta ms flexible los tipos genricos. Las reglas que hemos visto hasta ahora son muy restrictivas. 4
Pgina 5

4 comodines Considere el problema de escribir una rutina que imprime todos los elementos de una coleccin. He aqu cmo usted puede ser que lo escriba en una versin anterior de la lengua: vaco printCollection (Coleccin c) { Iterator i = c.iterator (); for (k = 0; k <c.size (); k + +) { System.out.println (i.next ()); }} Y aqu es un ingenuo intento de escribir utilizando los genricos (y el nuevo ciclo de synimpuestos): vaco printCollection (Coleccin <Objeto> c) { de (e Objeto: c) { System.out.println (e); }}

El problema es que esta nueva versin es mucho menos til que el anterior. Mientras el cdigo anterior se podra llamar con cualquier tipo de coleccin como un parmetro, el nuevo cdigo Slo se tarda Coleccin <Objeto>, que, como acabamos de demostrar, no es un supertipo de todo tipo de colecciones! Entonces, cul es el supertipo de todo tipo de colecciones? Est escrito Collection <> (Se pronuncia "coleccin de lo desconocido"), es decir, un conjunto cuyo elemento coincida con el tipo cualquier cosa. Se llama un tipo de comodn por razones obvias. Podemos escribir: printCollection void (Collection <> c) { de (e Objeto: c) { System.out.println (e); }} y ahora, podemos llamarlo con cualquier tipo de coleccin. Tenga en cuenta que dentro de printColleccin (), podemos leer los elementos de c, y darles el tipo Object. Esto es siempre seguro, ya que sea cual sea el tipo real de la coleccin, que contiene los objetos. No es seguro para agregar objetos arbitrarios no obstante: Coleccin c = new ArrayList <String> () <>; contenedor.add (nuevo Object ()); / / compile error de tiempo Dado que no sabemos lo que el tipo de elemento de c significa, no podemos agregar objetos a la misma. El mtodo add () toma los argumentos de tipo E, el tipo de elemento de la coleccin. Cuando el parmetro de tipo real es?, Lo que representa un tipo desconocido. Cualquier parmetro pasamos a aadir tendra que ser un subtipo de este tipo desconocido. Dado que no sabemos qu tipo, es decir, que no puede pasar nada pulg La nica excepcin es nula, lo cual es una miembros de todo tipo. Por otro lado, dada una lista de <>, podemos llamar a get () y hacer uso de los resultados. El tipo de resultado es un tipo desconocido, pero siempre sabemos que es un objeto. Es 5

Pgina 6

por lo tanto seguro para asignar el resultado de get () a una variable de tipo Object o pasar como una parmetro en el tipo de objeto que se espera. 4.1 Comodines Limitado Considere la posibilidad de una aplicacin de dibujo sencillo que se puede dibujar formas como rectngulos y las circulos. Para representar estas formas dentro del programa, se puede definir una jerarqua de clases como el siguiente: Forma pblico clase abstracta { public abstract void draw (lienzo c); } Crculo public class Shape { private int x, y, radio; dibujar public void (lienzo c) {... } } Rectngulo public class Shape { private int x, y, ancho, alto; dibujar public void (lienzo c) {... } } Estas clases se pueden dibujar en un lienzo: Lienzo public class { dibujar public void (Shape s) { s.draw (este); } } Cualquier dibujo por lo general, contienen una serie de formas. Suponiendo que se representado como una lista, sera conveniente disponer de un mtodo en el lienzo que dibuja todos ellos: drawAll public void (formas Lista <Shape>) { para la (s Forma: formas) { s.draw (este); } } Ahora, las reglas de tipo decir que drawAll () slo puede ser llamado en las listas de forma exacta:

no puede, por ejemplo, ser llamado en un <Circle> lista. Eso es lamentable, ya que todos los el mtodo que hace es leer las formas de la lista, as que igual podra ser llamado en un Lista <Circle>. Lo que realmente queremos es que el mtodo para aceptar una lista de cualquier tipo de forma: drawAll public void (List <? extiende Forma> formas) {... } Hay una diferencia pequea pero muy importante aqu: hemos sustituido el tipo <Shape> Lista con la lista <? Se extiende de forma>. Ahora drawAll () aceptar las listas de cualquier subclase de Shape, por lo que ahora podemos llamar a un <Circle> lista si queremos. 6
Pgina 7

List <? Se extiende de forma> es un ejemplo de un comodn limitada. El? se encuentra de un tipo desconocido, al igual que los comodines que vimos anteriormente. Sin embargo, en este caso, sabemos que este tipo desconocido es en realidad un subtipo de la forma 1 . Decimos que es la forma lmite superior del comodn. No es, como siempre, un precio a pagar por la flexibilidad de usar comodines. Que el precio es que ahora es ilegal escribir en formas en el cuerpo del mtodo. Por ejemplo, esto no est permitido: addRectangle public void (List <? extiende Forma> formas) { shapes.add (0, new Rectangle ()); / / error en tiempo de compilacin! } Usted debe ser capaz de averiguar por qu el cdigo anterior no est permitida. El tipo de El segundo parmetro a shapes.add () se extiende Forma - un subtipo desconocido de forma. Dado que no sabemos de qu tipo es, no sabemos si es un supertipo del rectngulo, ya que podra o podra no ser un supertipo, por lo que no es suficiente para pasar una Rectngulo de all. Comodines limitado son slo lo que uno necesita para manejar el ejemplo del DMV

pasar sus datos a la Oficina del Censo. Nuestro ejemplo se asume que los datos se representan mediante la asignacin de nombres (representados como cadenas) a las personas (representadas por referencia tipos como persona o de sus subtipos, como el conductor). Mapa <K,V> es un ejemplo de un tipo genrico que toma dos argumentos de tipo, que representan las claves y valores de la mapa. Una vez ms, tenga en cuenta la convencin de nomenclatura para parmetros de tipo formal - K para las claves y V de los valores. Censo pblico de clase { public static void addRegistry (Mapa <String,? extiende registro Person>) {...} } ... Mapa <String, Driver> allDrivers = ...; Census.addRegistry (allDrivers); 5 Mtodos genricos Considere la posibilidad de escribir un mtodo que toma un conjunto de objetos y una coleccin y pone todo objetos de la matriz en la coleccin. Aqu est un primer intento: fromArrayToCollection void (Object [] a, Collection <> c) { for (Object o: a) { contenedor.add (o); / / compile error de tiempo }} Por ahora, se han aprendido a evitar el error del principiante de tratar de usar Coleccin <Objeto> como el tipo del parmetro de la coleccin. Usted puede o no puede 1 Podra ser en s la forma, o subclase algunos, no es necesario que, literalmente, se extienden de forma. 7
Pgina 8

han reconocido que el uso de la coleccin <> no va a funcionar bien. Conviene recordar que se no se puede simplemente meter objetos en una coleccin de tipo desconocido.

La manera de hacer frente a estos problemas es el uso de mtodos genricos. Al igual que el tipo declaraciones, declaraciones de mtodos pueden ser genricos - es decir, parmetros de uno o ms parmetros de tipo. esttica <T> fromArrayToCollection vaco (T [] a, Coleccin <T> c) { de (T o: a) { contenedor.add (o) / / correcta }} Podemos llamar a este mtodo con cualquier tipo de coleccin cuyo tipo de elemento es un supertipo del tipo de elemento de la matriz. Object [] = new OA Object [100]; Coleccin <Objeto> co = new ArrayList <Objeto> (); fromArrayToCollection (OA, co) / / T infiere que objetos String [] sa = new String [100]; <String> Coleccin cs = new ArrayList <String> (); fromArrayToCollection (sa, cs); / / T infiere que de cuerdas fromArrayToCollection (sa, co) / / T infiere que objetos Integer [] = new Integer ia [100]; Float [] fa = Float nueva [100]; [Nmero] n = nmero de nuevas [100]; Coleccin <Number> cn = new ArrayList <Number> (); fromArrayToCollection (ia, cn); / / T inferidos a ser el nmero fromArrayToCollection (fa, cn); / / T inferidos a ser el nmero fromArrayToCollection (na, cn); / / T inferidos a ser el nmero fromArrayToCollection (na, co) / / T infiere que objetos fromArrayToCollection (na, cs); / / error en tiempo de compilacin Tenga en cuenta que no tenemos que pasar un argumento de tipo real de un mtodo genrico. La compilador infiere el tipo de argumento para nosotros, sobre la base de los tipos de los argumentos reales. Lo generalmente deducir el argumento de tipo ms especfico que har la llamada del tipo correcto. Una pregunta que surge es: cundo debo usar mtodos genricos, y cundo debo utilizar los tipos de comodn? Para entender la respuesta, vamos a examinar algunos mtodos de la Coleccin de bibliotecas. Interfaz Collection <E> { public boolean containsAll (Coleccin c <>); public boolean AddAll (Coleccin c <extiende E>);

} Podramos haber utilizado los mtodos genricos aqu en su lugar: Interfaz Collection <E> { pblica <T> booleano containsAll (Coleccin <T> c); pblica <t extiende E> booleano AddAll (Coleccin <T> c); / / Hey, variables de tipo puede tener lmites tambin! } 8
Pgina 9

Sin embargo, en ambos containsAll y AddAll, el parmetro de tipo T se utiliza slo una vez. El tipo de retorno no depende del parmetro de tipo, ni ningn otro argumento con el mtodo (en este caso, simplemente no hay un solo argumento). Esto nos dice que la tipo de argumento se utiliza para el polimorfismo, su nico efecto es permitir una variedad de tipos reales de argumento que se utiliza en los sitios de invocacin diferentes. Si ese es el caso, un debe usar comodines. Los comodines estn diseados para apoyar subtipificacin flexible, que se lo que estamos tratando de expresar aqu. Los mtodos genricos permiten parmetros de tipo que se utiliza para expresar las dependencias entre los tipos de uno o ms argumentos a un mtodo y / o el tipo que devuelve. Si no hay esa dependencia, un mtodo genrico no se debe utilizar. Es posible utilizar ambos mtodos genricos y comodines en tndem. Aqu est la Collections.copy mtodo (): Colecciones de clase { public static <T> vaco copia (Lista <T> dest, List <? extiende T> src ){...} } Tenga en cuenta la dependencia entre los tipos de los dos parmetros. Cualquier objeto copiado de la lista fuente, src, se debe asignar a la T de tipo de elemento del destino lista, horario de verano. Por lo que el tipo de elemento de src puede ser cualquier subtipo de T - no nos importa que. La firma de la copia expresa la dependencia mediante un parmetro de tipo, pero utiliza un comodn para el tipo de elemento del segundo parmetro.

Podramos haber escrito la firma de este mtodo de otra manera, sin utilizar comodines en todo: Colecciones de clase { public static <T, S se extiende T> anular la copia (Lista <T> dest, src <S> Lista ){...} } Esto est bien, pero mientras que el primer parmetro de tipo es utilizado tanto en el tipo de horario de verano y en el lmite del parmetro segundo tipo, S, S es en s mismo slo se utilizan una vez, en el tipo de src - nada ms depende de ella. Esta es una seal de que podemos reemplazar S con un comodn. Uso de comodines es ms clara y concisa de declarar explcitamente los parmetros de tipo, y por lo tanto es preferible siempre que sea posible. Los comodines tambin tienen la ventaja de que pueden ser utilizadas fuera del mtodo de firestructuras, como los tipos de campos, variables locales y matrices. He aqu un ejemplo. Volviendo a nuestro problema de dibujo de formas, supongamos que queremos mantener un historial de dibujo peticiones. Podemos mantener la historia en una variable esttica en el interior Forma de clase, y han drawAll () guardan su argumento de entrada en el campo de la historia. Lista esttica <List <? se extiende de forma>> Historia = new ArrayList <List <? ampla Shape >>(); drawAll public void (List <? extiende Forma> formas) { history.addLast (formas); para la (s Forma: formas) { s.draw (este); }} 9
Pgina 10

Finalmente, una vez ms vamos a tomar nota de la convencin de nomenclatura utilizada para el tipo de partros. Utilizamos para el tipo T, siempre que no hay nada ms especfico sobre el tipo para distinguirlo. Esto es a menudo el caso de los mtodos genricos. Si hay varios tipos

parmetros, podemos usar letras que T vecino en el alfabeto, como S. Si un genrico mtodo aparece dentro de una clase genrica, es una buena idea evitar el uso de los mismos nombres para los parmetros de tipo del mtodo y de clase, para evitar confusiones. Lo mismo se aplica para anidar clases genricas. 6 Interoperabilidad con cdigo heredado Hasta ahora, todos nuestros ejemplos han asumido un mundo idealizado, donde todo el mundo est utilizando la ltima versin del lenguaje de programacin Java, que apoya los genricos. Por desgracia, en realidad este no es el caso. Millones de lneas de cdigo han sido escritas en las versiones anteriores de la lengua, y no todos se convertirn durante la noche. Ms tarde, en la seccin 10, se abordar el problema de convertir el cdigo antiguo para usar genricos. En esta seccin, nos centraremos en un problema ms simple: cmo puede el cdigo heredado y cdigo genrico interoperar? Esta pregunta tiene dos partes: el uso de cdigo heredado desde dentro cdigo genrico, y el uso de cdigo genrico en el cdigo de legado. 6.1 Legado mediante cdigo en cdigo genrico Cmo puede utilizar el cdigo viejo, mientras disfruta de los beneficios de los genricos en su propia cdigo? Como ejemplo, suponga que desea utilizar el com.Fooblibar.widgets paquete. La gente de Fooblibar.com 2 mercado un sistema de control de inventario, que se destaca se muestra a continuacin: com.Fooblibar.widgets paquete; Parte interfaz pblica {...} Inventario de la clase pblica { / ** * Agrega una nueva Asamblea de la base de datos de inventario. * El conjunto recibe el nombre el nombre, y consiste en un conjunto * Especificados por las partes. Todos los elementos de las piezas de coleccin * Debe ser compatible con la interfaz de la Parte. ** / addAssembly public void (String nombre, piezas de coleccin) {...} getAssembly pblica Asamblea esttica (String nombre) {...}

} pblica de la Asamblea interfaz { Coleccin getParts (); / / Devuelve una coleccin de piezas } Ahora, usted quisiera agregar nuevo cdigo que utiliza la API de arriba. Sera bueno que asegurarse de que siempre llama addAssembly () con los argumentos adecuados es decir, que 2 Fooblibar.com es una empresa puramente de ficcin, que se utiliza con fines ilustrativos.Cualquier relacin con cualquier verdadero empresa o institucin, o cualquier personas vivas o muertas, es pura coincidencia. 10
Pgina 11

la coleccin se le pasa es de hecho una coleccin de la primera parte. Por supuesto, los genricos son a medida hizo para esto: paquete com.mycompany.inventory; com.Fooblibar.widgets importacin .*; Hoja public class {Parte ... } Guillotina public class {Parte } pblico principal la clase { public static void main (String [] args) { Coleccin <part> c = new ArrayList <part> (); contenedor.add (nuevo Guillotina ()); contenedor.add (nueva hoja ()); Inventory.addAssembly ("thingee", c); Coleccin <part> k = Inventory.getAssembly ("thingee") getParts ().; }} Cuando llamamos a addAssembly, que espera que el segundo parmetro a ser de tipo coleccin. El argumento real es de <part> tipo de coleccin. Esto funciona, pero por qu? Despus de todos, la mayora de las colecciones no contienen objetos de la parte, y por lo tanto, en general, el compilador no tiene manera de saber qu tipo de coleccin de la coleccin tipo se refiere.

En el cdigo genrico apropiado, la coleccin siempre ira acompaado de un tipo de pareter. Cuando un tipo genrico, como la coleccin se utiliza sin un parmetro de tipo, se llama un tipo de crudo. Instinto de la mayora de la gente primero es que la coleccin que realmente significa la coleccin <Object>. Sin embargo, como hemos visto anteriormente, no es suficiente para pasar un <part> coleccin en un lugar donde una coleccin de <Objeto> se requiere. Es ms exacto decir que la coleccin tipo denota una coleccin de tipo desconocido, al igual que la coleccin <>. Pero espera, que no puede ser correcto, ya sea! Considere la posibilidad de la llamada a getParts (), que devuelve una coleccin. Esto se asigna a k, que es una coleccin de <part>. Si el resultado de la llamada es una coleccin de <>, la cesin sera un error. En realidad, la asignacin es legal, pero se genera una advertencia sin marcar. La advertencia es necesaria, porque el hecho es que el compilador no puede garantizar su exactitud. No tenemos forma de comprobar el cdigo de la herencia en getAssembly () para asegurarse de que, efectivamente, la coleccin que se devuelve es una coleccin de elementos. El tipo de los utilizados en el cdigo es Coleccin, y una ley puede insertar todo tipo de objetos en una coleccin. Por lo tanto, si esto no es un error? Tericamente hablando, s, pero hablar prcticamente cin, si el cdigo genrico se va a llamar a cdigo heredado, esto tiene que ser permitido. Depende de usted, el programador, para estar seguro de que en este caso, la asignacin es seguro porque la contrato de getAssembly () dice que devuelve una coleccin de piezas, a pesar de que el tipo la firma no se muestra esto. Tipos tan crudas son muy similares a los tipos de comodn, pero no son como typechecked estricta. Esta es una decisin de diseo deliberada, para permitir que los medicamentos genricos para interoperar con pre-existentes cdigo heredado. Llamar a cdigo heredado de cdigo genrico es intrnsecamente peligrosa, una vez que la mezcla

cdigo genrico con el cdigo heredado no genrica, todas las garantas de seguridad que los genricos 11
Pgina 12

el tipo de sistema por lo general proporciona son nulos. Sin embargo, usted sigue siendo mejor de lo que se sin necesidad de utilizar medicamentos genricos en absoluto. Por lo menos sabes el cdigo de su parte es coherente. En este momento hay mucho ms cdigo no genrico por ah entonces no hay genricos cdigo, y es inevitable que haya situaciones en las que se han de mezclar. Si usted encuentra que usted debe legado se entremezclan y el cdigo genrico, prestar mucha atencin a las advertencias sin marcar. Piense con cuidado cmo se puede justificar la seguridad del cdigo que da lugar a la advertencia. Qu sucede si an cometi un error, y es el cdigo que provoc una advertencia de hecho, no un tipo seguro? Vamos a echar un vistazo a esta situacin. En el proceso, nos pondremos en contacto alguna informacin sobre el funcionamiento del compilador. 6.2 Erasure y traduccin laguna public String (nmero entero x) { <String> Lista ys = new LinkedList <String> (); Lista xs = ys; xs.add (x) / / en tiempo de compilacin de advertencia sin control ys.iterator retorno () next ().; } Aqu, hemos suavizado una lista de cadenas y una lista simple y llano. Insertamos un nmero entero en la lista, y tratar de extraer una cadena. Esto es claramente errneo. Si hacemos caso omiso de la advertencia y tratar de ejecutar este cdigo, se producir un error en el punto exacto donde se intenta utilizar el tipo incorrecto. En tiempo de ejecucin, este cdigo se comporta como: laguna public String (nmero entero x) { Lista ys = new LinkedList; Lista xs = ys; xs.add (x); . return (String) ys.iterator () next (); / / error en tiempo de ejecucin }

Cuando se extrae un elemento de la lista, y el intento de tratarla como una cadena convirtindola en cadena, vamos a obtener una ClassCastException. Exactamente lo mismo sucede con la versin genrica de la laguna (). La razn de esto es, que los genricos son implementadas por el compilador de Java como un front-end de conversin llamado borrado. Usted puede (casi) piensa en l como una fuente a fuente traduccin, por lo que la versin genrica de la laguna () se convierte en el no genricos versin. Como resultado, el tipo de seguridad y la integridad de la mquina virtual de Java no son en riesgo, incluso en presencia de advertencias sin marcar. Bsicamente, la supresin elimina (o borra) toda la informacin de tipo genrico. Todos los tipos informacin entre parntesis angulares se lanza hacia fuera, por lo que, por ejemplo, una parametrizacin tipo como <String> lista se convierte en la Lista. Todos los restantes usos de las variables de tipo son reemplazado por el lmite superior de la variable de tipo (por lo general objetos). Y, cada vez que el cdigo resultante no es del tipo correcto, un elenco en el tipo adecuado se inserta, al igual que en los ltimos lnea de vaco. 12
Pgina 13

Los detalles completos de borrado estn fuera del alcance de este tutorial, pero el simple descripcin que acabamos de dar no est lejos de la verdad. Es bueno saber un poco acerca de esto, sobre todo si quieres hacer cosas ms sofisticadas como la conversin de las API existentes a uso de medicamentos genricos (ver seccin 10), o simplemente quiere entender por qu las cosas son las que forma se. 6.3 Uso genricas del cdigo en el cdigo heredado Ahora vamos a considerar el caso inverso. Imagina que Fooblibar.com decidi convertir su

API para el uso de genricos, pero que algunos de sus clientes no tienen todava. As que ahora el cdigo es como: com.Fooblibar.widgets paquete; Parte interfaz pblica {...} Inventario de la clase pblica { / ** * Agrega una nueva Asamblea de la base de datos de inventario. * El conjunto recibe el nombre el nombre, y consiste en un conjunto * Especificados por las partes. Todos los elementos de las piezas de coleccin * Debe ser compatible con la interfaz de la Parte. ** / public void esttica addAssembly (String nombre, coleccin de piezas <part>) {...} getAssembly pblica Asamblea esttica (String nombre) {...} } pblica de la Asamblea interfaz { Coleccin <part> getParts (); / / Devuelve una coleccin de piezas } y el cdigo del cliente se ve as: paquete com.mycompany.inventory; com.Fooblibar.widgets importacin .*; Hoja public class {Parte ... } Guillotina public class {Parte } pblico principal la clase { public static void main (String [] args) { Coleccin c = new ArrayList (); contenedor.add (nuevo Guillotina ()); contenedor.add (nueva hoja ()); Inventory.addAssembly ("thingee", c) / / 1: aviso sin control Coleccin k = Inventory.getAssembly ("thingee") getParts ().; }} El cdigo de cliente fue escrito antes de los genricos se han introducido, sino que utiliza el paquete com.Fooblibar.widgets y la biblioteca de la coleccin, los cuales utilizan genricos tipos. Todos los usos de las declaraciones de tipo genrico en el cdigo del cliente son los tipos de primas.

13
Pgina 14

La lnea 1 genera una advertencia sin control, ya que una coleccin de primas se est pasando en donde un conjunto de partes que se espera, y el compilador no puede garantizar que el Coleccin cruda realidad es una coleccin de partes. Como alternativa, se puede compilar el cdigo del cliente utilizando la fuente de 1,4 bandera, garanticin de que no se generan advertencias. Sin embargo, en ese caso, usted no ser capaz de utilizar cualquier de las nuevas caractersticas de lenguaje introducidas en el JDK 1.5. 7 La letra pequea 7.1 Una clase genrica es compartida por todos sus invocaciones Qu imprime el siguiente fragmento de cdigo? L1 <String> lista = new ArrayList <String> (); Lista <Integer> l2 = new <Integer> ArrayList (); System.out.println (l1.getClass () == l2.getClass ()); Usted puede verse tentado a decir falso, pero podra estar equivocado. Imprime verdad, porque todos los instancias de una clase genrica tienen el mismo tiempo de ejecucin de la clase, independientemente de su tipo real parmetros. De hecho, lo que hace que una clase genrica es el hecho de que tiene el mismo comportamiento para todos los de sus parmetros de tipo posible, la misma clase puede ser visto como teniendo muchas diferentes tipos. Como consecuencia, las variables estticas y mtodos de una clase tambin se comparten entre todas las instancias. Es por eso que es ilegal para referirse a los parmetros de tipo de un tipo declaracin de un mtodo esttico o un inicializador, o en la declaracin o de un inicializador esttico variable. 7.2 Los yesos y instanceof Otra de las implicaciones del hecho de que una clase genrica se comparte entre todas sus instancias, es que por lo general no tiene sentido pedirle a un ejemplo, si se trata de una instancia de un particular

invocacin de un tipo genrico: Coleccin cs = new ArrayList <String> (); if (cs <String> Coleccin instanceof) {...} / / ilegal Del mismo modo, un elenco como el Coleccin <String> CStr = (<String> Collection) cs / / advertencia sin control da una advertencia sin control, ya que esto no es algo que el sistema de tiempo de ejecucin va para comprobar que usted. Lo mismo sucede con las variables de tipo <T> BadCast T (T, Object o) {return (T) o / / advertencia sin control } 14
Pgina 15

Variables de tipo no existen en tiempo de ejecucin. Esto significa que no entraen el rendimiento gastos generales, ya sea en el tiempo ni el espacio, lo cual es bueno. Desafortunadamente, tambin significa que usted no puede usarlos de forma fiable en las heces. 7.3 Arrays El tipo de componente de un objeto de matriz no puede ser una variable de tipo o con parmetros un tipo, a menos que sea un comodn (sin lmites) type.You puede declarar los tipos de matrices que tipo de elemento es una variable de tipo o un tipo de parmetros, pero no los objetos de matriz. Esto es molesto, para estar seguro. Esta restriccin es necesaria para evitar situaciones como: <String> Lista [] lsa <String> = new List [10]; / / no permite realmente Object o = lsa; Object [] = OA (Object []) o; <Integer> Lista li = new ArrayList <Integer> (); li.add (new Integer (3)); OA [1] = li / / errneo, sino que pasa a ver correr el tiempo tienda String s = lsa [1] se (0);. / / Tiempo de ejecucin de error - ClassCastException Si las matrices de tipo parametrizado se les permiti, el ejemplo anterior se compilara sin ningn tipo de advertencias no se controla, y sin embargo no en tiempo de ejecucin.Hemos tenido la seguridad de tipos como un primer objetivo de los genricos. En particular, el lenguaje est diseado para garan-

T que si toda la aplicacin ha sido compilada sin advertencias sin control usando javac-fuente de 1.5, es un tipo seguro. Sin embargo, an puede utilizar matrices comodn. Aqu hay dos variaciones en el cdigo arriba. La primera renuncia a la utilizacin de objetos de matriz y dos tipos de matriz cuyo elemento tipo tiene parmetros. Como resultado, tenemos que desechar de forma explcita para obtener una cadena de la matriz. Lista <?>[] lsa = new List [10] <> / / ok, matriz de tipo comodn sin lmites Object o = lsa; Object [] = OA (Object []) o; <Integer> Lista li = new ArrayList <Integer> (); li.add (new Integer (3)); OA [1] = li / / correcta String s = (String) lsa [1] se (0);. Error / / tiempo de ejecucin, sino que es conversin explcita EEn el siguiente variacin, que se abstengan de crear un objeto de matriz cuyo tipo de elemento tiene parmetros, pero todava utilizan un tipo de matriz con un tipo de elemento de parmetros. Es legal, sino que genera una advertencia sin marcar. De hecho, el cdigo es seguro, y, finalmente, se produce un error. <String> Lista [] lsa = new List [10]; / / advertencia sin control - esto no es seguro <>! Object o = lsa; Object [] = OA (Object []) o; <Integer> Lista li = new ArrayList <Integer> (); li.add (new Integer (3)); OA [1] = li / / correcta String s = lsa [1] se (0);. / / Run time error, pero se les advirti 15
Pgina 16

De manera similar, tratando de crear un objeto de matriz cuyo tipo de elemento es una variable de tipo provoca un error en tiempo de compilacin: <T> T [] makeArray (T) { return new T [100]; / / error }

Since type variables don't exist at run time, there is no way to determine what the actual array type would be. The way to work around these kinds of limitations is to use class literals as run time type tokens, as described in section 8. 8 Class Literals as Run-time Type Tokens One of the changes in JDK 1.5 is that the class java.lang.Class is generic. It's an interesting example of using genericity for something other than a container class. Now that Class has a type parameter T, you might well ask, what does T stand for? It stands for the type that the Class object is representing. For example, the type of String.class is Class<String>, and the type of Serializable.class is Class<Serializable>. This can be used to improve the type safety of your reflection code. In particular, since the newInstance() method in Class now returns a T, you can get more precise types when creating objects reflectively. For example, suppose you need to write a utility method that performs a database query, given as a string of SQL, and returns a collection of objects in the database that match that query. One way is to pass in a factory object explicitly, writing code like: interface Factory<T> { T make();} public <T> Collection<T> select(Factory<T> factory, String statement) { Collection<T> result = new ArrayList<T>(); /* run sql query using jdbc */ for ( /* iterate over jdbc results */ ) { T item = factory.make(); /* use reflection and set all of item's fields from sql results */ result.add(item); } return result; } You can call this either as select(new Factory<EmpInfo>(){ public EmpInfo make() { return new EmpInfo(); }} , selection string); or you can declare a class EmpInfoFactory to support the Factory interface 16

Pgina 17

class EmpInfoFactory implements Factory<EmpInfo> { ... public EmpInfo make() { return new EmpInfo();} } and call it select(getMyEmpInfoFactory(), selection string); The downside of this solution is that it requires either: the use of verbose anonymous factory classes at the call site, or declaring a factory class for every type used and passing a factory instance at the call site, which is somewhat unnatural. It is very natural to use the class literal as a factory object, which can then be used by reflection. Today (without generics) the code might be written: Collection emps = sqlUtility.select(EmpInfo.class, select * from emps); ... public static Collection select(Class c, String sqlStatement) { Collection result = new ArrayList(); /* run sql query using jdbc */ for ( /* iterate over jdbc results */ ) { Object item = c.newInstance(); /* use reflection and set all of item's fields from sql results */ result.add(item); } return result; } However, this would not give us a collection of the precise type we desire. Ahora that Class is generic, we can instead write Collection<EmpInfo> emps = sqlUtility.select(EmpInfo.class, select * from emps); ... public static <T> Collection<T> select(Class<T>c, String sqlStatement) { Collection<T> result = new ArrayList<T>(); /* run sql query using jdbc */ for ( /* iterate over jdbc results */ ) { T item = c.newInstance(); /* use reflection and set all of item's fields from sql results */ result.add(item); } return result;

} giving us the precise type of collection in a type safe way. This technique of using class literals as run time type tokens is a very useful trick to know. It's an idiom that's used extensively in the new APIs for manipulating annotations, for example. 17
Pgina 18

9 More Fun with Wildcards In this section, we'll consider some of the more advanced uses of wildcards. Hemos seen several examples where bounded wildcards were useful when reading from a data estructura. Now consider the inverse, a write-only data structure. The interface Sink is a simple example of this sort. interface Sink<T> { flush(T t); } We can imagine using it as demonstrated by the code below. The method writeAll() is designed to flush all elements of the collection coll to the sink snk, and return the last element flushed. public static <T> T writeAll(Collection<T> coll, Sink<T> snk){ T last; for (T t : coll) { last = t; snk.flush(last); } return last; } ... Sink<Object> s; Collection<String> cs; String str = writeAll(cs, s); // illegal call As written, the call to writeAll() is illegal, as no valid type argument can be inferred; neither String nor Object are appropriate types for T, because the Collection element and the Sink element must be of the same type.

We can fix this by modifying the signature of writeAll() as shown below, using a wildcard. public static <T> T writeAll(Collection<? extends T>, Sink<T>){...} ... String str = writeAll(cs, s); // call ok, but wrong return type The call is now legal, but the assignment is erroneous, since the return type inferred is Object because T matches the element type of s, which is Object. The solution is to use a form of bounded wildcard we haven't seen yet: wildcards with a lower bound. The syntax ? super T denotes an unknown type that is a supertype of T 3 . It is the dual of the bounded wildcards we've been using, where we use ? extends T to denote an unknown type that is a subtype of T. public static <T> T writeAll(Collection<T> coll, Sink<? super T> snk){...} ... String str = writeAll(cs, s); // Yes! Using this syntax, the call is legal, and the inferred type is String, as desired. 3 Or T itself. Remember, the supertype relation is reflexive. 18
Pgina 19

Now let's turn to a more realistic example. A java.util.TreeSet<E> represents a tree of elements of type E that are ordered. One way to construct a TreeSet is to pass a Comparator object to the constructor. That comparator will be used to sort the elements of the TreeSet according to a desired ordering. TreeSet(Comparator<E> c) The Comparator interface is essentially: interface Comparator<T> { int compare(T fst, T snd); } Suppose we want to create a TreeSet<String> and pass in a suitable comparator, We need to pass it a Comparator that can compare Strings. This can be done by a Comparator<String>, but a Comparator<Object> will do just as well. Sin embargo, we won't be able to invoke the constructor given above on a Comparator<Object>. We can use a lower bounded wildcard to get the flexibility we want: TreeSet(Comparator<? super E> c)

This allows any applicable comparator to be used. As a final example of using lower bounded wildcards, lets look at the method Collections.max(), which returns the maximal element in a collection passed to it as an argumento. Now, in order for max() to work, all elements of the collection being passed in must implement Comparable. Furthermore, they must all be comparable to each other. A first attempt at generifying this method signature yields public static <T extends Comparable<T>> T max(Collection<T> coll) That is, the method takes a collection of some type T that is comparable to itself, and returns an element of that type. This turns out to be too restrictive. To see why, consider a type that is comparable to arbitrary objects class Foo implements Comparable<Object> {...} ... Collection<Foo> cf = ...; Collections.max(cf); // should work Every element of cf is comparable to every other element in cf, since every such element is a Foo, which is comparable to any object, and in particular to another Foo. However, using the signature above, we find that the call is rejected. The inferred type must be Foo, but Foo does not implement Comparable<Foo>. It isn't necessary that T be comparable to exactly itself. All that's required is that T be comparable to one of its supertypes. This give us: 4 4 The actual signature of Collections.max() is more involved. We return to it in section 10 19
Pgina 20

public static <T extends Comparable<? super T>> T max(Collection<T> coll) This reasoning applies to almost any usage of Comparable that is intended to work for arbitrary types: You always want to use Comparable<? super T>. In general, if you have an API that only uses a type parameter T as an argument, its

uses should take advantage of lower bounded wildcards (? super T). Por el contrario, si the API only returns T, you'll give your clients more flexibility by using upper bounded wildcards (? extends T). 9.1 Wildcard Capture It should be pretty clear by now that given Set<?> unknownSet = new HashSet<String>(); ... /** Add an element t to a Set s */ public static <T> void addToSet(Set<T> s, T t) {...} The call below is illegal. addToSet(unknownSet, abc); // illegal It makes no difference that the actual set being passed is a set of strings; what matters is that the expression being passed as an argument is a set of an unknown type, which cannot be guaranteed to be a set of strings, or of any type in particular. Now, consider class Collections { ... <T> public static Set<T> unmodifiableSet(Set<T> set) { ... } } ... Set<?> s = Collections.unmodifiableSet(unknownSet); // this works! Por qu? It seems this should not be allowed; yet, looking at this specific call, it is perfectly safe to permit it. After all, unmodifiableSet() does work for any kind of Set, regardless of its element type. Because this situation arises relatively frequently, there is a special rule that allows such code under very specific circumstances in which the code can be proven to be seguro. This rule, known as wildcard capture, allows the compiler to infer the unknown type of a wildcard as a type argument to a generic method. 10 Converting Legacy Code to Use Generics Earlier, we showed how new and legacy code can interoperate. Now, it's time to look at the harder problem of generifying old code.

If you decide to convert old code to use generics, you need to think carefully about how you modify the API. 20
Pgina 21

You need to make certain that the generic API is not unduly restrictive; it must continue to support the original contract of the API. Consider again some examples from java.util.Collection. The pre-generic API looks like: interface Collection { public boolean containsAll(Collection c); public boolean addAll(Collection c); } A naive attempt to generify it is: interface Collection<E> { public boolean containsAll(Collection<E> c); public boolean addAll(Collection<E> c); } While this is certainly type safe, it doesn't live up to the API's original contract. The containsAll() method works with any kind of incoming collection. It will only succeed if the incoming collection really contains only instances of E, but: The static type of the incoming collection might differ, perhaps because the caller doesn't know the precise type of the collection being passed in, or perhaps because it is a Collection<S>,where S is a subtype of E. It's perfectly legitimate to call containsAll() with a collection of a different type. The routine should work, returning false. In the case of addAll(), we should be able to add any collection that consists of instances of a subtype of E. We saw how to handle this situation correctly in section 5. You also need to ensure that the revised API retains binary compatibility with old los clientes. This implies that the erasure of the API must be the same as the original, ungenerified API. In most cases, this falls out naturally, but there are some subtle los casos. We'll examine one of the subtlest cases we've encountered, the method Collections.max(). As we saw in section 9, a plausible signature for max() is: public static <T extends Comparable<? super T>> T max(Collection<T> coll)

This is fine, except that the erasure of this signature is public static Comparable max(Collection coll) which is different than the original signature of max(): public static Object max(Collection coll) One could certainly have specified this signature for max(), but it was not done, and all the old binary class files that call Collections.max() depend on a signature that returns Object. 21
Pgina 22

We can force the erasure to be different, by explicitly specifying a superclass in the bound for the formal type parameter T. public static <T extends Object & Comparable<? super T>> T max(Collection<T> coll) This is an example of giving multiple bounds for a type parameter, using the syntax T1& T2 ... & Tn. A type variable with multiple bounds is known to be a subtype of all of the types listed in the bound. When a multiple bound is used, the first type mentioned in the bound is used as the erasure of the type variable. Finally, we should recall that max only reads from its input collection, and so is applicable to collections of any subtype of T. This brings us to the actual signature used in the JDK: public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll) It's very rare that anything so involved comes up in practice, but expert library designers should be prepared to think very carefully when converting existing APIs. Another issue to watch out for is covariant returns, that is, refining the return type of a method in a subclass. You should not take advantage of this feature in an old API. To see why, let's look at an example. Assume your original API was of the form public class Foo { public Foo create(){...} // Factory, should create an instance of whatever class it is declared in } public class Bar extends Foo {

public Foo create(){...} // actually creates a Bar } Taking advantage of covariant returns, you modify it to: public class Foo { public Foo create(){...} // Factory, should create an instance of whatever class it is declared in } public class Bar extends Foo { public Bar create(){...} // actually creates a Bar } Now, assume a third party client of your code wrote public class Baz extends Bar { public Foo create(){...} // actually creates a Baz } The Java virtual machine does not directly support overriding of methods with different return types. This feature is supported by the compiler. Consequently, unless 22
Pgina 23

the class Baz is recompiled, it will not properly override the create() method of Bar. Furthermore, Baz will have to be modified, since the code will be rejected as written - the return type of create() in Baz is not a subtype of the return type of create() in Bar. 11 Acknowledgements Erik Ernst, Christian Plesner Hansen, Jeff Norton, Mads Torgersen, Peter von der Ahe and Philip Wadler contributed material to this tutorial. Thanks to David Biesack, Bruce Chapman, David Flanagan, Neal Gafter,Orjan Petersson, Scott Seligman, Yoshiki Shibata and Kresten Krab Thorup for valuable feedback on earlier versions of this tutorial. Apologies to anyone whom I've forgotten.

S-ar putea să vă placă și