Razumevanje združljivosti tipov je bistvenega pomena za pisanje dobrih programov Java, toda medsebojno vplivanje na razlike med elementi jezika Java se lahko nepoznavalcem zdi zelo akademsko. Ta dvodelni članek je namenjen razvijalcem programske opreme, ki so pripravljeni spoprijeti se z izzivom! Prvi del je razkril kovariante in kontravariantne povezave med enostavnejšimi elementi, kot so tipi matrike in generični tipi, pa tudi posebni element jezika Java, nadomestni znak. Drugi del raziskuje odvisnost tipa v API-ju Java Collections, v generikih in lambda izrazih.
Skočili bomo takoj, tako da, če še niste prebrali 1. dela, priporočam, da začnete tam.
Primeri API za kontravariance
Za naš prvi primer si oglejte Primerjalnik
različica java.util.Collections.sort ()
, iz Java Collections API. Podpis te metode je:
void sort (seznam seznamov, primerjalnik c)
The razvrsti ()
metoda razvrsti katero koli Seznam
. Običajno je lažje uporabiti preobremenjeno različico s podpisom:
razvrsti (Seznam)
V tem primeru, razširja Primerljivo
izraža, da razvrsti ()
se lahko pokliče le, če so potrebni elementi za primerjavo metod (in sicer primerjaj)
so bili definirani v tipu elementa (ali v njegovem nadtipu, zahvaljujoč ? super T)
:
sort (integerList); // Integer izvaja primerljivo razvrščanje (customerList); // deluje samo, če kupec izvaja Primerljivo
Uporaba generičnih zdravil za primerjavo
Očitno je, da je seznam mogoče razvrstiti le, če je mogoče njegove elemente med seboj primerjati. Primerjava se opravi z eno samo metodo primerjajTo
, ki pripada vmesniku Primerljivo
. Morate izvesti primerjajTo
v razredu elementov.
To vrsto elementa pa je mogoče razvrstiti samo na en način. Na primer, lahko razvrstite a Stranka
po osebnem dokumentu, ne pa tudi po rojstnem dnevu ali poštni številki. Uporabljati Primerjalnik
različica razvrsti ()
je bolj prilagodljiv:
publicstatic void sort (seznam seznamov, primerjalnik c)
Zdaj elemente primerjamo ne v razredu elementa, ampak v dodatnem Primerjalnik
predmet. Ta generični vmesnik ima eno objektno metodo:
int primerjava (T o1, T o2);
Parametri kontravarijantov
Instanciranje predmeta večkrat vam omogoča razvrščanje predmetov z uporabo različnih meril. Toda ali res potrebujemo tako zapleteno Primerjalnik
parameter tipa? V večini primerov, Primerjalnik
bi bilo dovolj. Lahko bi uporabili njegovo primerjaj ()
metoda za primerjavo katerih koli dveh elementov v Seznam
predmet, kot sledi:
razred DateComparator izvaja primerjalnik {public int compare (Date d1, Date d2) {return ...} // primerja oba predmeta Date} Seznam dateList = ...; // Seznam predmetov Date sort (dateList, new DateComparator ()); // razvrsti dateList
Uporaba bolj zapletene različice metode Collection.sort ()
vendar nas pripravite na dodatne primere uporabe. Parameter kontravariantnega tipa za Primerljivo
omogoča razvrščanje seznama vrst Seznam
, Ker java.util.Date
je supertip java.sql.Date
:
Seznam sqlList = ...; razvrsti (sqlList, nov DateComparator ());
Če izpustimo kontravariance v razvrsti ()
podpis (samo z uporabo ali neopredeljeno, nevarno
), nato pa prevajalnik zavrne zadnjo vrstico kot napako tipa.
Da bi poklicali
razvrsti (sqlList, nov SqlDateComparator ());
morali bi napisati dodaten razred brez značilnosti:
razred SqlDateComparator podaljša DateComparator {}
Dodatne metode
Collections.sort ()
ni edina metoda API Java Collections, opremljena s kontravariantnim parametrom. Metode kot addAll ()
, binarySearch ()
, kopirati()
, fill ()
, in tako naprej, se lahko uporabljajo s podobno prilagodljivostjo.
Zbirke
metode, kot so največ ()
in min ()
ponujajo kontravariantne vrste rezultatov:
javni statični T max (zbirka zbirk) {...}
Kot vidite tukaj, lahko od parametra tipa zahtevamo, da izpolnjuje več pogojev, samo z uporabo &
. The razširi Object
morda zdi odveč, vendar to določa največ ()
vrne rezultat tipa Predmet
in ne vrstice Primerljivo
v bytecode. (V bajtkodi ni parametrov tipa.)
Preobremenjena različica največ ()
s Primerjalnik
je še bolj smešno:
javni statični T max (zbirka zbirk, primerjalnik comp)
To največ ()
ima oboje kontravariantno in parametri kovariantnega tipa. Medtem ko so elementi Zbirka
mora biti (po možnosti drugačnih) podtipov določene (ne izrecno podane) vrste, Primerjalnik
mora biti primer za nadtip istega tipa. Za razlikovanje tega vmesnega tipa od takšnega klica je potrebno veliko od algoritma sklepanja prevajalnika:
Zbirka zbirke = ...; Primerjalnik primerjalnik = ...; max (zbirka, primerjalnik);
Vezava tipskih parametrov v škatli
Kot naš zadnji primer odvisnosti od tipa in variance v API-ju Java Collections, ponovno razmislimo o podpisu razvrsti ()
s Primerljivo
. Upoštevajte, da uporablja oboje podaljša
in super
, ki so zapakirani:
statično neveljavno razvrščanje (seznam seznamov) {...}
V tem primeru nas združljivost referenc ne zanima tako zelo kot zavezujoča instancacija. Ta primer razvrsti ()
metoda razvrsti a seznam
objekt z elementi izvedbe razreda Primerljivo
. V večini primerov bi razvrščanje delovalo brez v podpisu metode:
razvrsti (dateList); // java.util.Date izvaja primerljivo razvrščanje (sqlList); // java.sql.Date izvaja Primerljivo
Spodnja meja parametra tipa pa omogoča dodatno prilagodljivost. Primerljivo
ni nujno, da je treba implementirati v razred elementov; dovolj je, da smo ga implementirali v superrazred. Na primer:
razred SuperClass implementira Primerljiv {public int compareTo (SuperClass s) {...}} razred SubClass razširja SuperClass {} // brez preobremenitve s primerjavo () Seznam superList = ...; razvrsti (superList); SubList seznama = ...; razvrsti (podlist);
Prevajalnik sprejme zadnjo vrstico z
statično neveljavno razvrščanje (seznam seznamov) {...}
in jo zavrne z
statično void sort (Seznam seznamov) {...}
Razlog za to zavrnitev je, da je tip Podrazred
(ki bi ga prevajalnik določil glede na vrsto Seznam
v parametru subList
) ni primeren kot parameter tipa za T se razteza Primerljivo
. Tip Podrazred
se ne izvaja Primerljivo
; samo izvaja Primerljivo
. Oba elementa sicer nista združljiva zaradi pomanjkanja implicitne kovarijance Podrazred
je združljiv z SuperClass
.
Po drugi strani pa, če uporabimo , prevajalnik ne pričakuje
Podrazred
za izvajanje Primerljivo
; dovolj je, če SuperClass
naredi to. Dovolj je, ker metoda compareTo ()
je podedovan od SuperClass
in se lahko zahteva Podrazred
predmeti: izraža to, kar povzroča kontravarenco.
Kontravariantno dostopanje do spremenljivk parametra tipa
Zgornja ali spodnja meja velja samo za parameter tipa primerov, ki jih napoti kovariantna ali kontravariantna referenca. V primeru Generična kovariantna referenca;
in Generični kontravariantReference;
, lahko ustvarimo in napotimo predmete različnih Splošno
instanci.
Za parameter in vrsto rezultata metode veljajo različna pravila (na primer za vhod in izhod vrste parametrov generičnega tipa). Poljuben objekt, združljiv z Podtip
se lahko posreduje kot parameter metode piši ()
, kot je opredeljeno zgoraj.
contravariantReference.write (nov podtip ()); // V redu contravariantReference.write (nov SubSubType ()); // V redu tudi kontravariantReference.write (nov SuperType ()); // napaka tipa ((Generic) contravariantReference) .write (new SuperType ()); // V REDU
Zaradi kontravariance lahko parametru posredujemo piši ()
. To je v nasprotju s kovarijantnim (tudi neomejenim) nadomestnim tipom.
Situacija se za vrsto rezultata ne spremeni z vezavo: preberi ()
še vedno daje rezultat tipa ?
, združljiv samo z Predmet
:
Objekt o = contravariantReference.read (); Podtip st = contravariantReference.read (); // napaka tipa
Zadnja vrstica povzroči napako, čeprav smo razglasili a contravariantReference
vrste Splošno
.
Vrsta rezultata je združljiva z drugo vrsto šele po vrsta reference je bila izrecno pretvorjena:
SuperSuperType sst = ((Generic) contravariantReference) .read (); sst = (SuperSuperType) contravariantReference.read (); // nevarnejša alternativa
Primeri v prejšnjih navedbah kažejo, da ima dostop do branja ali pisanja spremenljivko tipa parameter
se obnaša enako, ne glede na to, ali se to zgodi prek metode (branje in pisanje) ali neposredno (podatki v primerih).
Branje in pisanje spremenljivk tipa parameter
Tabela 1 prikazuje, da je branje v Predmet
spremenljivke je vedno mogoče, ker so vsi razredi in nadomestni znaki združljivi z Predmet
. Pisanje Predmet
je mogoča le nad kontravariantno referenco po ustreznem ulivanju, ker Predmet
ni združljiv z nadomestnim znakom. Branje brez oddajanja v neprimerno spremenljivko je možno s kovarijantno referenco. Pisanje je možno s kontravariantno referenco.
Tabela 1. Dostop do branja in pisanja spremenljivk parametra tipa
branje (vnos) | preberite Predmet | piši Predmet | preberite supertip | piši supertip | preberite podtip | piši podtip |
Nadomestni znak
| v redu | Napaka | Igralska zasedba | Igralska zasedba | Igralska zasedba | Igralska zasedba |
Kovariantno
| v redu | Napaka | v redu | Igralska zasedba | Igralska zasedba | Igralska zasedba |
Kontravariant
| v redu | Igralska zasedba | Igralska zasedba | Igralska zasedba | Igralska zasedba | v redu |
Vrstice v tabeli 1 se nanašajo na nekakšna referencain stolpci v vrsta podatkov do katerega lahko dostopate. Naslovi "nadtip" in "podtip" označujejo meje nadomestnih znakov. Vnos "oddaja" pomeni, da je treba sklic oddati. Primer "OK" v zadnjih štirih stolpcih se nanaša na tipične primere za kovarianco in kontravarianco.
Glejte konec tega članka za sistematični preskusni program za tabelo s podrobnimi razlagami.
Ustvarjanje predmetov
Po eni strani ne morete ustvariti predmetov z nadomestnimi znaki, ker so abstraktni. Po drugi strani pa lahko ustvarite niza predmetov samo neomejenega nadomestnega tipa. Vendar ne morete ustvariti predmetov drugih generičnih primerkov.
Generic [] genericArray = novo Generic [20]; // napaka tipa Generic [] wildcardArray = new Generic [20]; // V redu genericArray = (Generic []) wildcardArray; // nepreverjena pretvorba genericArray [0] = new Generic (); genericArray [0] = novo Generic (); // tip napake wildcardArray [0] = novo Generic (); // V REDU
Zaradi kovariacije nizov je vrsta nadomestnega polja Splošno []
je nadtip vrste matrike vseh primerkov; zato je dodelitev v zadnji vrstici zgornje kode mogoča.
Znotraj generičnega razreda ne moremo ustvariti predmetov parametra tipa. Na primer, v konstruktorju datoteke ArrayList
izvedbe, mora biti objekt polja tipa Predmet []
ob ustvarjanju. Nato ga lahko pretvorimo v vrsto polja parametra tipa:
razred MyArrayList izvaja vsebino List {private final E []; MyArrayList (int size) {content = new E [size]; // vrsta napake vsebina = (E []) nov objekt [velikost]; // rešitev} ...}
Za varnejšo zaobidejo mimo Razred
vrednost dejanskega parametra tipa za konstruktor:
content = (E []) java.lang.reflect.Array.newInstance(myClass, velikost);
Več parametrov tipa
Splošni tip ima lahko več kot en parameter tipa. Parametri tipa ne spreminjajo vedenja kovarijance in kontravariance in več parametrov tipa se lahko pojavi skupaj, kot je prikazano spodaj:
razred G {} referenca G; sklic = nov G (); // brez referenc variance = novo G (); // s ko- in kontravariancem
Splošni vmesnik java.util.Map
se pogosto uporablja kot primer za parametre več vrst. Vmesnik ima dva parametra tipa, enega za ključ in enega za vrednost. Predmete je koristno povezati s ključi, na primer, da jih bomo lažje našli. Telefonski imenik je primer a Zemljevid
objekt, ki uporablja parametre več vrst: ime naročnika je ključ, telefonska številka je vrednost.
Izvedba vmesnika java.util.HashMap
ima konstruktor za pretvorbo poljubnega Zemljevid
objekt v asociacijsko tabelo:
javni HashMap (zemljevid m) ...
Zaradi kovariancije parametru tipa objekta parametra v tem primeru ni treba ustrezati natančnim razredom parametrov tipa K
in V
. Namesto tega ga je mogoče prilagoditi s kovarianco:
Kupci zemljevidov; ... kontakti = novi HashMap (kupci); // kovariantno
Tukaj, Id
je supertip Številka kupca
, in Oseba
je supertip za Stranka
.
Različnost metod
Govorili smo o različnosti vrst; zdaj se obrnimo na nekoliko lažjo temo.