stringtranslate.com

Genéricos en Java

Los genéricos son una facilidad de programación genérica que se agregaron al lenguaje de programación Java en 2004 dentro de la versión J2SE 5.0. Fueron diseñados para ampliar el sistema de tipos de Java para permitir que "un tipo o método opere en objetos de varios tipos mientras proporciona seguridad de tipos en tiempo de compilación". [1] El aspecto de seguridad de tipos en tiempo de compilación no se logró completamente, ya que se demostró en 2016 que no está garantizado en todos los casos. [2] [3]

El marco de colecciones de Java admite genéricos para especificar el tipo de objetos almacenados en una instancia de colección.

En 1998, Gilad Bracha , Martin Odersky , David Stoutamire y Philip Wadler crearon Generic Java, una extensión del lenguaje Java para admitir tipos genéricos. [4] Java genérico se incorporó a Java con la adición de comodines .

Jerarquía y clasificación

Según la especificación del lenguaje Java : [5]

Motivación

El siguiente bloque de código Java ilustra un problema que existe cuando no se utilizan genéricos. Primero, declara un ArrayListtipo Object. Luego, agrega un Stringal ArrayList. Finalmente, intenta recuperar el agregado Stringy convertirlo en un Integererror de lógica, ya que generalmente no es posible convertir una cadena arbitraria en un número entero.

Lista final v = nueva ArrayList (); v . agregar ( "prueba" ); // Una cadena que no se puede convertir a un entero final Integer i = ( Integer ) v . obtener ( 0 ); // Error de tiempo de ejecución            

Aunque el código se compila sin errores, genera una excepción de tiempo de ejecución ( java.lang.ClassCastException) al ejecutar la tercera línea de código. Este tipo de error lógico se puede detectar durante el tiempo de compilación mediante el uso de genéricos [7] y es la motivación principal para usarlos. [6] Define una o más variables de tipo que actúan como parámetros.

El fragmento de código anterior se puede reescribir usando genéricos de la siguiente manera:

Lista final <Cadena> v = nueva ArrayList <Cadena> ( ) ;v . agregar ( "prueba" ); Entero final i = ( Entero ) v . obtener ( 0 ); // (error de tipo) error en tiempo de compilación           

El parámetro de tipo Stringentre corchetes angulares declara que ArrayListestá constituido por String(un descendiente de los constituyentes ArrayListgenéricos de Object). Con los genéricos, ya no es necesario convertir la tercera línea a ningún tipo en particular, porque el resultado de v.get(0)está definido Stringpor el código generado por el compilador.

La falla lógica en la tercera línea de este fragmento se detectará como un error en tiempo de compilación (con J2SE 5.0 o posterior) porque el compilador detectará que v.get(0)devuelve Stringen lugar de Integer. [7] Para un ejemplo más elaborado, ver referencia. [9]

Aquí hay un pequeño extracto de la definición de las interfaces java.util.Listy java.util.Iteratordel paquete java.util:

 Lista de interfaz <E> {  anular agregar ( E x ); Iterador <E> iterador ( ) ;    } iterador de interfaz <E> {  mi siguiente (); booleano hasSiguiente ();   }

Definiciones de clases genéricas

A continuación se muestra un ejemplo de una clase Java genérica, que se puede utilizar para representar entradas individuales (asignaciones de clave a valor) en un mapa :

Entrada de clase pública < Tipo de clave , Tipo de valor > { clave de tipo de clave final privada ; valor ValueType final privado ;              Entrada pública ( clave KeyType , valor ValueType ) { this . clave = clave ; este . valor = valor ; }              tipo de clave pública getKey () { clave de retorno ; }       public ValueType getValue () { valor de retorno ; }       public String toString () { return "(" + clave + ", " + valor + ")" ; }                }

Esta clase genérica podría usarse de las siguientes maneras, por ejemplo:

Entrada final < Cadena , Cadena > calificación = nueva Entrada < Cadena , Cadena > ( "Mike" , "A" ); Entrada final < Cadena , Entero > marca = nueva Entrada < Cadena , Entero > ( "Mike" , 100 ); Sistema . afuera . println ( "calificación: " + calificación ); Sistema . afuera . println ( "marca: " + marca );                    Entrada final < Entero , Booleano > principal = nueva Entrada < Entero , Booleano > ( 13 , verdadero ); if ( prime . getValue ()) { Sistema . afuera . println ( prime.getKey ( ) + "es primo." ) ; } más { Sistema . afuera . println ( prime.getKey ( ) + "no es primo." ) ; }                 

Produce:

grado: (Mike, A)marca: (Mike, 100)13 es primo.

Definiciones de métodos genéricos

A continuación se muestra un ejemplo de un método genérico que utiliza la clase genérica anterior:

public static <Tipo> Entrada < Tipo , Tipo > dos veces ( Tipo valor ) { return nueva Entrada < Tipo , Tipo > ( valor , valor ) ; }            

Nota: Si eliminamos el primero <Type>en el método anterior, obtendremos un error de compilación (no se puede encontrar el símbolo "Tipo"), ya que representa la declaración del símbolo.

En muchos casos, el usuario del método no necesita indicar los parámetros de tipo, ya que se pueden inferir:

Entrada final < Cadena , Cadena > par = Entrada . dos veces ( "Hola" );     

Los parámetros se pueden agregar explícitamente si es necesario:

Entrada final < Cadena , Cadena > par = Entrada . <Cadena> dos veces ( "Hola" ) ;     

No se permite el uso de tipos primitivos y en su lugar se deben utilizar versiones en caja :

Entrada final < int , int > par ; // Falla la compilación. Utilice Entero en su lugar.    

También existe la posibilidad de crear métodos genéricos basados ​​en parámetros dados.

público <Tipo> Tipo [ ] toArray ( Tipo ... elementos ) { elementos de retorno ; }       

En tales casos tampoco puedes usar tipos primitivos, por ejemplo:

Entero [] matriz = toArray ( 1 , 2 , 3 , 4 , 5 , 6 );        

operador de diamantes

Gracias a la inferencia de tipos , Java SE 7 y superiores permiten al programador sustituir un par de corchetes angulares vacíos ( <>llamado operador de diamante ) por un par de corchetes angulares que contienen uno o más parámetros de tipo que implica un contexto suficientemente cercano . [10] Por lo tanto, el ejemplo de código anterior Entrypuede reescribirse como:

Entrada final < Cadena , Cadena > calificación = nueva Entrada <> ( "Mike" , "A" ); Entrada final < Cadena , Entero > marca = nueva Entrada <> ( "Mike" , 100 ); Sistema . afuera . println ( "calificación: " + calificación ); Sistema . afuera . println ( "marca: " + marca );                  Entrada final < Entero , Booleano > principal = nueva Entrada <> ( 13 , verdadero ); if ( prime . getValue ()) Sistema . afuera . println ( prime.getKey ( ) + "es primo." ) ; más Sistema . afuera . println ( prime . getKey () + "no es primo." );              

Escriba comodines

Un argumento de tipo para un tipo parametrizado no se limita a una clase o interfaz concreta. Java permite el uso de "comodines de tipo" para que sirvan como argumentos de tipo para tipos parametrizados. Los comodines son argumentos de tipo con el formato " <?>"; opcionalmente con un límite superior o inferior . Dado que se desconoce el tipo exacto representado por un comodín, se imponen restricciones sobre el tipo de métodos que se pueden llamar en un objeto que utiliza tipos parametrizados.

A continuación se muestra un ejemplo en el que el tipo de elemento de a Collection<E>está parametrizado mediante un comodín:

Colección final <?> c = nueva ArrayList < String > (); C . agregar ( nuevo objeto ()); // error en tiempo de compilación c . agregar ( nulo ); // permitido        

Como no sabemos qué csignifica el tipo de elemento, no podemos agregarle objetos. El add()método toma argumentos de tipo E, el tipo de elemento de la Collection<E>interfaz genérica. Cuando el argumento de tipo real es ?, representa algún tipo desconocido. Cualquier valor de argumento de método que le pasemos al add()método tendría que ser un subtipo de este tipo desconocido. Como no sabemos de qué tipo es, no podemos pasar nada. La única excepción es nula ; que es miembro de cada tipo. [11]

Para especificar el límite superior de un comodín de tipo, extendsse utiliza la palabra clave para indicar que el argumento de tipo es un subtipo de la clase delimitadora. [12] Entonces significa que la lista dada contiene objetos de algún tipo desconocido que extiende la clase. Por ejemplo, la lista podría ser o . Leer un elemento de la lista devolverá un archivo . Nuevamente, también se permite agregar elementos nulos. [13]List<? extends Number>NumberList<Float>List<Number>Number

El uso de comodines arriba agrega flexibilidad [12] ya que no existe ninguna relación de herencia entre dos tipos parametrizados con un tipo concreto como argumento de tipo. Ninguno List<Number>ni List<Integer>es un subtipo del otro; aunque Integeres un subtipo de Number. [12] Entonces, cualquier método que tome List<Number>como parámetro no acepta un argumento de List<Integer>. Si así fuera, sería posible insertar un Numberque no sea un Integeren él; lo que viola la seguridad de tipo. A continuación se muestra un ejemplo que demuestra cómo se violaría la seguridad de tipos si List<Integer>fuera un subtipo de List<Number>:

Lista final <Entero> ints = nueva ArrayList <> ( ) ; enteros . agregar ( 2 ); Lista final < Número > nums = ints ; // válido si List<Integer> fuera un subtipo de List<Number> según la regla de sustitución. números . añadir ( 3.14 ); Entero final x = enteros . obtener ( 1 ); // ¡ahora 3.14 está asignado a una variable entera!                

La solución con comodines funciona porque no permite operaciones que violarían la seguridad de tipos:

Lista final <? extiende Número > nums = ints ; // OK números . añadir ( 3.14 ); // números de error en tiempo de compilación . agregar ( nulo ); // permitido         

Para especificar la clase de límite inferior de un tipo comodín, superse utiliza la palabra clave. Esta palabra clave indica que el argumento de tipo es un supertipo de la clase delimitadora. Entonces, podría representar o . La lectura de una lista definida como devuelve elementos de tipo . Agregar a dicha lista requiere elementos de tipo , cualquier subtipo de o nulo (que es miembro de cada tipo).List<? super Number>List<Number>List<Object>List<? super Number>ObjectNumberNumber

El mnemotécnico PECS (Producer Extends, Consumer Super) del libro Effective Java de Joshua Bloch ofrece una manera fácil de recordar cuándo usar comodines (correspondientes a covarianza y contravarianza ) en Java. [12]

Genéricos en cláusula throws

Aunque las excepciones en sí no pueden ser genéricas, los parámetros genéricos pueden aparecer en una cláusula throws:

public < T extiende Throwable > void throwMeConditional ( condicional booleano , excepción T ) lanza T { if ( condicional ) { throw excepción ; } }                 

Problemas con el borrado de tipos

Los genéricos se verifican en tiempo de compilación para verificar la corrección de tipos. [7] La ​​información de tipo genérico luego se elimina en un proceso llamado borrado de tipo . [6] Por ejemplo, List<Integer>se convertirá al tipo no genérico List, que normalmente contiene objetos arbitrarios. La verificación en tiempo de compilación garantiza que el código resultante utilice el tipo correcto. [7]

Debido al borrado de tipos, los parámetros de tipo no se pueden determinar en tiempo de ejecución. [6] Por ejemplo, cuando se examina un ArrayListen tiempo de ejecución, no existe una forma general de determinar si, antes de borrar el tipo, era un ArrayList<Integer>o un ArrayList<Float>. Mucha gente no está satisfecha con esta restricción. [14] Hay enfoques parciales. Por ejemplo, se pueden examinar elementos individuales para determinar el tipo al que pertenecen; por ejemplo, si an ArrayListcontiene un Integer, es posible que ArrayList se haya parametrizado con Integer(sin embargo, se puede haber parametrizado con cualquier padre de Integer, como Numbero Object).

Para demostrar este punto, el siguiente código genera "Igual":

Lista final <Entero> li = nueva ArrayList <> ( ) ; Lista final <flotante> lf = nueva ArrayList <> ( ) ; if ( li . getClass () == lf . getClass ()) { // se evalúa como verdadero System . afuera . println ( "Igual" ); }                

Otro efecto del borrado de tipo es que una clase genérica no puede extender la Throwableclase de ninguna manera, directa o indirectamente: [15]

clase pública GenericException <T> extiende la excepción    

La razón por la que esto no es compatible se debe al borrado de tipos:

intente { lanzar una nueva excepción genérica <entero> () ; } catch ( GenericException <Integer> e ) { System .errar . println ( "Entero" ); } catch ( GenericException <String> e ) { System .errar . println ( "Cadena" ); }            

Debido al borrado de tipos, el tiempo de ejecución no sabrá qué bloque catch ejecutar, por lo que el compilador lo prohíbe.

Los genéricos de Java difieren de las plantillas de C++ . Los genéricos de Java generan solo una versión compilada de una clase o función genérica, independientemente del número de tipos de parametrización utilizados. Además, el entorno de ejecución de Java no necesita saber qué tipo parametrizado se utiliza porque la información del tipo se valida en tiempo de compilación y no se incluye en el código compilado. En consecuencia, crear una instancia de una clase Java de un tipo parametrizado es imposible porque la creación de instancias requiere una llamada a un constructor, que no está disponible si el tipo es desconocido.

Por ejemplo, el siguiente código no se puede compilar:

< T > T instanciateElementType ( Lista < T > arg ) { return new T (); //provoca un error de compilación }        

Debido a que solo hay una copia por clase genérica en tiempo de ejecución, las variables estáticas se comparten entre todas las instancias de la clase, independientemente de su parámetro de tipo. En consecuencia, el parámetro de tipo no se puede utilizar en la declaración de variables estáticas ni en métodos estáticos.

El borrado de tipos se implementó en Java para mantener la compatibilidad con programas escritos antes de Java SE5. [7]

Diferencias con las matrices

Existen varias diferencias importantes entre las matrices (tanto matrices primitivas como Objectmatrices) y los genéricos en Java. Dos de las principales diferencias, a saber, diferencias en términos de varianza y cosificación .

Covarianza, contravarianza e invarianza

Los genéricos son invariantes, mientras que las matrices son covariantes . [6] Este es un beneficio de usar genéricos en comparación con objetos no genéricos como matrices. [6] Específicamente, los genéricos pueden ayudar a prevenir excepciones en tiempo de ejecución al generar una excepción en tiempo de compilación para obligar al desarrollador a corregir el código.

Por ejemplo, si un desarrollador declara un Object[]objeto y crea una instancia del objeto como un Long[]objeto nuevo, no se genera ninguna excepción en tiempo de compilación (ya que las matrices son covariantes). [6] Esto puede dar la falsa impresión de que el código está escrito correctamente. Sin embargo, si el desarrollador intenta agregar un Stringa este Long[]objeto, el programa generará un archivo ArrayStoreException. [6] Esta excepción de tiempo de ejecución se puede evitar por completo si el desarrollador utiliza genéricos.

Si el desarrollador declara un Collection<Object>objeto y crea una nueva instancia de este objeto con tipo de retorno ArrayList<Long>, el compilador de Java generará (correctamente) una excepción en tiempo de compilación para indicar la presencia de tipos incompatibles (ya que los genéricos son invariantes). [6] Por lo tanto, esto evita posibles excepciones en tiempo de ejecución. Este problema se puede solucionar creando una instancia de Collection<Object>uso ArrayList<Object>de objeto. Para el código que usa Java SE7 o versiones posteriores, se Collection<Object>puede crear una instancia con un ArrayList<>objeto usando el operador de diamante.

Cosificación

Las matrices están cosificadas , lo que significa que un objeto de matriz impone su información de tipo en tiempo de ejecución, mientras que los genéricos en Java no están cosificados. [6]

Hablando más formalmente, los objetos con tipo genérico en Java son tipos no verificables. [6] Un tipo no verificable es un tipo cuya representación en tiempo de ejecución tiene menos información que su representación en tiempo de compilación. [6]

Los objetos con tipo genérico en Java no son verificables debido al borrado de tipo. [6] Java solo aplica información de tipo en tiempo de compilación. Después de verificar la información de tipo en tiempo de compilación, la información de tipo se descarta y, en tiempo de ejecución, la información de tipo no estará disponible. [6]

Ejemplos de tipos no verificables incluyen List<T>y List<String>, donde Tes un parámetro formal genérico. [6]

Proyecto sobre genéricos

Project Valhalla es un proyecto experimental para incubar características de lenguaje y genéricos de Java mejorados, para versiones futuras potencialmente desde Java 10 en adelante. Las posibles mejoras incluyen: [16]

Ver también

Citas

  1. ^ Lenguaje de programación Java
  2. ^ Se puede generar una ClassCastException incluso en ausencia de conversiones o nulos. "Los sistemas de tipos de Java y Scala no son sólidos" (PDF) .
  3. ^ Bloch 2018, págs. 123-125, Capítulo §5 Artículo 27: Eliminar advertencias no verificadas.
  4. ^ GJ: Java genérico
  5. ^ Especificación del lenguaje Java, tercera edición de James Gosling, Bill Joy, Guy Steele, Gilad Bracha - Prentice Hall PTR 2005
  6. ^ abcdefghijklmno Bloch 2018, págs. 126-129, Capítulo §5 Artículo 28: Prefiera listas a matrices.
  7. ^ abcdefgh Bloch 2018, págs. 117–122, Capítulo §5 Artículo 26: No utilice tipos sin formato.
  8. ^ Bloch 2018, págs. 135-138, Capítulo §5 Artículo 30: Favorecer los métodos genéricos.
  9. ^ Gilad Bracha (5 de julio de 2004). "Genéricos en el lenguaje de programación Java" (PDF) . www.oracle.com .
  10. ^ "Inferencia de tipos para la creación de instancias genéricas".
  11. ^ Gilad Bracha (5 de julio de 2004). "Genéricos en el lenguaje de programación Java" (PDF) . www.oracle.com . pag. 5.
  12. ^ abcd Bloch 2018, págs. 139-145, Capítulo §5 Artículo 31: Utilice comodines acotados para aumentar la flexibilidad de la API.
  13. ^ Bracha, Gilad . "Comodines > Bonificaciones > Genéricos". Los tutoriales de Java™ . Oráculo. ...La única excepción es nula, que es miembro de cada tipo...
  14. ^ Gafter, Neal (5 de noviembre de 2006). "Genéricos reificados para Java" . Consultado el 20 de abril de 2010 .
  15. ^ "Especificación del lenguaje Java, sección 8.1.2". Oráculo . Consultado el 24 de octubre de 2015 .
  16. ^ Goetz, Brian. "¡Bienvenidos al Valhalla!". Archivo de correo OpenJDK . AbiertoJDK . Consultado el 12 de agosto de 2014 .

Referencias