Tengo una interfaz genérica que puede ser implementada por diferentes subclases de una clase base abstracta.

interface PropertyOne<T> {
  T type();
  List<String> values();
}

En la clase base, definí un pequeño método auxiliar que verifica si la instancia actual implementa esa interfaz y la envía si es posible:

<T> Optional<T> toProperty(Class<T> clazz){
    if (clazz.isAssignableFrom(this.getClass())){
      return Optional.of(clazz.cast(this));
    }
    return Optional.empty();
}

Utilizo este método auxiliar, por ejemplo, para obtener e imprimir esos valores:

void printValues() {
    List<String> values = toProperty(PropertyOne.class)
      .map(p -> p.values())
      .orElse(Collections.emptyList());
    System.out.println(values);
}

Pero esto me da una advertencia de lanzamiento inseguro, ya que values() ahora devuelve un List en lugar de un List<String>. Sé que la información de tipo genérico se pierde después de compilar el código, pero dado que el tipo de retorno de values es siempre un List de tipo String, ¿debería ser seguro de todos modos?

El uso del siguiente código repetitivo funciona sin advertencias.

if ( this instanceof PropertyOne<?>){
   List<String> vs = ((PropertyOne<?>)this).values();
   System.out.println(vs);
}

Puedo hacer un lanzamiento manual para la primera variante así como .map(p -> ((PropertyOne<?>)p).values()) para deshacerme de la advertencia, pero esto nuevamente agrega mucho texto estándar.

Entonces tengo dos preguntas:

  1. ¿Por qué se pierde la información de tipo List<String> en primer lugar?
  2. ¿Es posible lanzar dinámicamente a un tipo comodín / ilimitado directamente sin hacer una conversión manualmente a PropertyOne<?> después?
0
Leikingo 1 sep. 2020 a las 01:40

1 respuesta

La mejor respuesta

Debido a que los genéricos en Class<T> son bastante limitados y no se pueden usar de esta manera (yo diría que los genéricos en j.l.Class están dañados, pero eso quizás sea un poco duro. También tienen algún uso, solo. . no mucho). Los genéricos pueden representar cosas que una instancia de clase no puede; instancias de clase (una instancia de java.lang.Class puede representar cosas que los genéricos no pueden. Específicamente, List<String> no se puede expresar como una instancia de clase, y int.class es una instancia de clase que no se puede expresar en genéricos.

En otras palabras, en el momento en que intentas comunicar un tipo genérico completo (como PropertyOne<String>) en una instancia java.lang.Class<T>, perdiste el juego: esa T en j.l.Class solo puede ser un tipo sin genéricos.

Entonces, esta no es la forma de hacerlo.

Su método toProperty tiene que irse. No por los problemas de esta publicación; se trata de intentar invocar type() en su instancia PropertyOne derivada de la llamada toProperty (envuelta opcionalmente).

Aparte de eso, la razón por la que tiene problemas con su invocación values(), que parece no tener partes genéricas relevantes, es debido a las reglas arcaicas de Java: una vez que se vuelve 'crudo', TODO es crudo, incluso las cosas que no lo necesitan.

Entonces, en toProperty(PropertyOne.class), eso terminaría devolviendo un Optional<PropertyOne> - un tipo sin formato, porque eso no está bien; que necesitarías, por ejemplo PropertyOne<?> que es diferente de PropertyOne (el último está en bruto; el primero no lo es), pero no puedes hacer eso con Class<T>, porque Class<T> está roto. Ahora estás en territorio sin formato, e invocar values() en este tipo sin formato terminará devolviendo una lista sin formato (solo List), pensarías que todavía te da un List<String> como el tipo de retorno de values() no depende de los genéricos de PropertyOne pero no es así como funciona, la especificación java lang dice que no.

Hay algunas formas de salir de este enigma, pero la mayoría de las veces solo está haciendo un mal uso de los genéricos aquí, realmente no puede hacer nada de esto. Recuerde, los genéricos son producto de la imaginación del compilador: no existen en tiempo de ejecución en absoluto. No hay forma de verificar nada en tiempo de ejecución. Una vez que empiece a mezclar genéricos y construcciones en tiempo de ejecución como instanceof o x.isAssignableFrom, nunca funcionará.

Si es necesario, puede llegar allí con algo llamado 'Super Type Tokens'; puede buscar en Google (agregue 'java' como palabra clave), estos PUEDEN representar con bastante precisión cualquier cosa que pueda colocar dentro de los genéricos. Pero eso todavía suena como un error; pero realmente no puedo decirle lo que tiene que hacer aquí, la descripción de su problema se refiere a una solución defectuosa y no al problema que su solución defectuosa está tratando de resolver.

0
rzwitserloot 31 ago. 2020 a las 22:49