Genéricos en Java

Sintaxis

  • clase MiClase<T1, T2 extiende CharSequence> implementa Comparable<MiClase> //...
  • interface MyListInterface<T extends Serializable> extends List<T> //...
  • public <T1, T2 extends Instant> T1 provideClone(T1 toClone, T2 instant> //...
  • Public static List<CharSequence> safe(Colección<? extiende CharSequence> l) { return new ArrayList<>(l);}
  • Set<String> cadenas = Collections.singleton("Hola mundo");
  • Lista<CharSequence> chsList = seguro(cadenas);

El borrado de tipo limita la reflexión, aunque eso no es específico de JVM, por ejemplo Ceylon [usa genéricos cosificados](http://ceylon-lang.org/documentation/1.2/spec /html/execution.html#reification).

El soporte de tipo existencial no es necesariamente compatible con otros lenguajes en esta forma: Kotlin [lo admite a través de proyecciones de tipo](https://kotlinlang.org/docs/reference/java-interop. html#java-generics-in-kotlin).

Introducción

Generics se introdujo en Java en su versión (1.)5. Estos se borran durante la compilación, por lo que la reflexión en tiempo de ejecución no es posible para ellos. Los genéricos generan nuevos tipos parametrizados por otros tipos. Por ejemplo, no tenemos que crear nuevas clases para usar una colección segura de tipos de Strings y Numbers, se puede usar ArrayList<T> genérico en todos los casos, como: new ArrayList<String> ().

Ejemplo:

List<String> variable = new ArrayList<String>();

En Java 7 se introdujo algo de azúcar sintáctico para facilitar la construcción (<> también conocido como diamante):

List<String> variable = new ArrayList<>();

Curiosamente, también fue posible (desde Java 5) usar la inferencia de tipos, cuando un método estático tenía como valor de retorno (a menudo usado en Google Guava por ejemplo):

List<String> singleton = Collections.singletonList();//Note the missing `<>` or `<String>`!

En Java se usaban tipos existenciales para proporcionar polimorfismo a los tipos, ya que los tipos genéricos son invariantes (por ejemplo: List<String> no es un subtipo, ni un supertipo de List<CharSequence>, aunque en Java String [] es un subtipo de CharSequence[]; nota: String implementa la interfaz CharSequence). Los tipos genéricos existenciales se pueden expresar como:

List<? extends CharSequence> list = new ArrayList<String>();
Comparable<? super ChronoLocalDate> ccld = LocalDate.now();
ChronoLocalDate cld = JapaneseDate.now(); //ChronoLocalDate extends Comparable<ChronoLocalDate>
ccld.compareTo(cld);
//cld.compareTo(ccld);//fails to compile because ccld is not a `ChronoLocalDate` (compile time)

Ambas instancias se pueden utilizar en una lista parametrizada por el ‘Comparable’ correspondiente:

List<Comparable<? super ChronoLocalDate>> list2 = new ArrayList<>();
list2.add(cld);
list2.add(ccld);

Métodos genéricos

Los parámetros de tipo genéricos se definen comúnmente en el nivel de clase o interfaz, pero los métodos y (rara vez) los constructores también admiten la declaración de parámetros de tipo vinculados al alcance de una sola llamada de método.

class Utility // no generics at the class level
{
    @SafeVarargs
    public static <T> T randomOf(T first, T... rest) {
        int choice = new java.util.Random().nextInt(rest.length + 1);
        return choice == rest.length ? first : rest[choice];
    }

    public static <T extends Comparable<T>> T max(T t1, T t2) {
        return t1.compareTo(t2) < 0 ? t2 : t1;
    }
}

Observe que las declaraciones de parámetros de tipo, T y <T extends Comparable<T>> respectivamente, aparecen después de los modificadores de método y antes del tipo de retorno. Esto permite que el parámetro de tipo T se use dentro del alcance de tales métodos, actuando como:

  • tipos de argumentos
  • tipo de retorno
  • tipos de variables locales

Aunque los dos métodos anteriores usan el mismo nombre de parámetro de tipo T, a nivel de método son completamente independientes entre sí. El compilador inferirá el tipo real basado en los argumentos pasados ​​al método en cada sitio de llamada que invoca el método. Dado que el método max declara que T extiende Comparable<T>, el compilador también impone que los tipos inferidos sean implementaciones compatibles de la interfaz Comparable.

Integer num1 = 1;
Integer num2 = 2;
String str1 = "abc";
String str2 = "xyz";

Integer bigger = Utility.max(num1, num2);
assert bigger == num2;

String later = Utility.max(str2, str1);
assert later == str2;

Utility.max(num1, str1); // compiler error: num1 and str1 are incompatible types

Utility.max(new Object(), new Object()); // compiler error: Object does not implement Comparable

Java 8 mejoró significativamente la capacidad del compilador para inferir correctamente los tipos genéricos en los sitios de llamadas. Si el compilador no puede inferir el tipo adecuado, los desarrolladores pueden indicar explícitamente el tipo como parte de la llamada:

Object obj = Utility.<Object>randomOf(str1, new Object(), num1);