Pergunta

I encontrar um comportamento totalmente estranho do compilador Java.
Eu não posso lançar um supertipo para um subtipo quando tipo genérico cíclica relação está envolvido.

caso de teste JUnit para reproduzir o 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
    }
}

saída de erro do 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:. Eu estou usando o NetBeans mais recente tronco, empacotado Ant, última versão Java 6
Eu tentei usar Ant linha de comando (Netbeans gera um arquivo build.xml) mas resulta em mesmos erros.

O que está errado?
Existe uma maneira elegante resolver o problema?

O estranho é: O Netbeans não marcar erros (nem mesmo advertências) em determinado código.

EDIT:
Não, agora eu entendo nada !
O Eclipse 3.4.1 não marcar nem avisos nem erros, e compila o código sem problemas !!!
Como isso pode ser? Eu pensei, usando Ant na linha de comando juntamente com build.xml fornecido pelo Netbeans' seria neutro.
Am I faltando alguma coisa?

EDIT 2:
Usando JDK7 biblioteca e formato de código JDK7, o NetBeans compila sem erros / advertências!
(estou usando o 1.7.0-ea-B55)

EDIT 3:
Changed título para indicar que estamos lidando com um bug javac.

Foi útil?

Solução

Eu não tenho a pretensão de entender facilmente esses tipos genéricos complexos, mas se você encontrar algum código que compila em javac e não em ecj (o compilador eclipse), em seguida, enviar um relatório de bug com os dois Sun e Eclipse e descrever as situações Cleary (melhor se você também mencionar que você entrou com ambos os relatórios de bugs e mencionar suas respectivas URLs, embora para Sun pode demorar algum tempo antes que o bug é acessível ao público).

Eu já fiz isso no passado e fiquei realmente boas respostas onde

  1. uma das equipes descobriram que a abordagem correta foi (dar compilação de erro, aviso ou nada)
  2. e o compilador com defeito foi corrigido

Uma vez que ambos os compiladores implementar as mesmas características, uma delas é por errada definição, mesmo que apenas um deles compila o código.

Para o registro:

Eu tentei compilar o código de exemplo com javac (javac 1.6.0_13) e ecj (Eclipse Java Compiler 0.894_R34x, 3.4.2 release) e javac reclamou em voz alta e não conseguiu produzir quaisquer arquivos .class, enquanto ecj única reclamou sobre alguns variáveis ??não utilizados (avisos) e produzidos todos os arquivos .class esperados.

Outras dicas

Eu acabei usando não-genéricos para isso:

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

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

o que o impede de usar os tipos, sem curingas?

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
}

dessa forma ele lê muito mais clara, mais ele compila e executa :) eu acho que isso seria "uma maneira elegante resolver o problema"

O problema pode ser que você está tentando IAtom<?, ?> elenco para Atom (que é um Atom<Atom, Space>). Como o diabo é o sistema deveria saber que ? pode ser Atom and Space ou não?

Quando você não conhece os tipos de vara em que os genéricos são preenchidos, você geralmente apenas deixar de fora a coisa toda, como em

ISpace spaceSupertype = new Space();

Isso gera um aviso do compilador (não erro), mas o seu código ainda irá executar (embora se o tipo real não é lançado compatível, você poderá obter um erro de execução).

Essa coisa toda não faz sentido olhar para, no entanto. Você diz que precisa tipagem forte, então você ficar ? onde os tipos ir. Então você volta e tentar lançá-los.

Se você precisa ser capaz de lançá-los ao espaço e Atom, você provavelmente deve apenas usar aqueles para começar. Se você não pode porque você está indo furar outros tipos nessas variáveis, eventualmente, seu código vai quebrar como todos Parreira quando você alterar o tipo de tempo de execução de qualquer maneira, a menos que você use um monte de if / declarações depois (como no acabar deste comentário).

Realmente, porém, se você está fazendo coisas esta estranha, eu acho que nós estamos olhando para projeto do código pobres. Repensar como você está estruturação isso. Talvez você precisa de outras classes ou interfaces para cumprir a funcionalidade que você tem aqui.

Pergunte a si mesmo: "Eu realmente preciso os tipos fortes? O que ele ganha de mim?" É melhor não adicionar campos desde que você está usando interfaces. (Tenha em mente que se os campos são acessados ??somente através de métodos, então você está acrescentando apenas métodos para a interface pública). Se ele adiciona métodos, em seguida, usando as interfaces únicas IAtom e ISpace aqui é uma má idéia, porque você só vai ser capaz de usar test() com isso de qualquer maneira subtipo. test() não vai generalizar a outras implementações de ISpace / IAtom. Se todas as implementações você estará colocando aqui têm os mesmos métodos que você precisa para test() mas não todas as implementações de IAtom / ISpace tê-los, você precisa de uma subinterface intermediário:

public interface IAtom2 extends IAtom
{
    [additional methods]
}

Então você pode usar IAtom2 vez de IAtom em test(). Então você tem automaticamente a digitação que você precisa e não precisa dos genéricos. Lembre-se, se um conjunto de classes de todos têm uma interface pública comum (conjunto de métodos e campos), que a interface pública é um bom candidato para um supertipo ou interface de ter. O que eu estou descrevendo é algo como o paralelogramo, retângulo, relação praça, onde você está tentando pular a parte retângulo.

Se você não está indo para redesenhar, a outra idéia é que você soltar os genéricos cíclicos e apenas fazer testes instância via instanceof:

if (spaceSupertype instanceof Space)
{
    Space space = (Space)spaceSupertype;
    ...
}
else
...
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top