1. Por qué: la razón para introducir mecanismos genéricos
Si queremos implementar una matriz de cadenas y exigirle que cambie dinámicamente el tamaño, todos pensaremos en usar ArrayList para agregar objetos de cadena. Sin embargo, después de un tiempo, queremos implementar una matriz de objetos de fecha cuyo tamaño se pueda cambiar. En este momento, ciertamente esperamos poder reutilizar la implementación de ArrayList para los objetos de cadena que escribí antes.
Antes de Java 5, la implementación de ArrayList es aproximadamente la siguiente:
Public Class ArrayList {Public Object get (int i) {...} public void add (objeto o) {...} ... objeto privado [] elementData;}En el código anterior, podemos ver que la función Agregar utilizada para agregar elementos a la lista de matrices recibe un parámetro de tipo objeto. El método GET que obtiene el elemento especificado de ArrayList también devuelve un objeto de tipo de objeto. La matriz de objeto ElementData almacena el objeto en ArrayList. Es decir, sin importar qué tipo de tipo que ponga en la lista de matrices, es un objeto de objeto dentro de él.
La implementación genérica basada en la herencia traerá dos problemas: la primera pregunta es sobre el método GET. Cada vez que llamemos al método GET, devolveremos un objeto de objeto, y cada vez que tengamos que lanzar el tipo del tipo que necesitamos, lo que parecerá muy problemático; La segunda pregunta es sobre el método ADD. Si agregamos un objeto de archivo a ArrayList que agregue el objeto String, el compilador no generará ninguna indicación de error, que no es lo que queremos.
Por lo tanto, a partir de Java 5, ArrayList se puede usar para agregar un parámetro tipo (parámetro de tipo) al usarlo. Este parámetro de tipo se usa para indicar el tipo de elemento en ArrayList. La introducción de los parámetros de tipo resuelve los dos problemas mencionados anteriormente, como se muestra en el siguiente código:
ArrayList <String> S = new ArrayList <String> (); S.Add ("ABC"); String S = S.get (0); // no es necesario lanzar s.add (123); // Error de compilación, solo puede agregarle objetos de cadena ...En el código anterior, después de que el compilador "conoce" la cadena de parámetros de tipo de ArrayList, completará la verificación de fundición y tipo para nosotros.
2. Clases genéricas
La llamada clase genérica es una clase con uno o más parámetros de tipo. Por ejemplo:
par de clase pública <t, u> {private t primero; privado u segundo; par de par (t primero, u segundo) {this.first = primero; this.second = segundo; } public t getFirst () {return primero; } public u getSecond () {return Second; } public void setFirst (t newValue) {first = newValue; }}En el código anterior, podemos ver que los parámetros de tipo del par de clase genérico son T y U, y se colocan en los soportes de ángulo después del nombre de la clase. Aquí, T significa la primera letra de tipo, que representa el tipo. Comúnmente utilizados son E (
Al instancias de una clase genérica, solo necesitamos reemplazar el parámetro de tipo con un tipo específico, como instanciar un par <t, u> clase, podemos hacer esto:
Par <string, integer> par = new Par <String, Integer> ();
3. Métodos genéricos
El llamado método genérico es un método con parámetros de tipo. Se puede definir en una clase genérica o en una clase normal. Por ejemplo:
Public Class ArrayAlg {public static <t> t getMiddle (t [] a) {return a [a.length / 2]; }}El método GetMiddle en el código anterior es un método genérico, y el formato definido es que la variable de tipo se coloca después del modificador y antes del tipo de retorno. Podemos ver que los métodos genéricos anteriores pueden ser llamados para varios tipos de matrices. Cuando se sabe que los tipos de estas matrices son limitados, aunque también se pueden implementar con sobrecarga, la eficiencia de codificación es mucho menor. El código de ejemplo para llamar al método genérico anterior es el siguiente:
String [] Strings = {"Aa", "Bb", "CC"};
String Middle = Arrayalg.getMiddle (nombres);
4. Limitación de variables de tipo
En algunos casos, las clases genéricas o los métodos genéricos desean limitar aún más sus parámetros de tipo. Por ejemplo, si queremos definir los parámetros de tipo que solo pueden ser subclases de una determinada clase o solo clases que implementan una determinada interfaz. La sintaxis relevante es la siguiente:
<T extiende BoundingType> (BoundingType es una clase o interfaz). Puede haber más de 1 BoundingType, solo use "y" para conectarse.
5. Comprender la implementación de genéricos
De hecho, desde la perspectiva de las máquinas virtuales, no hay un concepto de "genéricos". Por ejemplo, el par de clases genérico que definimos anteriormente se ve así en la máquina virtual (es decir, después de ser compilada en bytecode):
Par de clase pública {objeto privado primero; objeto privado segundo; PUBLICO PUBLIC (Object First, Object Second) {this.first = primero; this.second = segundo; } Public Object getFirst () {return primero; } objeto público getSecond () {return Second; } public void setFirst (objeto newValue) {first = newValue; } public void setSecond (objeto newValue) {Second = newValue; }}La clase anterior se obtiene mediante borrado de tipo y es el tipo sin procesar correspondiente a la clase genérica de par. Tipo de borrado significa reemplazar todos los parámetros de tipo con BoundingType (reemplácelo con objeto si no se agregan restricciones).
Simplemente podemos verificar eso después de compilar par.java, escriba "par javap -c -c -s" para obtener:
La línea con "descriptor" en la figura anterior es la firma del método correspondiente. Por ejemplo, desde la cuarta línea, podemos ver que los dos parámetros formales del constructor de par se han convertido en objeto después de borrar el tipo.
Dado que el par de clase genérico se convierte en su tipo sin procesar en la máquina virtual, el método GetFirst devuelve un objeto de objeto, y desde la perspectiva del compilador, este método devuelve un objeto del parámetro de tipo especificado cuando instanciamos la clase. De hecho, es el compilador el que nos ayuda a completar el trabajo de casting. En otras palabras, el compilador convertirá la llamada al método getFirst en la clase genérica de par en dos instrucciones de máquina virtual:
El primero es una llamada al método de tipo sin procesar getFirst, que devuelve un objeto de objeto; La segunda instrucción emite el objeto devuelto al tipo de parámetro Tipo que especificamos.
El tipo de borrado de tipo también ocurre en métodos genéricos, como los siguientes métodos genéricos:
Pública estática <t extiende comparable> t min (t [] a)
Después de la compilación, se volverá así después de borrar el tipo:
Min estático público comparable (comparable [] a)
El tipo de borrado de los métodos puede causar algunos problemas, considere el siguiente código:
class DateInterval extiende el par <Date, date> {public void setSecond (date segundo) {if (Second.Compareto (getFirst ())> = 0) {super.setSecond (segundo); }} ...}Después de que el código anterior se borra por tipo, se convierte en:
clase DateInterval extiende el par {public void setSecond (fecha segunda) {...} ...}En la clase DateInterval, también hay un método SetSegundo heredado de la clase de par (después de borrar el tipo) de la siguiente manera:
Public void setSecond (objeto segundo)
Ahora podemos ver que este método tiene diferentes firmas de métodos (diferentes parámetros formales) del método SetSeCond anulado por DateInterval, por lo que son dos métodos diferentes, sin embargo, estos dos métodos no deberían ser métodos diferentes (porque está anulada). Considere el siguiente código:
DateInterval Interval = new DateInterval (...); par <date, date> par = interval; date adate = new date (...); par.setsecond (adate);
En el código anterior, podemos ver que el par realmente se refiere al objeto DateInterval, por lo que se debe llamar al método SetSeCond de DateInterval. El problema aquí es ese tipo de borrado de conflictos con el polimorfismo.
Ordenemos por qué ocurre este problema: el par se declaró previamente como tipo de par <fecha, fecha>, y esta clase parece tener solo un método "setSegund (object)" en la máquina virtual. Por lo tanto, cuando se ejecuta, la máquina virtual descubre que el par realmente se refiere al objeto DateInterval, llamará al método "setSeCond (object)" de DateInterval, pero solo está el método "setSecond (date)" en la clase DateInterval.
La solución a este problema es generar un método de puente en DateInterval por el compilador:
public void setSecond (objeto segundo) {setSecond ((date) segundo);}6. cosas a tener en cuenta
(1) Los parámetros de tipo no se pueden instanciar con tipos básicos
Es decir, la siguiente declaración es ilegal:
Par <int, int> par = new Par <int, int> ();
Sin embargo, podemos usar el tipo de embalaje correspondiente en su lugar.
(2) no puede tirar ni capturar instancias de clase genérica
La extensión de clase genérica lanzable es ilegal, por lo que las instancias de clase genéricas no pueden ser arrojadas o capturadas. Pero es legal usar parámetros de tipo en declaraciones de excepción:
public static <t extiende lando> void dowork (t t) lanza t {try {...} catch (lanzable realCause) {t.initCause (realCause); tirar t; }}(3) La matriz parametrizada es ilegal
En Java, una matriz de objeto [] puede ser la clase principal de cualquier matriz (porque cualquier matriz puede transformarse hacia arriba en una matriz de la clase principal que especifica el tipo de elemento cuando se define). Considere el siguiente código:
Cadena [] strs = nueva cadena [10]; objeto [] objs = strs; obj [0] = nueva fecha (...);
En el código anterior, asignamos el elemento de matriz a un objeto que satisface el tipo de clase principal (objeto), pero a diferencia del tipo original (par), puede pasar en el momento de la compilación, y una excepción de ArrayStoreException se lanzará en tiempo de ejecución.
Según las razones anteriores, suponga que Java nos permite declarar e inicializar una matriz genérica a través de la siguiente declaración:
Par <string, string> [] pars = new Par <String, String> [10];
Luego, después de que la máquina virtual realiza borrado de tipo, los pares en realidad se convierten en matrices de par [], y podemos transformarlo hacia arriba en una matriz de objeto []. En este momento, si agregamos objetos de par <fecha, fecha>, podemos pasar cheques de tiempo de compilación y cheques en tiempo de ejecución. Nuestra intención original es simplemente dejar que esta matriz almacena los objetos del par <cadena, cadena>, lo que causará difícil localizar errores. Por lo tanto, Java no nos permite declarar e inicializar una matriz genérica a través del formulario de instrucción anterior.
Se puede declarar e inicializar una matriz genérica utilizando la siguiente declaración:
Par <string, string> [] pars = (par <string, string> []) nuevo par [10];
(4) La variable de tipo no se puede instanciar
Las variables de tipo no se pueden usar en formas como "nueva t (...)", "nueva t [...]", "t.class". La razón por la cual Java nos prohíbe hacer esto es simple. Debido a que hay un tipo de borrado, declaraciones como "New T (...)" se convertirán en "Objeto nuevo (...)", que generalmente no es lo que queremos decir. Podemos reemplazar la llamada a "nueva T [...]" con la siguiente declaración:
matrices = (t []) nuevo objeto [n];
(5) Las variables de tipo no se pueden usar en el contexto estático de las clases genéricas
Tenga en cuenta que enfatizamos las clases genéricas aquí. Debido a que los métodos genéricos estáticos se pueden definir en clases ordinarias, como el método GetMiddle en la clase ArrayAlg mencionada anteriormente. Por razones de tal regla, considere el siguiente código:
gente de clase pública <t> {nombre público estático t; public static t getName () {...}}Sabemos que al mismo tiempo, puede haber más de una instancia de clase <t> en la memoria. Supongamos que ahora hay un objeto People <String> y objeto de personas <integer> en la memoria, y las variables estáticas y los métodos estáticos de la clase son compartidos por todas las instancias de clase. Entonces, la pregunta es, ¿es el tipo de cadena de nombre o el tipo de entero? Por esta razón, las variables de tipo no están permitidas en Java que se usen en contextos estáticos de clases genéricas.
7. Escriba comodín
Antes de introducir el Type Wildcard, primero introduzca dos puntos:
(1) Supongamos que el estudiante es una subclase de personas, pero pareja <estudiante, estudiante> no es una subclase de pares <personas, personas>, y no hay una relación "es" entre ellos.
(2) Existe una relación "IS-A" entre el par <t, t> y su par de tipo original. El par <t, t> se puede convertir al tipo de par en cualquier caso.
Ahora considere este método:
public static void printname (par <personas, personas> p) {personas p1 = p.getFirst (); System.out.println (p1.getName ()); // Suponga que la clase People define el método de instancia getName}En el método anterior, queremos poder pasar parámetros de pares <estudiante, estudiante> y pares <personas, personas> al mismo tiempo, pero no hay una relación "is-a" entre los dos. En este caso, Java nos proporciona una solución: ¿usar par <? extiende a las personas> como el tipo de parámetro formal. Es decir, emparejarse <estudiante, estudiante> y par <personas, las personas> se pueden considerar subclases de par <? extiende personas>.
El código que parece "<? Extiende BoundingType>" se llama limitación de subtipo de los caracteres comodines. Correspondiente a esto es la limitación de súper tipo de los caracteres comodín, el formato es el siguiente: <? Super BoundingType>.
Ahora consideremos el siguiente código:
Pare <Student> Students = New Par <Student> (Student1, Student2); par <? extiende a las personas> chasquinas silvestres = estudiantes; chasquillas Wilds.setFirst (People1);
La tercera línea del código anterior informará un error porque WildChards es un par <? extiende las personas> objeto, y su método SetFirst y el método getFirst son los siguientes:
Void setFirst (? Extensa a las personas)? extiende a las personas getFirst ()
Para el método SetFirst, el compilador no sabrá qué tipo de parámetros formales son (solo se sabe que es una subclase de las personas). Cuando intentamos pasar por un objeto People, el compilador no puede determinar si las personas y los parámetros formales son "IS-A", por lo que llamar al método SetFirst informará un error. Es legal llamar al método GetFirst Wildchirst porque sabemos que devolverá una subclase de personas, y la subclase de las personas "siempre es un pueblo". (Siempre puede convertir objetos de subclase a objetos principales)
En el caso de la limitación de supertipo de comodín, llamar al método Getter es ilegal, mientras que llamar al método de Setter es legal.
Además de las limitaciones de subtipo y las limitaciones de supertipo, también hay un comodín llamado comodín infinito, que se ve así: <?>. ¿Cuándo usaremos esta cosa? Considere este escenario. Cuando llamamos a un método, devolveremos un método GetPays, que devolverá un conjunto de objetos de par <t, t>. Entre ellos se encuentran los objetos de pareja <estudiante, estudiante> y de pareja <maestro, maestro>. (No existe una relación de herencia entre la clase de estudiantes y la clase de maestro) obviamente, en este caso, no se puede usar tanto la limitación del subtipo como la limitación de supertipo. En este momento, podemos usar esta declaración para resolverla:
Par <?> [] Pars = getPairs (...);