Uma explicação muito boa com um exemplo @ youtube.com/watch?v=34oiEq9nD0M&feature=youtu.be&t=1630 que explica superparte, mas dá uma idéia de outra.
Lupchiazoem
Respostas:
844
tl; dr: "PECS" é do ponto de vista da coleção. Se você está apenas puxando itens de uma coleção genérica, é um produtor e você deve usá-lo extends; se você estiver enchendo apenas itens, ele é um consumidor e você deve usá-lo super. Se você fizer ambos com a mesma coleção, você não deve usar extendsou super.
Suponha que você tenha um método que tome como parâmetro uma coleção de coisas, mas deseja que seja mais flexível do que apenas aceitar a Collection<Thing>.
Caso 1: você deseja percorrer a coleção e fazer as coisas com cada item.
Então a lista é um produtor , então você deve usar a Collection<? extends Thing>.
O raciocínio é que a Collection<? extends Thing>poderia conter qualquer subtipo de Thinge, portanto, cada elemento se comportará como Thingquando você executa sua operação. (Na verdade, você não pode adicionar nada a Collection<? extends Thing>, porque não pode saber em tempo de execução qual subtipo específico da Thingcoleção contém.)
Caso 2: você deseja adicionar itens à coleção.
Então a lista é um consumidor , então você deve usar a Collection<? super Thing>.
O raciocínio aqui é que Collection<? extends Thing>, diferentemente , Collection<? super Thing>sempre pode conter, Thingnão importa qual seja o tipo parametrizado real. Aqui você não se importa com o que já está na lista, desde que permita que um Thingseja adicionado; é isso que ? super Thinggarante.
Estou sempre tentando pensar dessa maneira: um produtor pode produzir algo mais específico e, portanto , se estende , um consumidor pode aceitar algo mais geral e, portanto, super .
Feuermurmel
10
Outra maneira de lembrar a distinção produtor / consumidor é pensar em uma assinatura de método. Se você possui um método doSomethingWithList(List list), está consumindo a lista e, portanto, precisará de covariância / extensão (ou uma Lista invariável). Por outro lado, se o seu método for List doSomethingProvidingList, você estará produzindo a Lista e precisará de contravariância / super (ou uma Lista invariável).
Raman
3
@MichaelMyers: Por que não podemos simplesmente usar um tipo parametrizado para esses dois casos? Existe alguma vantagem específica no uso de curingas aqui, ou é apenas um meio de melhorar a legibilidade semelhante a, digamos, usar referências a constcomo parâmetros de método em C ++ para significar que o método não modifica os argumentos?
Chatterjee
7
@Raman, acho que você acabou de confundir. No doSthWithList (você pode ter a List <? Super Thing>), já que você é consumidor, pode usar o super (lembre-se, CS). No entanto, é a lista <? estende Thing> getList (), pois você pode retornar algo mais específico ao produzir (PE).
Masterxilo 27/05
4
@AZ_ Partilho o seu sentimento. Se um método obtiver () da lista, o método será considerado um Consumidor <T> e a lista será considerada um provedor; mas a regra do PECS é "do ponto de vista da lista", portanto é necessário "estender". Deve ser GEPS: get extends; colocar super.
Treefish Zhang
561
Os princípios por trás disso na ciência da computação são chamados
Covariance: ? extends MyClass,
Contravariância: ? super MyClasse
Invariância / não variação: MyClass
A figura abaixo deve explicar o conceito. Imagem cedida por: Andrey Tyukin
Ei pessoal. Sou Andrey Tyukin, só queria confirmar que a anoopelias e a DaoWen entraram em contato comigo e obtiveram minha permissão para usar o esboço. Ele está licenciado sob (CC) -BY-SA. Thx @ Anoop por dar uma segunda vida ^^ @Brian Agnew: (em "poucos votos"): Isso é porque é um esboço para o Scala, ele usa a sintaxe do Scala e assume a variação do site de declaração, que é bem diferente da chamada estranha de Java variância -site ... Talvez eu devesse escrever uma resposta mais detalhada que mostra claramente como este esboço se aplica Java ...
Andrey Tyukin
3
Esta é uma das explicações mais simples e claras para covariância e contravariância que eu já encontrei!
Cs4r #
@Andrey Tyukin Oi, eu também quero usar esta imagem. Como posso entrar em contato com você?
Use um curinga estendido quando você obtém apenas valores de uma estrutura.
Use um super curinga quando você coloca apenas valores em uma estrutura.
E não use um curinga quando você recebe e coloca.
Exemplo em Java:
classSuper{Object testCoVariance(){returnnull;}//Covariance of return types in the subtype.void testContraVariance(Object parameter){}// Contravariance of method arguments in the subtype.}classSubextendsSuper{@OverrideString testCoVariance(){returnnull;}//compiles successfully i.e. return type is don't care(String is subtype of Object) @Overridevoid testContraVariance(String parameter){}//doesn't support even though String is subtype of Object}
Tipos de dados somente leitura (fontes) podem ser covariantes ;
tipos de dados somente para gravação (coletores) podem ser contrários .
Tipos de dados mutáveis que atuam como fontes e sumidouros devem ser invariáveis .
Para ilustrar esse fenômeno geral, considere o tipo de matriz. Para o tipo Animal, podemos criar o tipo Animal []
covariante : um gato [] é um animal [];
contravariante : um animal [] é um gato [];
invariante : um Animal [] não é um Gato [] e um Gato [] não é um Animal [].
Exemplos Java:
Object name=newString("prem");//worksList<Number> numbers =newArrayList<Integer>();//gets compile time errorInteger[] myInts ={1,2,3,4};Number[] myNumber = myInts;
myNumber[0]=3.14;//attempt of heap pollution i.e. at runtime gets java.lang.ArrayStoreException: java.lang.Double(we can fool compiler but not run-time)List<String> list=newArrayList<>();
list.add("prem");List<Object> listObject=list;//Type mismatch: cannot convert from List<String> to List<Object> at Compiletime
curinga limitado (ou seja, indo para algum lugar) : Existem 3 tipos diferentes de curingas:
Variação / não variação: ?ou ? extends Object- Curinga não vinculado . Representa a família de todos os tipos. Use quando você pegar e colocar.
Co-variância: ? extends T(a família de todos os tipos que são subtipos de T) - um curinga com um limite superior . Té a classe mais alta na hierarquia de herança. Use um extendscuringa quando você apenas obtém valores de uma estrutura.
Contra-variância: ? super T(a família de todos os tipos que são supertipos de T) - um curinga com um limite inferior . Té a classe mais baixa na hierarquia de herança. Use um supercuringa quando você apenas coloca valores em uma estrutura.
Nota: curinga ?significa zero ou uma vez , representa um tipo desconhecido. O curinga pode ser usado como o tipo de um parâmetro, nunca usado como um argumento de tipo para uma chamada de método genérico, a criação da instância classe genérica. (Ou seja, quando curinga usado essa referência não utilizados em outras partes programa como usamos T)
classShape{void draw(){}}classCircleextendsShape{void draw(){}}classSquareextendsShape{void draw(){}}classRectangleextendsShape{void draw(){}}publicclassTest{/*
* Example for an upper bound wildcard (Get values i.e Producer `extends`)
*
* */publicvoid testCoVariance(List<?extendsShape> list){
list.add(newShape());// Error: is not applicable for the arguments (Shape) i.e. inheritance is not supporting
list.add(newCircle());// Error: is not applicable for the arguments (Circle) i.e. inheritance is not supporting
list.add(newSquare());// Error: is not applicable for the arguments (Square) i.e. inheritance is not supporting
list.add(newRectangle());// Error: is not applicable for the arguments (Rectangle) i.e. inheritance is not supportingShape shape= list.get(0);//compiles so list act as produces only/*You can't add a Shape,Circle,Square,Rectangle to a List<? extends Shape>
* You can get an object and know that it will be an Shape
*/}/*
* Example for a lower bound wildcard (Put values i.e Consumer`super`)
* */publicvoid testContraVariance(List<?superShape> list){
list.add(newShape());//compiles i.e. inheritance is supporting
list.add(newCircle());//compiles i.e. inheritance is supporting
list.add(newSquare());//compiles i.e. inheritance is supporting
list.add(newRectangle());//compiles i.e. inheritance is supportingShape shape= list.get(0);// Error: Type mismatch, so list acts only as consumerObject object= list.get(0);// gets an object, but we don't know what kind of Object it is./*You can add a Shape,Circle,Square,Rectangle to a List<? super Shape>
* You can't get an Shape(but can get Object) and don't know what kind of Shape it is.
*/}}
Ei, eu só queria saber o que você quis dizer com a última frase: "Se você acha que minha analogia está errada, atualize". Você quer dizer se é eticamente errado (o que é subjetivo) ou se está errado no contexto da programação (o que é objetivo: não, não está errado)? Eu gostaria de substituí-lo por um exemplo mais neutro, universalmente aceitável, independente de normas culturais e crenças éticas; Se estiver tudo bem com você.
Neuron
finalmente consegui entender. Boa explicação.
Oleg Kuts
2
@Premraj,, In-variance/Non-variance: ? or ? extends Object - Unbounded Wildcard. It stands for the family of all types. Use when you both get and put.não consigo adicionar elemento à Lista <?> Ou Lista <? estende Object>, então não entendo por que pode ser Use when you both get and put.
LiuWenbin_NO.
1
@LiuWenbin_NO. - Essa parte da resposta é enganosa. ?- o "curinga ilimitado" - corresponde ao exato oposto da invariância. Consulte a seguinte documentação: docs.oracle.com/javase/tutorial/java/generics/…, que declara: No caso em que o código precisa acessar a variável como variável "in" e "out", faça Não use um curinga. (Eles estão usando "in" e "out" como sinônimo de "get" e "put"). Com exceção de nullvocê não pode adicionar a uma coleção parametrizada com ?.
mouselabs 16/04
29
publicclassTest{publicclass A {}publicclass B extends A {}publicclass C extends B {}publicvoid testCoVariance(List<?extends B> myBlist){
B b =new B();
C c =new C();
myBlist.add(b);// does not compile
myBlist.add(c);// does not compile
A a = myBlist.get(0);}publicvoid testContraVariance(List<?super B> myBlist){
B b =new B();
C c =new C();
myBlist.add(b);
myBlist.add(c);
A a = myBlist.get(0);// does not compile}}
Portanto, "? Estende B" deve ser interpretado como "? Estende B". É algo que B se estende para incluir todas as super classes de B até Objeto, excluindo B em si. Obrigado pelo código!
Saurabh Patil
3
@SaurabhPatil n, ? extends Bmeio B e qualquer coisa que se estende B.
ASGs
24
Como eu explico em minha resposta a outra pergunta, PECS é um dispositivo mnemônico criado por Josh Bloch para ajudar a lembrar P roducer extends, C onsumer super.
Isso significa que, quando um tipo parametrizado sendo passado para um método produzirá instâncias de T(elas serão recuperadas de alguma forma), ? extends Tdeve ser usado, pois qualquer instância de uma subclasse de Ttambém é a T.
Quando um tipo parametrizado sendo passado para um método consumirá instâncias de T(elas serão passadas a ele para fazer alguma coisa), ? super Tdeve ser usado porque uma instância de Tpode legalmente ser passada para qualquer método que aceite algum supertipo de T. A Comparator<Number>poderia ser usado em um Collection<Integer>, por exemplo. ? extends Tnão funcionaria, porque a Comparator<Integer>não poderia operar em a Collection<Number>.
Observe que geralmente você só deve usar ? extends Te ? super Tpara os parâmetros de algum método. Os métodos devem usar apenas Tcomo o parâmetro type em um tipo de retorno genérico.
Esse princípio vale apenas para coleções? Faz sentido quando se tenta correlacioná-lo com uma lista. Se você pensar na assinatura de classificação (Lista <T>, Comparador <? Super T>) ---> aqui o Comparador usa super, portanto significa que é um consumidor no contexto do PECS. Quando você olha para a implementação, por exemplo, como: public int compare (Pessoa a, Pessoa b) {return a.age <b.age? -1: a.age == b.age? 0: 1; } Sinto que Person não consome nada, mas produz idade. Isso me deixa confuso. Existe uma falha no meu raciocínio ou o PECS é válido apenas para cobranças?
Fatih Arslan
24
Em poucas palavras, três regras fáceis de lembrar do PECS:
Use o <? extends T>curinga se precisar recuperar o objeto do tipo Tde uma coleção.
Use o <? super T>curinga se precisar colocar objetos do tipo Tem uma coleção.
Se você precisar satisfazer as duas coisas, bem, não use nenhum curinga. Tão simples como isso.
classCreature{}// XclassAnimalextendsCreature{}// YclassFishextendsAnimal{}// ZclassSharkextendsFish{}// AclassHammerSkarkextendsShark{}// BclassDeadHammerSharkextendsHammerSkark{}// C
Vamos esclarecer o PE - Producer Extends:
List<?extendsShark> sharks =newArrayList<>();
Por que você não pode adicionar objetos que estendem "Shark" nesta lista? gostar:
sharks.add(newHammerShark());//will result in compilation error
Como você possui uma lista que pode ser do tipo A, B ou C em tempo de execução , não é possível adicionar nenhum objeto do tipo A, B ou C, pois pode acabar com uma combinação que não é permitida no java. Na prática, o compilador pode realmente ver na hora da compilação que você adiciona um B:
sharks.add(newHammerShark());
... mas não há como saber se, em tempo de execução, seu B será um subtipo ou supertipo do tipo de lista. No tempo de execução, o tipo de lista pode ser qualquer um dos tipos A, B, C. Portanto, você não pode adicionar o HammerSkark (supertipo) em uma lista do DeadHammerShark, por exemplo.
* Você dirá: "OK, mas por que não consigo adicionar o HammerSkark, pois esse é o menor?". Resposta: É o menor que você conhece. Mas o HammerSkark também pode ser estendido por outra pessoa e você acaba no mesmo cenário.
Você pode adicionar os tipos de objetos acima, porque qualquer coisa abaixo do tubarão (A, B, C) sempre será subtipos de qualquer coisa acima do tubarão (X, Y, Z). Fácil de entender.
Você não pode adicionar tipos acima do Shark, porque em tempo de execução, o tipo de objeto adicionado pode ser mais alto na hierarquia que o tipo declarado da lista (X, Y, Z). Isso não é permitido.
Mas por que você não pode ler desta lista? (Quero dizer, você pode obter um elemento dele, mas não pode atribuí-lo a nada além de Objeto o):
Object o;
o = sharks.get(2);// only assignment that worksAnimal s;
s = sharks.get(2);//doen't work
Em tempo de execução, o tipo de lista pode ser qualquer tipo acima de A: X, Y, Z, ... O compilador pode compilar sua declaração de atribuição (o que parece correto), mas, em tempo de execução, o tipo de s (Animal) pode ser menor em hierarquia que o tipo declarado da lista (que pode ser Criatura ou superior). Isso não é permitido.
Resumindo
Usamos <? super T>para adicionar objetos de tipos iguais ou inferiores Tao List. Não podemos ler sobre isso. Usamos <? extends T>para ler objetos de tipos iguais ou abaixo Tda lista. Não podemos adicionar elemento a ele.
(adicionando uma resposta porque nunca há exemplos suficientes com curingas genéricos)
// Source List<Integer> intList =Arrays.asList(1,2,3);List<Double> doubleList =Arrays.asList(2.78,3.14);List<Number> numList =Arrays.asList(1,2,2.78,3.14,5);// DestinationList<Integer> intList2 =newArrayList<>();List<Double> doublesList2 =newArrayList<>();List<Number> numList2 =newArrayList<>();// Works
copyElements1(intList,intList2);// from int to int
copyElements1(doubleList,doublesList2);// from double to doublestatic<T>void copyElements1(Collection<T> src,Collection<T> dest){for(T n : src){
dest.add(n);}}// Let's try to copy intList to its supertype
copyElements1(intList,numList2);// error, method signature just says "T"// and here the compiler is given // two types: Integer and Number, // so which one shall it be?// PECS to the rescue!
copyElements2(intList,numList2);// possible// copy Integer (? extends T) to its supertype (Number is super of Integer)privatestatic<T>void copyElements2(Collection<?extends T> src,Collection<?super T> dest){for(T n : src){
dest.add(n);}}
Esta é a maneira mais clara e simples de pensar em extensão versus super:
extendsé para leitura
superé para escrever
Eu acho que o "PECS" é uma maneira não óbvia de pensar sobre quem é o "produtor" e quem é o "consumidor". "PECS" é definido a partir da perspectiva da própria coleta de dados - a coleção "consome" se os objetos estão sendo escritas para ele (é consumir objetos de código de chamada), e "produz" se os objetos estão sendo lidos a partir dele (ele está produzindo objetos para algum código de chamada). Isso é contrário a como todo o resto é nomeado. As APIs Java padrão são nomeadas da perspectiva do código de chamada, não da coleção em si. Por exemplo, uma visão centrada na coleção de java.util.List deve ter um método chamado "receive ()" em vez de "add ()" - afinal,o elemento, mas a própria lista recebe o elemento.
Eu acho que é mais intuitivo, natural e consistente pensar nas coisas da perspectiva do código que interage com a coleção - o código "lê de" ou "escreve para" a coleção? Depois disso, qualquer código gravado na coleção seria o "produtor" e qualquer código lido na coleção seria o "consumidor".
Encontrei a mesma colisão mental e tenderia a concordar, exceto que o PECS não especifica a nomeação do código e os limites de tipo são definidos nas declarações de coleção. Além disso, no que diz respeito à nomeação, muitas vezes você tem nomes para produzir / consumir coleções como srce dst. Então, você está lidando com código e contêineres ao mesmo tempo e acabei pensando sobre isso nesse sentido - "consumir código" consome de um contêiner de produção e "produzir código" produz para um contêiner de consumo.
mouselabs 17/04
4
A "regra" do PECS apenas garante que o seguinte seja legal:
Consumidor: seja o que ?for, pode se referir legalmente aT
Produtor: seja o que ?for, pode ser legalmente referido porT
O emparelhamento típico ao longo das linhas de List<? extends T> producer, List<? super T> consumeré simplesmente garantir que o compilador possa impor as regras padrão de relacionamento de herança "IS-A". Se pudéssemos fazer isso legalmente, seria mais simples dizer <T extends ?>, <? extends T>(ou melhor ainda, em Scala, como você pode ver acima) [-T], [+T]. Infelizmente, o melhor que podemos fazer é <? super T>, <? extends T>.
Quando o encontrei pela primeira vez e quebrei na cabeça, a mecânica fazia sentido, mas o código em si continuava confuso para mim - fiquei pensando "parece que os limites não precisam ser invertidos assim" - mesmo que eu ficou claro o que foi dito acima - que se trata simplesmente de garantir a conformidade com as regras padrão de referência.
O que me ajudou foi vê-lo usando a atribuição comum como analogia.
Considere o seguinte código de brinquedo (não pronto para produção):
// copies the elements of 'producer' into 'consumer'static<T>void copy(List<?extends T> producer,List<?super T> consumer){for(T t : producer)
consumer.add(t);}
Ilustrando isso em termos da analogia atribuição, para consumero ?wildcard (tipo desconhecido) é a referência - o "lado esquerdo" do trabalho - e <? super T>garante que tudo o que ?é, T"é-A" ?- que Tpode ser atribuído a ele, porque ?é um super tipo (ou, no máximo, o mesmo tipo) de T.
Para producera preocupação é o mesmo que seja apenas invertido: producer's ?curinga (tipo desconhecido) é o referente - o 'lado direito' do trabalho - e <? extends T>garante que tudo o que ?é, ?'é-A' T- que ele pode ser atribuído a umT , porque ?é um subtipo (ou pelo menos o mesmo tipo) de T.
publicclass A {}//B is Apublicclass B extends A {}//C is Apublicclass C extends A {}
Genéricos permite que você trabalhe com tipos dinamicamente de uma maneira segura
//ListAList<A> listA =newArrayList<A>();//add
listA.add(new A());
listA.add(new B());
listA.add(new C());//get
A a0 = listA.get(0);
A a1 = listA.get(1);
A a2 = listA.get(2);
//ListBList<B> listB =newArrayList<B>();//add
listB.add(new B());//get
B b0 = listB.get(0);
Problema
Como o Java's Collection é um tipo de referência, temos os próximos problemas:
Problema # 1
//not compiled//danger of **adding** non-B objects using listA reference
listA = listB;
* O genérico do Swift não tem esse problema porque Collection é Value type[About], portanto, uma nova coleção é criada
Problema # 2
//not compiled//danger of **getting** non-B objects using listB reference
listB = listA;
A solução - Curingas genéricos
Curinga é um recurso do tipo de referência e não pode ser instanciado diretamente
A solução 1,<? super A> conhecida como contravariância de limite inferior, ou consumidores, garante que é operada por A e por todas as superclasses, por isso é seguro adicionar
<? extends A>aka limite superior aka covariância aka produtores garante que é operado por A e por todas as subclasses, é por isso que é seguro obter e transmitir
List<?extends A> listExtendsA;
listExtendsA = listA;
listExtendsA = listB;//get
A a0 = listExtendsA.get(0);
super
parte, mas dá uma idéia de outra.Respostas:
tl; dr: "PECS" é do ponto de vista da coleção. Se você está apenas puxando itens de uma coleção genérica, é um produtor e você deve usá-lo
extends
; se você estiver enchendo apenas itens, ele é um consumidor e você deve usá-losuper
. Se você fizer ambos com a mesma coleção, você não deve usarextends
ousuper
.Suponha que você tenha um método que tome como parâmetro uma coleção de coisas, mas deseja que seja mais flexível do que apenas aceitar a
Collection<Thing>
.Caso 1: você deseja percorrer a coleção e fazer as coisas com cada item.
Então a lista é um produtor , então você deve usar a
Collection<? extends Thing>
.O raciocínio é que a
Collection<? extends Thing>
poderia conter qualquer subtipo deThing
e, portanto, cada elemento se comportará comoThing
quando você executa sua operação. (Na verdade, você não pode adicionar nada aCollection<? extends Thing>
, porque não pode saber em tempo de execução qual subtipo específico daThing
coleção contém.)Caso 2: você deseja adicionar itens à coleção.
Então a lista é um consumidor , então você deve usar a
Collection<? super Thing>
.O raciocínio aqui é que
Collection<? extends Thing>
, diferentemente ,Collection<? super Thing>
sempre pode conter,Thing
não importa qual seja o tipo parametrizado real. Aqui você não se importa com o que já está na lista, desde que permita que umThing
seja adicionado; é isso que? super Thing
garante.fonte
doSomethingWithList(List list)
, está consumindo a lista e, portanto, precisará de covariância / extensão (ou uma Lista invariável). Por outro lado, se o seu método forList doSomethingProvidingList
, você estará produzindo a Lista e precisará de contravariância / super (ou uma Lista invariável).const
como parâmetros de método em C ++ para significar que o método não modifica os argumentos?Os princípios por trás disso na ciência da computação são chamados
? extends MyClass
,? super MyClass
eMyClass
A figura abaixo deve explicar o conceito. Imagem cedida por: Andrey Tyukin
fonte
PECS (Produtor
extends
e Consumidorsuper
)mnemônico → princípio Get and Put.
Este princípio afirma que:
Exemplo em Java:
Princípio de substituição de Liskov: se S é um subtipo de T, objetos do tipo T podem ser substituídos por objetos do tipo S.
No sistema de tipos de uma linguagem de programação, uma regra de digitação
Covariância e contravariância
Para ilustrar esse fenômeno geral, considere o tipo de matriz. Para o tipo Animal, podemos criar o tipo Animal []
Exemplos Java:
mais exemplos
curinga limitado (ou seja, indo para algum lugar) : Existem 3 tipos diferentes de curingas:
?
ou? extends Object
- Curinga não vinculado . Representa a família de todos os tipos. Use quando você pegar e colocar.? extends T
(a família de todos os tipos que são subtipos deT
) - um curinga com um limite superior .T
é a classe mais alta na hierarquia de herança. Use umextends
curinga quando você apenas obtém valores de uma estrutura.? super T
(a família de todos os tipos que são supertipos deT
) - um curinga com um limite inferior .T
é a classe mais baixa na hierarquia de herança. Use umsuper
curinga quando você apenas coloca valores em uma estrutura.Nota: curinga
?
significa zero ou uma vez , representa um tipo desconhecido. O curinga pode ser usado como o tipo de um parâmetro, nunca usado como um argumento de tipo para uma chamada de método genérico, a criação da instância classe genérica. (Ou seja, quando curinga usado essa referência não utilizados em outras partes programa como usamosT
)genéricos e exemplos
fonte
In-variance/Non-variance: ? or ? extends Object - Unbounded Wildcard. It stands for the family of all types. Use when you both get and put.
não consigo adicionar elemento à Lista <?> Ou Lista <? estende Object>, então não entendo por que pode serUse when you both get and put
.?
- o "curinga ilimitado" - corresponde ao exato oposto da invariância. Consulte a seguinte documentação: docs.oracle.com/javase/tutorial/java/generics/…, que declara: No caso em que o código precisa acessar a variável como variável "in" e "out", faça Não use um curinga. (Eles estão usando "in" e "out" como sinônimo de "get" e "put"). Com exceção denull
você não pode adicionar a uma coleção parametrizada com?
.fonte
? extends B
meio B e qualquer coisa que se estende B.Como eu explico em minha resposta a outra pergunta, PECS é um dispositivo mnemônico criado por Josh Bloch para ajudar a lembrar P roducer
extends
, C onsumersuper
.Observe que geralmente você só deve usar
? extends T
e? super T
para os parâmetros de algum método. Os métodos devem usar apenasT
como o parâmetro type em um tipo de retorno genérico.fonte
Em poucas palavras, três regras fáceis de lembrar do PECS:
<? extends T>
curinga se precisar recuperar o objeto do tipoT
de uma coleção.<? super T>
curinga se precisar colocar objetos do tipoT
em uma coleção.fonte
vamos assumir esta hierarquia:
Vamos esclarecer o PE - Producer Extends:
Por que você não pode adicionar objetos que estendem "Shark" nesta lista? gostar:
Como você possui uma lista que pode ser do tipo A, B ou C em tempo de execução , não é possível adicionar nenhum objeto do tipo A, B ou C, pois pode acabar com uma combinação que não é permitida no java.
Na prática, o compilador pode realmente ver na hora da compilação que você adiciona um B:
... mas não há como saber se, em tempo de execução, seu B será um subtipo ou supertipo do tipo de lista. No tempo de execução, o tipo de lista pode ser qualquer um dos tipos A, B, C. Portanto, você não pode adicionar o HammerSkark (supertipo) em uma lista do DeadHammerShark, por exemplo.
* Você dirá: "OK, mas por que não consigo adicionar o HammerSkark, pois esse é o menor?". Resposta: É o menor que você conhece. Mas o HammerSkark também pode ser estendido por outra pessoa e você acaba no mesmo cenário.
Vamos esclarecer o CS - Consumer Super:
Na mesma hierarquia, podemos tentar o seguinte:
O que e por que você pode adicionar a esta lista?
Você pode adicionar os tipos de objetos acima, porque qualquer coisa abaixo do tubarão (A, B, C) sempre será subtipos de qualquer coisa acima do tubarão (X, Y, Z). Fácil de entender.
Você não pode adicionar tipos acima do Shark, porque em tempo de execução, o tipo de objeto adicionado pode ser mais alto na hierarquia que o tipo declarado da lista (X, Y, Z). Isso não é permitido.
Mas por que você não pode ler desta lista? (Quero dizer, você pode obter um elemento dele, mas não pode atribuí-lo a nada além de Objeto o):
Em tempo de execução, o tipo de lista pode ser qualquer tipo acima de A: X, Y, Z, ... O compilador pode compilar sua declaração de atribuição (o que parece correto), mas, em tempo de execução, o tipo de s (Animal) pode ser menor em hierarquia que o tipo declarado da lista (que pode ser Criatura ou superior). Isso não é permitido.
Resumindo
Usamos
<? super T>
para adicionar objetos de tipos iguais ou inferioresT
aoList
. Não podemos ler sobre isso.Usamos
<? extends T>
para ler objetos de tipos iguais ou abaixoT
da lista. Não podemos adicionar elemento a ele.fonte
(adicionando uma resposta porque nunca há exemplos suficientes com curingas genéricos)
fonte
Esta é a maneira mais clara e simples de pensar em extensão versus super:
extends
é para leiturasuper
é para escreverEu acho que o "PECS" é uma maneira não óbvia de pensar sobre quem é o "produtor" e quem é o "consumidor". "PECS" é definido a partir da perspectiva da própria coleta de dados - a coleção "consome" se os objetos estão sendo escritas para ele (é consumir objetos de código de chamada), e "produz" se os objetos estão sendo lidos a partir dele (ele está produzindo objetos para algum código de chamada). Isso é contrário a como todo o resto é nomeado. As APIs Java padrão são nomeadas da perspectiva do código de chamada, não da coleção em si. Por exemplo, uma visão centrada na coleção de java.util.List deve ter um método chamado "receive ()" em vez de "add ()" - afinal,o elemento, mas a própria lista recebe o elemento.
Eu acho que é mais intuitivo, natural e consistente pensar nas coisas da perspectiva do código que interage com a coleção - o código "lê de" ou "escreve para" a coleção? Depois disso, qualquer código gravado na coleção seria o "produtor" e qualquer código lido na coleção seria o "consumidor".
fonte
src
edst
. Então, você está lidando com código e contêineres ao mesmo tempo e acabei pensando sobre isso nesse sentido - "consumir código" consome de um contêiner de produção e "produzir código" produz para um contêiner de consumo.A "regra" do PECS apenas garante que o seguinte seja legal:
?
for, pode se referir legalmente aT
?
for, pode ser legalmente referido porT
O emparelhamento típico ao longo das linhas de
List<? extends T> producer, List<? super T> consumer
é simplesmente garantir que o compilador possa impor as regras padrão de relacionamento de herança "IS-A". Se pudéssemos fazer isso legalmente, seria mais simples dizer<T extends ?>, <? extends T>
(ou melhor ainda, em Scala, como você pode ver acima)[-T], [+T]
. Infelizmente, o melhor que podemos fazer é<? super T>, <? extends T>
.Quando o encontrei pela primeira vez e quebrei na cabeça, a mecânica fazia sentido, mas o código em si continuava confuso para mim - fiquei pensando "parece que os limites não precisam ser invertidos assim" - mesmo que eu ficou claro o que foi dito acima - que se trata simplesmente de garantir a conformidade com as regras padrão de referência.
O que me ajudou foi vê-lo usando a atribuição comum como analogia.
Considere o seguinte código de brinquedo (não pronto para produção):
Ilustrando isso em termos da analogia atribuição, para
consumer
o?
wildcard (tipo desconhecido) é a referência - o "lado esquerdo" do trabalho - e<? super T>
garante que tudo o que?
é,T
"é-A"?
- queT
pode ser atribuído a ele, porque?
é um super tipo (ou, no máximo, o mesmo tipo) deT
.Para
producer
a preocupação é o mesmo que seja apenas invertido:producer
's?
curinga (tipo desconhecido) é o referente - o 'lado direito' do trabalho - e<? extends T>
garante que tudo o que?
é,?
'é-A'T
- que ele pode ser atribuído a umT
, porque?
é um subtipo (ou pelo menos o mesmo tipo) deT
.fonte
Lembre-se disso:
fonte
Usando o exemplo da vida real (com algumas simplificações):
<? super FreightCarSize>
<? extends DepotSize>
fonte
Covariância : aceitar subtipos
Contravariância : aceitar supertipos
Tipos covariantes são somente leitura, enquanto tipos contravariantes são somente gravação.
fonte
Vamos dar uma olhada no exemplo
Genéricos permite que você trabalhe com tipos dinamicamente de uma maneira segura
Problema
Como o Java's Collection é um tipo de referência, temos os próximos problemas:
Problema # 1
* O genérico do Swift não tem esse problema porque Collection é
Value type
[About], portanto, uma nova coleção é criadaProblema # 2
A solução - Curingas genéricos
Curinga é um recurso do tipo de referência e não pode ser instanciado diretamente
A solução 1,
<? super A>
conhecida como contravariância de limite inferior, ou consumidores, garante que é operada por A e por todas as superclasses, por isso é seguro adicionarSolução # 2
<? extends A>
aka limite superior aka covariância aka produtores garante que é operado por A e por todas as subclasses, é por isso que é seguro obter e transmitirfonte