Genéricos limitantes com palavra-chave ‘super’

Por que posso usar super somente com curingas e não com parâmetros de tipo?

Por exemplo, na interface Collection , por que o método toArray não é escrito assim?

 interface Collection{  S[] toArray(S[] a); } 

    super para ligar um parâmetro de tipo nomeado (por exemplo, ) ao contrário de um curinga (por exemplo, < ? super T> ) é ILEGAL simplesmente porque mesmo que seja permitido, não faria o que você esperava , porque desde que Object é o super final de todos os tipos de referência, e tudo é um Object , na verdade não há limite .

    No seu exemplo específico, uma vez que qualquer matriz de tipo de referência é um Object[] (por covariância de array Java), pode ser usado como um argumento para S[] toArray(S[] a) (se bound é legal) em tempo de compilation, e isso não impediria o ArrayStoreException em tempo de execução.

    O que você está tentando propor é o seguinte:

     List integerList; 

    e dado este super limite hipotético em toArray :

      S[] toArray(S[] a) // hypothetical! currently illegal in Java 

    o compilador deve permitir apenas o seguinte para compilar:

     integerList.toArray(new Integer[0]) // works fine! integerList.toArray(new Number[0]) // works fine! integerList.toArray(new Object[0]) // works fine! 

    e não há outros argumentos de tipo de matriz (já que Integer só possui esses 3 tipos como super ). Ou seja, você está tentando impedir isso de compilar:

     integerList.toArray(new String[0]) // trying to prevent this from compiling 

    porque, pelo seu argumento, String não é um super de Integer . Entretanto , Object é um super de Integer , e um String[] é um Object[] , então o compilador ainda deixaria o compilado acima, mesmo que hipoteticamente você possa fazer !

    Portanto, o seguinte ainda compilaria (da mesma maneira que são agora), e o ArrayStoreException em tempo de execução não poderia ser impedido por nenhuma verificação em tempo de compilation usando limites genéricos de tipo:

     integerList.toArray(new String[0]) // compiles fine! // throws ArrayStoreException at run-time 

    Os genéricos e os arrays não se misturam, e este é um dos muitos lugares onde isso é mostrado.


    Um exemplo sem matriz

    Novamente, digamos que você tenha esta declaração de método genérico:

      void add(T number) // hypothetical! currently illegal in Java 

    E você tem estas declarações de variables:

     Integer anInteger Number aNumber Object anObject String aString 

    Sua intenção com (se for legal) é que ele deve permitir add(anInteger) , e add(aNumber) , e, claro, add(anObject) , mas NÃO add(aString) . Bem, String é um Object , então add(aString) ainda compilaria de qualquer maneira.


    Veja também

    • Tutoriais Java / Genéricos
      • Subtipagem
      • Mais diversão com curingas

    Perguntas relacionadas

    Em regras de digitação de genéricos:

    • Qualquer maneira simples de explicar porque eu não posso fazer List animals = new ArrayList() ?
    • genéricos java (não) covariância
    • O que é um tipo cru e por que não devemos usá-lo?
      • Explica como tipo bruto List é diferente de List que é diferente de uma List< ?>

    Ao usar super e extends :

    • Java Generics: What is PECS?
      • De Efetivo Java 2nd Edition : “produtor extends consumidor super
    • Qual é a diferença entre super e extends em Java genéricos
    • Qual é a diferença entre e ?
    • Como posso adicionar à List< ? extends Number> List< ? extends Number> estruturas de dados? (VOCÊ NÃO PODE!)

    Como ninguém forneceu uma resposta satisfatória, a resposta correta parece ser “Por causa da deficiência da linguagem Java”.

    Os poliglicolubrificantes forneceram uma boa visão geral das coisas ruins que acontecem com a covariância do array java, que é uma característica terrível por si só. Considere o seguinte fragment de código:

     String[] strings = new String[1]; Object[] objects = strings; objects[0] = 0; 

    Este código obviamente errado compila sem recorrer a qualquer construção “super”, portanto a covariância de array não deve ser usada como argumento.

    Agora, aqui eu tenho um exemplo perfeitamente válido de código que requer super no parâmetro de tipo nomeado:

     class Nullable { private A value; // Does not compile!! public  B withDefault(B defaultValue) { return value == null ? defaultValue : value; } } 

    Potencialmente suportando algum uso legal:

     Nullable intOrNull = ...; Integer i = intOrNull.withDefault(8); Number n = intOrNull.withDefault(3.5); Object o = intOrNull.withDefault("What's so bad about a String here?"); 

    O último fragment de código não compila se eu remover o B completamente, então B é realmente necessário.

    Observe que o recurso que estou tentando implementar é facilmente obtido se eu inverter a ordem das declarações de parâmetro de tipo, alterando assim a super restrição para extends . No entanto, isso só é possível se eu rewrite o método como estático:

     // This one actually works and I use it. public static  B withDefault(Nullable nullable, B defaultValue) { ... } 

    O ponto é que essa restrição de linguagem Java está de fato restringindo alguns resources úteis de outra forma possíveis e pode requerer soluções alternativas feias. Eu me pergunto o que aconteceria se precisássemos que o withDefault fosse virtual.

    Agora, para correlacionar com o que os poligenelubrificantes disseram, usamos B aqui para não restringir o tipo de object passado como defaultValue (veja a String usada no exemplo), mas sim para restringir as expectativas do chamador sobre o object que retornamos. Como uma regra simples, você usa extends com os tipos que você procura e super com os tipos que você fornece.

    A resposta “oficial” à sua pergunta pode ser encontrada em um relatório de bug da Sun / Oracle .

    BT2: AVALIAÇÃO

    Vejo

    http://lampwww.epfl.ch/~odersky/ftp/local-ti.ps

    particularmente a seção 3 e o último parágrafo da página 9. A admissão de variables ​​de tipo em ambos os lados das restrições de subtipo pode resultar em um conjunto de equações de tipo sem uma única solução melhor; conseqüentemente, a inferência de tipos não pode ser feita usando qualquer um dos algoritmos padrão existentes. É por isso que as variables ​​de tipo têm apenas limites “estendidos”.

    Os curingas, por outro lado, não precisam ser inferidos, portanto, não há necessidade dessa restrição.

    @ ###. ### 2004-05-25

    Sim; o ponto-chave é que os curingas, mesmo quando capturados, são usados ​​apenas como inputs do processo de inferência; nada com (apenas) um limite inferior precisa ser inferido como resultado.

    @ ###. ### 2004-05-26

    Eu vejo o problema. Mas eu não vejo como é diferente dos problemas que temos com limites inferiores em curingas durante a inferência, por exemplo:

    Listar < ? super número> s;
    booleana b;

    s = b? s: s;

    Atualmente, inferimos List onde X estende o object como o tipo da expressão condicional, o que significa que a atribuição é ilegal.

    @ ###. ### 2004-05-26

    Infelizmente, a conversa termina aí. O papel para o qual o link (agora morto) usado para apontar é Inertred Type Instantiation for GJ . Olhando de relance para a última página, resume-se a: Se os limites inferiores são admitidos, a inferência de tipos pode produzir múltiplas soluções, nenhuma das quais é principal .

    Suponha que tenhamos:

    • classs básicas A> B> C e D

       class A{ void methodA(){} }; class B extends A{ void methodB(){} } class C extends B{ void methodC(){} } class D { void methodD(){} } 
    • classs de wrapper de trabalho

       interface Job { void exec(T t); } class JobOnA implements Job{ @Override public void exec(A a) { a.methodA(); } } class JobOnB implements Job{ @Override public void exec(B b) { b.methodB(); } } class JobOnC implements Job{ @Override public void exec(C c) { c.methodC(); } } class JobOnD implements Job{ @Override public void exec(D d) { d.methodD(); } } 
    • e uma class de gerenciador com 4 abordagens diferentes para executar o trabalho no object

       class Manager{ final T t; Manager(T t){ this.t=t; } public void execute1(Job job){ job.exec(t); } public  void execute2(Job job){ U u= (U) t; //not safe job.exec(u); } public  void execute3(Job job){ U u= (U) t; //not safe job.exec(u); } //desired feature, not compiled for now public  void execute4(Job job){ U u= (U) t; //safe job.exec(u); } } 
    • com uso

       void usage(){ B b = new B(); Manager managerB = new Manager<>(b); //TOO STRICT managerB.execute1(new JobOnA()); managerB.execute1(new JobOnB()); //compiled managerB.execute1(new JobOnC()); managerB.execute1(new JobOnD()); //TOO MUCH FREEDOM managerB.execute2(new JobOnA()); //compiled managerB.execute2(new JobOnB()); //compiled managerB.execute2(new JobOnC()); //compiled !! managerB.execute2(new JobOnD()); //compiled !! //NOT ADEQUATE RESTRICTIONS managerB.execute3(new JobOnA()); managerB.execute3(new JobOnB()); //compiled managerB.execute3(new JobOnC()); //compiled !! managerB.execute3(new JobOnD()); //SHOULD BE managerB.execute4(new JobOnA()); //compiled managerB.execute4(new JobOnB()); //compiled managerB.execute4(new JobOnC()); managerB.execute4(new JobOnD()); } 

    Alguma sugestão de como implementar o execute4 agora?

    ========== editado =======

      public void execute4(Job< ? super T> job){ job.exec( t); } 

    Obrigado a todos 🙂

    ========== editado ==========

      private  void execute2(Job job){ U u= (U) t; //now it's safe job.exec(u); } public void execute4(Job< ? super T> job){ execute2(job); } 

    muito melhor, qualquer código com U dentro de execute2

    o super tipo U é nomeado!

    discussão interessante 🙂