Ar sąrašas <Dog> yra sąrašo <Animal> poklasis? Kodėl „Java“ generiniai vaistai netiesiogiai polimorfiniai?

Aš šiek tiek supranta, kaip „Java“ generiniai vaistai tvarko paveldėjimą / polimorfizmą.

Tarkime, kad ši hierarchija -

Gyvūnai (tėvai)

Šuo - katė (vaikai)

Tarkime, aš turiu doSomething(List<Animal> animals) metodą doSomething(List<Animal> animals) . Visomis paveldėjimo ir polimorfizmo taisyklėmis manau, kad List<Dog> yra „ List<Animal> ir List<Cat> yra„ List<Animal> todėl bet kuris iš jų gali būti perduotas šiam metodui. Ne taip Jei noriu pasiekti šį elgesį, turiu aiškiai pasakyti metodą, kaip priimti bet kurio Gyvūnų poklasio sąrašą, sakydamas doSomething(List<? extends Animal> animals) .

Suprantu, kad tai yra „Java“ elgesys. Turiu klausimą, kodėl? Kodėl polimorfizmas paprastai yra netiesioginis, bet, kai kalbama apie generinius vaistus, ar tai turėtų būti nurodyta?

662
30 апр. nustatė froadie 30 balandis. 2010-04-30 17:39 „10 at 17:39 PM 2010-04-30 17:39
@ 17 atsakymų

Ne, List<Dog> nėra List<Animal> . Pagalvokite apie tai, ką galite daryti su List<Animal> - galite pridėti bet kokį gyvūną ... įskaitant katę. Ar galėtumėte logiškai pridėti katę į šuniukų vadą? Tikrai ne.

 // Illegal code - because otherwise life would be Bad List<Dog> dogs = new ArrayList<Dog>(); // ArrayList implements List List<Animal> animals = dogs; // Awooga awooga animals.add(new Cat()); Dog dog = dogs.get(0); // This should be safe, right? 

Staiga jūs turite labai susipynę katę.

Dabar negalite pridėti Cat prie List<? extends Animal> List<? extends Animal> nes nežinote jo List<Cat> . Jūs galite gauti vertę ir žinoti, kad tai bus Animal , bet jūs negalite pridėti savavališkų gyvūnų. Atgal yra teisinga „ List<? super Animal> List<? super Animal> tokiu atveju galite saugiai pridėti „ Animal , bet nieko nežinote apie tai, ką iš jo galima išmokti, nes jis gali būti „ List<Object> .

804
30 апр. Atsakyti Jon Skeet balandžio 30 d 2010-04-30 17:44 '10, 17:44, 2010-04-30 17:44

Tai, ko ieškote, vadinamas kovariantinio tipo parametrais. Tai reiškia, kad jei vienas objekto tipas gali būti pakeistas kitu metodu (pvz., Animal galima pakeisti Dog ), tas pats pasakytina ir apie išraiškas naudojant šiuos objektus (todėl List<Animal> gali būti pakeistas List<Dog> ). Problema yra ta, kad kovariacija yra nesaugi dėl bendrų sąrašų. Tarkime, kad turite List<Dog> , ir jis naudojamas kaip List<Animal> . Kas atsitinka, kai bandote pridėti katę į šį List<Animal> kuris iš tikrųjų yra List<Dog> ? Automatinio tipo parametrų kovariacijos raiška pažeidžia tipo sistemą.

Ar būtų naudinga pridėti sintaksę, kuri leistų nurodyti tipo parametrus kaip kovariantus, kurie vengia ? extends Foo ? extends Foo ? extends Foo ? extends Foo “ metodų deklaracijose, tačiau suteikia papildomą sudėtingumą.

70
30 апр. Atsakymą pateikė Michael Ekstrand balandžio 30 d 2010-04-30 17:44 '10, 17:44, 2010-04-30 17:44

Priežastis, kodėl List<Dog> nėra List<Animal> reiškia, kad galite įterpti Cat į List<Animal> , bet ne List<Dog> ... galite naudoti pakaitos simbolius, kad pratęstumėte generiniai vaistai; Pavyzdžiui, skaitymas iš List<Dog> panašus į skaitinį iš List<Animal> - bet ne raštu.

„Java Generics“ ir „Java“ vadovų „Generics“ skyrius turi labai gerą, išsamų paaiškinimą, kodėl kai kurie dalykai nėra polimorfiniai arba išspręsti naudojant generinius vaistus.

42
30 апр. Michael Aaron Safyan atsakymas balandžio 30 d 2010-04-30 17:46 '10, 17:46, 2010-04-30 17:46

Sakyčiau, kad visa Generics taškas yra tai, kad ji neleidžia. Apsvarstykite situaciją su matricomis, kurios leidžia šio tipo kovariacijai:

  Object[] objects = new String[10]; objects[0] = Boolean.FALSE; 

Šis kodas sukompiliuotas, bet suteikia klaidą ( java.> antroje eilutėje). Tai nėra tipiška. Generics taškas yra pridėti saugumo tipo kompiliavimo laiką, kitaip galite tiesiog laikytis paprastos klasės be generinių vaistų.

Dabar yra kartų, kai jums reikia būti lankstesniems ir jums to reikia ? super Class ? super Class ir ? extends Class ? extends Class . Pirma, kai reikia įdėti Collection tipą (pvz.), O paskutinis - kai reikia jį perskaityti, saugus tipas. Tačiau vienintelis būdas tai padaryti tuo pačiu metu yra tam tikras tipas.

32
30 апр. Yishai atsakymas, pateiktas balandžio 30 d 2010-04-30 17:50 '10, 5:50 PM 2010-04-30 17:50

Manau, kad tai turėtų būti pridėta prie kitų atsakymų

List<Dog>List<Animal> nėra „Java“ List<Animal>

taip pat tiesa

Šunų sąrašas yra sąrašas anglų kalba (gerai, su protingu aiškinimu)

Tai, kaip OP intuicija veikia - tai yra natūralus - yra paskutinis sakinys. Tačiau, jei taikysime šią intuiciją, mes gausime kalbą, kuri nėra „Java“ esė savo tipo sistemoje: Tarkime, kad mūsų kalba neleidžia pridėti katės prie mūsų šunų sąrašo. Ką tai reiškia? Tai reikštų, kad sąrašas nebebus šunų sąrašas ir išlieka tiesiog gyvūnų sąrašas. Ir žinduolių sąrašas ir kvadrantų sąrašas.

Kitaip tariant, List<Dog> „Java“ nereiškia „šunų sąrašo“ anglų kalba, tai reiškia „sąrašą, kuriame gali būti šunų ir nieko daugiau“.

Apskritai, OP intuicija yra tinkama kalbai, kurioje objektų operacijos gali pakeisti jų tipą , arba, tiksliau, objekto tipas yra (dinaminė) jos vertės funkcija.

29
30 марта '13 в 10:14 2013-03-30 10:14 atsakymą pateikė „ einpoklum“ kovo 30 d., 13 d., 10:14 2013-03-30 10:14

Norėdami suprasti problemą, naudinga palyginti su matricomis.

List<Dog> nėra List<Animal> poklasis.
Tačiau Dog[] yra Animal[] subklasė.

Array yra pakartotinai pritaikoma ir kovariacija .
reifikuojama, kad jų tipo informacija yra visiškai prieinama vykdymo metu.
Todėl matricos užtikrina saugumą vykdymo metu, tačiau nesuteikia saugumo, kaip ir kompiliavimo.

  // All compiles but throws ArrayStoreException at runtime at last line Dog[] dogs = new Dog[10]; Animal[] animals = dogs; // compiles animals[0] = new Cat(); // throws ArrayStoreException at runtime 

Tai priešingai generiniams vaistams:
Generiniai vaistai yra ištrinami ir keičiami .
Taigi, generatoriai negali užtikrinti saugumo runtime, tačiau jie teikia tokį saugumą kaip rinkinys.
Toliau pateiktame kode, jei generiniai vaistai buvo koviniai, 3 eilutėje galima užteršti taršą .

  List<Dog> dogs = new ArrayList<>(); List<Animal> animals = dogs; // compile-time error, otherwise heap pollution animals.add(new Cat()); 
7
29 сент. atsakymas pateiktas 29 rugsėjo 2017-09-29 22:55 '17, 10:55 pm 2017-09-29 22:55

Atsakymai čia ne visiškai įtikino mane. Taigi vietoj to, aš paimsiu kitą pavyzdį.

 public void passOn(Consumer<Animal> consumer, Supplier<Animal> supplier) { consumer.accept(supplier.get()); } 

tai skamba normaliai? Bet jūs galite perkelti tik Consumer ir Supplier Animal . Jei turite vartotojų Mammal , bet Duck teikėjas, jie neturi laikytis, nors abu yra gyvūnai. Tam uždrausti buvo įtraukti papildomi apribojimai.

Vietoj pirmiau nurodytų dalykų privalome apibrėžti ryšį tarp naudojamų tipų.

E.,

 public <A extends Animal> void passOn(Consumer<A> consumer, Supplier<? extends A> supplier) { consumer.accept(supplier.get()); } 

užtikrina, kad mes galime naudoti tik tiekėją, kuris vartotojui suteikia mums tinkamą objekto tipą.

OTOH, mes taip pat galėtume padaryti

 public <A extends Animal> void passOn(Consumer<? super A> consumer, Supplier<A> supplier) { consumer.accept(supplier.get()); } 

kur mes einame kitaip: mes apibrėžiame Supplier tipą ir apribojame jį tik tuo, kad jis gali būti patalpintas į Consumer .

Mes netgi galime padaryti

 public <A extends Animal> void passOn(Consumer<? super A> consumer, Supplier<? extends A> supplier) { consumer.accept(supplier.get()); } 

kur, turėdami intuityvius santykius LifeAnimalMammalDog , Cat ir kt., mes galėtume netgi įdėti „ Mammal į „ Life vartotoją, bet ne „The StringLife .

4
15 февр. atsakymas pateikiamas gglgl 15 feb . 2015-02-15 00:26 '15 - 0:26 2015-02-15 00:26

Šio elgesio logika pagrįsta tuo, kad Generics laikosi ištrynimo tipo mechanizmo. Todėl vykdymo metu negalite nustatyti collection tipo, collection ne arrays kuriuose nėra tokio ištrynimo proceso. Taigi grįžkite į savo klausimą ...

Tarkime, kad yra toks metodas:

 add(List<Animal>){ //You can add List<Dog or List<Cat> and this will compile as per rules of polymorphism } 

Dabar, jei java leidžia skambinančiam asmeniui pridėti šio metodo „Animal“ tipo sąrašą, tuomet kolekcijai galite pridėti neteisingą dalyką, o vykdymo metu jis bus pradėtas dėl tipo ištrynimo. Nors matricų atveju jūs gaunate tokį scenarijų vykdymo išimtis ...

Taigi, iš esmės šis elgesys įgyvendinamas taip, kad į kolekciją neįtrauktas neteisingas dalykas. Dabar manau, kad egzistuoja stilių ištrynimas, siekiant užtikrinti suderinamumą su senovine java be generinių vaistų.

4
04 дек. Atsakymas duotas Hitesh gruodžio 4 d. 2012-12-04 13:43 '12 13:43 2012-12-04 13:43

Tiesą sakant, galite pasiekti sąsają norimam pasiekti.

 public interface Animal { String getName(); String getVoice(); } public class Dog implements Animal{ @Override String getName(){return "Dog";} @Override String getVoice(){return "woof!";} 

}

galite naudoti kolekcijas naudodami

 List <Animal> animalGroup = new ArrayList<Animal>(); animalGroup.add(new Dog()); 
3
12 июля '15 в 7:14 2015-07-12 07:14 atsakymą pateikė Angel Koh , liepos 15 d., 15 val., 07:14 2015-07-12 07:14

Atsakymas, taip pat kiti atsakymai yra teisingi. Aš atsakysiu į šiuos atsakymus, kurie, mano manymu, bus naudingi. Manau, kad tai dažnai įvyksta programuojant. Pažymėtina, kad kolekcijoms (sąrašams, rinkiniams ir pan.) Pagrindinė problema yra įtraukti į kolekciją. Štai kur pertraukos. Net pašalinimas yra gerai.

Daugeliu atvejų galime naudoti Collection<? extends T> Collection<? extends T> , o ne Collection<T> , ir tai turėtų būti pirmasis pasirinkimas. Tačiau manau, kad tai nėra lengva padaryti. Jis aptaria, ar tai visada yra geriausias. Čia pateikiu DownCastCollection klasę, kuri gali konvertuoti Collection<? extends T> Collection<? extends T> į Collection<T> (mes galime apibrėžti panašias klases sąrašui, rinkiniui, navigaciniam rinkiniui, ..), kurios bus naudojamos naudojant standartinį metodą yra labai nepatogu. Toliau pateikiamas pavyzdys, kaip jį naudoti (šiuo atveju taip pat galėtume naudoti Collection<? extends Object> , bet aš negaliu parodyti naudojant „DownCastCollection“.

  public static void print(Collection<Object> col){ for(Object obj : col){ System.out.println(obj); } } public static void main(String[] args){ ArrayList<String> list = new ArrayList<>(); list.addAll(Arrays.asList("a","b","c")); print(new DownCastCollection<Object>(list)); } 

Dabar klasė:

 import java.util.AbstractCollection; import java.util.Collection; import java.util.Iterator; import java.util.NoSuchElementException; public class DownCastCollection<E> extends AbstractCollection<E> implements Collection<E> { private Collection<? extends E> delegate; public DownCastCollection(Collection<? extends E> delegate) { super(); this.delegate = delegate; } @Override public int size() { return delegate ==null ? 0 : delegate.size(); } @Override public boolean isEmpty() { return delegate==null || delegate.isEmpty(); } @Override public boolean contains(Object o) { if(isEmpty()) return false; return delegate.contains(o); } private class MyIterator implements Iterator<E>{ Iterator<? extends E> delegateIterator; protected MyIterator() { super(); this.delegateIterator = delegate == null ? null :delegate.iterator(); } @Override public boolean hasNext() { return delegateIterator != null  delegateIterator.hasNext(); } @Override public E next() { if(!hasNext()) throw new NoSuchElementException("The iterator is empty"); return delegateIterator.next(); } @Override public void remove() { delegateIterator.remove(); } } @Override public Iterator<E> iterator() { return new MyIterator(); } @Override public boolean add(E e) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object o) { if(delegate == null) return false; return delegate.remove(o); } @Override public boolean containsAll(Collection<?> c) { if(delegate==null) return false; return delegate.containsAll(c); } @Override public boolean addAll(Collection<? extends E> c) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection<?> c) { if(delegate == null) return false; return delegate.removeAll(c); } @Override public boolean retainAll(Collection<?> c) { if(delegate == null) return false; return delegate.retainAll(c); } @Override public void clear() { if(delegate == null) return; delegate.clear(); } 

}

1
15 дек. atsakymas pateikiamas dan b 15 dec. 2014-12-15 14:14 '14, 14:14 2014-12-15 14:14

Jei esate tikri, kad sąrašo elementai yra šio super tipo poklasiai, galite naudoti šį sąrašą naudodami šį metodą:

 (List<Animal>) (List<?>) dogs 

Tai yra naudinga, jei norite per jį sudaryti konstruktoriaus ar kilpos sąrašą.

1
29 янв. atsakymas pateikiamas sagits Jan 29 2016-01-29 00:11 '16 at 0:11 2016-01-29 00:11

Parametrų tipai yra subartiški. Net sunki Dog klasė yra Animal potipis, parametruojamasis List<Dog> nėra List<Animal> potipis. Priešingai, kovariantinis potipis naudojamas masyvuose, todėl Dog[] masyvo tipas yra Animal[] .

Nepakankamas subtipavimas užtikrina, kad nebūtų pažeisti „Java“ vykdomi tipų apribojimai. Apsvarstykite šį @Jon Skeet pateiktą kodą:

 List<Dog> dogs = new ArrayList<Dog>(1); List<Animal> animals = dogs; animals.add(new Cat()); // compile-time error Dog dog = dogs.get(0); 

Kaip teigė @Jon Skeet, šis kodas yra neteisėtas, nes kitaip jis pažeidžia tipo apribojimus grąžindamas katę, kai tikimasi šuns.

Būtina paminėti aukščiau pateiktus duomenis su panašiu masyvų kodu.

 Dog[] dogs = new Dog[1]; Object[] animals = dogs; animals[0] = new Cat(); // run-time error Dog dog = dogs[0]; 

Šis kodas yra teisėtas. Tačiau jis išmeta masyvo saugojimo išimtį . Masyvas turi savo tipą paleidimo metu, todėl JVM gali suteikti kovariantinio subtipavimo tipo apsaugą.

Norėdami tai suprasti, pažvelkime žemiau pateiktą „ javap klasės sukurtą javap :

 import java.util.ArrayList; import java.util.List; public class Demonstration { public void normal() { List normal = new ArrayList(1); normal.add("lorem ipsum"); } public void parameterized() { List<String> parameterized = new ArrayList<>(1); parameterized.add("lorem ipsum"); } } 

Naudojant komandą javap -c Demonstration ši Java bytecode:

 Compiled from "Demonstration.java" public class Demonstration { public Demonstration(); Code: 0: aload_0 1: invokespecial #1 // Method java/> 

Atkreipkite dėmesį, kad išverstas metodų korpuso kodas yra identiškas. Kompiliatorius pakeitė kiekvieną parametrų tipą su jo ištrynimu . Ši savybė yra labai svarbi, nes nesuderina suderinamumo.

Galiausiai, parametrų tipams neįmanoma paleisti laiko, nes kompiliatorius pakeičia kiekvieną parametruotą tipą su jo ištrynimu. Dėl to parametruojantys tipai nėra tik sintaksinis cukrus.

1
03 мая '18 в 17:00 2018-05-03 17:00 atsakymas pateikiamas Root G 03 Gegužės 18 d. 17:00 2018-05-03 17:00

Turiu tą pačią problemą. Tačiau bendras paramos tipas yra <? extends Y> <? extends Y> kad išspręstumėte šią problemą:

Collection<A> aCollection= getAFromDB(); AUtil.findMetConidition(aCollection);

Klasė:

 public interface A extends B {...} 

Metodų deklaracija:

 b findMetConidition(Collection<? extends B> bCollection ) { ...} 
0
02 дек. Atsakymas pateiktas Hlex 02 Dec. 2017-12-02 20:12 '17, 08:12 pm 2017-12-02 20:12

Taip pat turime apsvarstyti, kaip kompiliatorius kelia grėsmę bendroms klasėms: kuriant bendrus argumentus, sukuriame skirtingo tipo atvejus.

Taigi mes turime „ ListOfAnimal , „ ListOfDog , „ ListOfCat ir tt, kurie yra įvairios klasės, kurias galiausiai „sukuria“ kompiliatorius, nurodydamas bendrus argumentus. Ir tai yra plokščia hierarchija (iš tikrųjų, palyginti su List nėra hierarchija).

Kitas argumentas, kodėl kovariacija nėra prasminga bendrųjų klasių atveju, yra ta, kad duomenų bazėje visos klasės yra tos pačios - tai List atvejai. Specializuodamasis į List , užpildydamas bendrąjį argumentą, neplatina klasės, jis paprasčiausiai leidžia dirbti šiam bendram argumentui.

0
02 марта '18 в 10:48 2018-03-02 10:48 atsakymą pateikė Cristik kovo 2 d. 18 d. 10:48 2018-03-02 10:48

Paimkime pavyzdį iš „JavaSE“ vadovo

 public abstract class Shape { public abstract void draw(Canvas c); } public class Circle extends Shape { private int x, y, radius; public void draw(Canvas c) { ... } } public class Rectangle extends Shape { private int x, y, width, height; public void draw(Canvas c) { ... } } 

Tad kodėl šunų (apskritimų) sąrašas neturėtų būti laikomas numanomu gyvūnų sąrašu (skaičiais) dėl šios padėties:

 // drawAll method call drawAll(circleList); public void drawAll(List<Shape> shapes) { shapes.add(new Rectangle()); } 

Taigi „Java“ architektai turėjo dvi galimybes išspręsti šią problemą:

  • nemanau, kad potipis yra netiesiogiai supernas ir suteikia klaidų, kaip tai daroma dabar

  • apsvarstyti, ar potipis yra „supperpe“, ir renkant „pridėti“ metodą, apribokite (taigi, taikant „DrawAll“ metodą, jei yra perduodamas formų pogrupių sąrašas, kompiliatorius turėtų tai aptikti ir apriboti jums kompiliavimo klaidą).

Dėl akivaizdžių priežasčių tai pasirinko pirmąjį kelią.

0
27 февр. atsakymas suteikiamas „ Aurelius “ vasario 27 d 2016-02-27 16:00 '16 at 16:00 2016-02-27 4:00

Problema buvo gerai nustatyta. Tačiau yra sprendimas; doSomething bendrinis:

 <T extends Animal> void doSomething<List<T> animals) { } 

dabar galite skambinti „doSomething“ su sąrašu <Dog> arba „List <Cat> arba„ List <Animal>.

0
28 марта '18 в 21:56 2018-03-28 21:56 atsakymą gerardw pateikė kovo 28 d. 18 val. 21:56 2018-03-28 21:56

kitas sprendimas - sukurti naują sąrašą

 List<Dog> dogs = new ArrayList<Dog>(); List<Animal> animals = new ArrayList<Animal>(dogs); animals.add(new Cat()); 
0
20 июля '18 в 17:12 2018-07-20 17:12 atsakymas duotas ejaenv'ui liepos 20 d., 18 val., 12:12, 2018-07-20 17:12