Java:La relación de tipo genérico cíclico no permite la conversión desde supertipo (error de Javac)

StackOverflow https://stackoverflow.com/questions/804247

  •  03-07-2019
  •  | 
  •  

Pregunta

Encuentro un comportamiento totalmente extraño del compilador de Java.
No puedo convertir un supertipo a un subtipo cuando relación de tipo genérico cíclico esta involucrado.

Caso de prueba JUnit para reproducir el problema:

public class _SupertypeGenericTest {

    interface ISpace<S extends ISpace<S, A>, A extends IAtom<S, A>> {
    }

    interface IAtom<S extends ISpace<S, A>, A extends IAtom<S, A>> {
    }

    static class Space
            implements ISpace<Space, Atom> {
    }

    static class Atom
            implements IAtom<Space, Atom> {
    }

    public void test() {
        ISpace<?, ?> spaceSupertype = new Space();
        IAtom<?, ?> atomSupertype = new Atom();

        Space space = (Space) spaceSupertype;  // cast error
        Atom atom = (Atom) atomSupertype;  // cast error
    }
}

Salida de error del compilador:

_SupertypeGenericTest.java:33: inconvertible types
found   : pinetag.data._SupertypeGenericTest.ISpace<capture#341 of ?,capture#820 of ?>
required: pinetag.data._SupertypeGenericTest.Space
                Space space = (Space) spaceSupertype;
                                    ^

_SupertypeGenericTest.java:34: inconvertible types
found   : pinetag.data._SupertypeGenericTest.IAtom<capture#94 of ?,capture#48 of ?>
required: pinetag.data._SupertypeGenericTest.Atom
                Atom atom = (Atom) atomSupertype;
                                ^
2 errors

Nota:Estoy usando el último tronco de Netbeans, Ant incluido, la última versión de Java 6.
Intenté usar Ant desde la línea de comandos (Netbeans genera un archivo build.xml) pero da lugar a los mismos errores.

¿Lo que está mal?
¿Existe una forma elegante de resolver el problema?

Lo extraño es:Netbeans no marca errores (ni siquiera advertencias) en el código dado.

EDITAR:
No, ahora lo entiendo nada!
Eclipse 3.4.1 no marca ni advertencias ni errores, y compila el código sin problemas!!
¿Cómo puede ser esto?Pensé, usando Ant desde la línea de comandos junto con build.xml proporcionado por Netbeans' sería neutral.
¿Me estoy perdiendo de algo?

EDITAR 2:
Usando JDK7 y el formato de código JDK7, netbeans compila sin ¡Errores/advertencias!
(Estoy usando 1.7.0-ea-b55)

EDITAR 3:
Se cambió el título para indicar que estamos tratando con un error de javac.

¿Fue útil?

Solución

No pretendo comprender fácilmente esos tipos genéricos complejos, pero si encuentra algún código que se compile en javac y no en ecj (el compilador de eclipse), luego presente un informe de error con ambos Sol y Eclipse y describa las situaciones claramente (mejor si también menciona que presentó ambos informes de error y menciona sus respectivas URL, aunque para Sun puede pasar un tiempo antes de que el error sea accesible públicamente).

Lo hice en el pasado y obtuve muy buenas respuestas donde

  1. uno de los equipos descubrió cuál era el enfoque correcto (indicar error de compilación, advertencia o nada)
  2. y el compilador defectuoso fue reparado

Dado que ambos compiladores implementan la misma especificación, uno de ellos es, por definición, incorrecto, si solo uno de ellos compila el código.

Para el registro:

Intenté compilar el código de muestra con javac (javac 1.6.0_13) y ecj (Compilador Java Eclipse 0.894_R34x, versión 3.4.2) y javac se quejó en voz alta y no logró presentar ninguna .class archivos, mientras ecj solo se quejó de algunas variables no utilizadas (advertencias) y produjo todo lo esperado .class archivos.

Otros consejos

Terminé usando no genéricos para esto:

    @Test
    public void test() {
            ISpace spaceSupertype = new Space();
            IAtom atomSupertype = new Atom();

            Space space = (Space) spaceSupertype;  // ok
            Atom atom = (Atom) atomSupertype;  // ok
    }

¿qué le impide usar los tipos sin comodines?

public void test() {
    ISpace<Space, Atom> spaceSupertype = new Space();
    IAtom<Space, Atom> atomSupertype = new Atom();

    Space space = (Space) spaceSupertype;  // no error
    Atom atom = (Atom) atomSupertype;  // no error
}

de esa manera se lee mucho más claro, además se compila y se ejecuta :) Creo que esto sería " una forma elegante de resolver el problema "

El problema podría ser que estás intentando transmitir IAtom<?, ?> a Atom (que es un Atom<Atom, Space>).¿Cómo diablos se supone que el sistema sabe eso? ? ¿Podría ser Átomo y Espacio o no?

Cuando no sabes qué tipos colocar donde se surten los genéricos, normalmente simplemente omites todo, como en

ISpace spaceSupertype = new Space();

Eso genera una advertencia del compilador (no un error), pero su código aún se ejecutará (aunque si el tipo real no es compatible con la conversión, obtendrá un error de tiempo de ejecución).

Sin embargo, no tiene sentido mirar todo esto.Dices que necesitas una escritura fuerte y luego te quedas ? donde van los tipos.Luego te das la vuelta e intentas lanzarlos.

Si necesita poder transmitirlos a Space y Atom, probablemente debería usarlos para empezar.Si no puede porque eventualmente va a incluir otros tipos en esas variables, su código se romperá cuando cambie el tipo de tiempo de ejecución de todos modos, a menos que use un montón de declaraciones si/entonces (como en el final de este comentario).

Realmente, sin embargo, si estás haciendo cosas tan extrañas, creo que estamos ante un diseño de código deficiente.Reconsidera cómo estás estructurando esto.Quizás necesites otras clases o interfaces para cumplir con la funcionalidad que tienes aquí.

Pregúntese: "¿Realmente necesito a los tipos fuertes?¿Qué me aporta?" Es mejor no agregar campos ya que estás usando interfaces.(Tenga en cuenta que si solo se accede a los campos a través de métodos, entonces solo está agregando métodos a la interfaz pública). Si agrega métodos, entonces use las interfaces únicas IAtom y ISpace Esta es una mala idea porque solo podrás usar test() con ese subtipo de todos modos. test() no se generalizará a otras implementaciones de ISpace/IAtom.Si todas las implementaciones que colocará aquí tienen los mismos métodos que necesita para test() pero no todas las implementaciones de IAtom/ISpace tenerlos, necesitas una subinterfaz intermedia:

public interface IAtom2 extends IAtom
{
    [additional methods]
}

Entonces puedes usar IAtom2 en lugar de IAtom en test().Entonces automáticamente obtendrás la escritura que necesitas y no necesitarás los genéricos.Recuerde, si un conjunto de clases tiene una interfaz pública común (conjunto de métodos y campos), esa interfaz pública es una buena candidata para tener un supertipo o interfaz.Lo que estoy describiendo es algo así como la relación paralelogramo, rectángulo, cuadrado, donde intentas omitir la parte del rectángulo.

Si no va a rediseñar, la otra idea es descartar los genéricos cíclicos y simplemente realizar pruebas de instancia a través de instanceof:

if (spaceSupertype instanceof Space)
{
    Space space = (Space)spaceSupertype;
    ...
}
else
...
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top