Ohjelmointi

Tyyppiriippuvuus Java, osa 2

Tyyppien yhteensopivuuden ymmärtäminen on perustavaa laatua hyville Java-ohjelmille, mutta Java-kielielementtien välisten variaatioiden vuorovaikutus voi tuntua aloittelijalle erittäin akateemiselta. Tämä kaksiosainen artikkeli on tarkoitettu ohjelmistokehittäjille, jotka ovat valmiita vastaamaan haasteeseen! Osa 1 paljasti kovariaattiset ja ristiriitaiset suhteet yksinkertaisempien elementtien, kuten taulukotyyppien ja yleisten tyyppien, sekä erityisen Java-kielielementin, jokerin, välillä. Osa 2 tutkii tyyppiriippuvuutta Java Collections -sovellusliittymässä, yleisissä ja lambda-lausekkeissa.

Hyppäämme suoraan sisään, joten jos et ole vielä lukenut osaa 1, suosittelen aloittamista siitä.

API-esimerkkejä ristiriitaisuudesta

Harkitse ensimmäisessä esimerkissämme Vertailija version java.util.Collections.sort (), Java Collections -sovellusliittymästä. Tämän menetelmän allekirjoitus on:

  void sort (Luetteloluettelo, vertailija c) 

järjestellä() menetelmä lajittelee kaikki Lista. Yleensä ylikuormitetun version käyttö on helpompaa allekirjoituksella:

 lajittelu (Lista) 

Tässä tapauksessa, ulottuu Vertailukelpoinen ilmaisee, että järjestellä() voidaan kutsua vain, jos tarvittavat menetelmien vertailuelementit (nimittäin vertaa) on määritelty elementtityypissä (tai sen supertyypissä ? super T):

 sort (integerList); // Kokonaisluku toteuttaa Vertailukelpoinen lajittelu (asiakaslista); // toimii vain, jos asiakas toteuttaa vertailukelpoisen 

Geneeristen lääkkeiden käyttö vertailuun

Luonnollisesti luettelo on lajiteltavissa vain, jos sen osia voidaan verrata keskenään. Vertailu tehdään yhdellä menetelmällä vertaa, joka kuuluu käyttöliittymään Vertailukelpoinen. Sinun on toteutettava vertaa elementtiluokassa.

Tämän tyyppinen elementti voidaan kuitenkin lajitella vain yhdellä tavalla. Voit esimerkiksi lajitella a Asiakas henkilötodistuksensa, mutta ei syntymäpäivän tai postinumeron perusteella. Käyttämällä Vertailija version järjestellä() on joustavampi:

 publicstatic void sort (luetteloluettelo, vertailija c) 

Nyt verrataan elementtejä ei elementtiluokkaan, vaan lisäosaan Vertailija esine. Tällä yleisellä rajapinnalla on yksi objektimenetelmä:

 int vertaa (T o1, T02); 

Suhteelliset parametrit

Objektin pikakäsittely useammin kuin kerran antaa sinun lajitella objekteja eri ehtojen mukaan. Mutta tarvitsemmeko todella niin monimutkaista Vertailija tyypin parametri? Useimmissa tapauksissa, Vertailija riittäisi. Voisimme käyttää sitä vertailla() menetelmä verrata mitä tahansa kahta elementtiä Lista esine seuraavasti:

class DateComparator toteuttaa Comparator {public int vertailla (Date d1, Date d2) {return ...} // vertaa kahta Date-objektia} List dateList = ...; // Päivämääräobjektien luettelo (dateList, new DateComparator ()); // lajittelee dateList 

Menetelmän monimutkaisemman version käyttäminen Collection.sort () kuitenkin asettanut meidät uusiin käyttötapauksiin. Kiistanalainen tyypin parametri Vertailukelpoinen mahdollistaa lajittelulistan lajittelun Lista, koska java.util.Päiväys on supertyyppi java.sql.Päiväys:

 Lista sqlList = ...; lajittelu (sqlList, uusi DateComparator ()); 

Jos jätämme ristiriitaisuuden ohjelmassa järjestellä() allekirjoitus (vain tai määrittelemätön, vaarallinen ), kääntäjä hylkää viimeisen rivin tyypin virheenä.

Soittaaksesi

 lajittelu (sqlList, uusi SqlDateComparator ()); 

sinun pitäisi kirjoittaa ylimääräinen erikoislaatuinen luokka:

 class SqlDateComparator laajentaa DateComparatoria {} 

Lisämenetelmät

Collections.sort () ei ole ainoa Java Collections -sovellusliittymämenetelmä, joka on varustettu kiistanalaisella parametrilla. Menetelmät, kuten addAll (), binaarihaku (), kopio(), täyttää(), ja niin edelleen, voidaan käyttää samanlaisella joustavuudella.

Kokoelmat menetelmiä, kuten enintään () ja min () tarjoavat sopivia tulostyyppejä:

 julkinen staattinen  T max (kokoelmakokoelma) {...} 

Kuten näette täällä, tyypin parametria voidaan pyytää täyttämään useampi kuin yksi ehto, vain käyttämällä &. ulottuu Object saattaa tuntua tarpeettomalta, mutta siinä määrätään enintään () palauttaa tyypin tuloksen Esine eikä rivistä Vertailukelpoinen tavukoodissa. (Tavutekoodissa ei ole tyypin parametreja.)

Ylikuormitettu versio enintään () kanssa Vertailija on vielä hauskempaa:

 public staattinen T max (kokoelmakokoelma, Comparator comp) 

Tämä enintään () on molemmat sopivia ja kovariaattityyppiset parametrit. Vaikka elementit Kokoelma on oltava (mahdollisesti erilaisia) tietyn tyyppisiä (ei nimenomaisesti annettuja) alatyyppejä, Vertailija täytyy olla instanssoitu samantyyppiselle supertyypille. Kääntäjän päättelyalgoritmilta vaaditaan paljon tämän välisen tyypin erottamiseksi tällaisesta puhelusta:

 Kokoelmakokoelma = ...; Vertailuvertailija = ...; max (kokoelma, vertailija); 

Tyyppiparametrien laatikoihin sidonta

Viimeisenä esimerkkinä tyypin riippuvuudesta ja varianssista Java Collections -sovellusliittymässä harkitaan uudelleen järjestellä() kanssa Vertailukelpoinen. Huomaa, että se käyttää molempia ulottuu ja super, jotka ovat laatikoissa:

 staattinen  void sort (luetteloluettelo) {...} 

Tässä tapauksessa emme ole yhtä kiinnostuneita viitteiden yhteensopivuudesta kuin sitomalla instansointia. Tämä järjestellä() menetelmä lajittelee a lista objekti, jonka luokan elementit toteuttavat Vertailukelpoinen. Useimmissa tapauksissa lajittelu toimisi ilman menetelmän allekirjoituksessa:

 lajittelu (dateList); // java.util.Date toteuttaa Vertailukelpoinen lajittelu (sqlList); // java.sql.Date toteuttaa Vertailukelpoinen 

Tyyppiparametrin alaraja antaa kuitenkin lisää joustavuutta. Vertailukelpoinen ei välttämättä tarvitse toteuttaa elementtiluokassa; riittää, kun olet ottanut sen käyttöön superluokassa. Esimerkiksi:

 luokka SuperClass toteuttaa Vertailukelpoinen {public int CompareTo (SuperClass s) {...}} -luokan aliluokka laajentaa SuperClassia {} // ilman ylikuormitusta vertailulle () List superList = ...; lajittelu (superList); Lista alilista = ...; lajittelu (alilista); 

Kääntäjä hyväksyy viimeisen rivin

 staattinen  void sort (luetteloluettelo) {...} 

ja hylkää sen

staattinen  void sort (luetteloluettelo) {...} 

Syynä hylkäämiseen on tyyppi Alaluokka (jonka kääntäjä määrittäisi tyypin perusteella Lista parametrissa alilista) ei sovi tyypin parametriksi T ulottuu vertailukelpoiseksi. Tyyppi Alaluokka ei toteuta Vertailukelpoinen; se vain toteuttaa Vertailukelpoinen. Nämä kaksi elementtiä eivät ole yhteensopivia implisiittisen kovarianssin puuttumisen vuoksi Alaluokka on yhteensopiva SuperClass.

Toisaalta, jos käytämme , kääntäjä ei odota Alaluokka toteuttaa Vertailukelpoinen; riittää jos SuperClass tekee sen. Se riittää, koska menetelmä vertaa() on peritty SuperClass ja sitä voidaan pyytää Alaluokka esineet: ilmaisee tämän, aiheuttaen ristiriitaisuutta.

Tyyppiparametrin käyttöoikeusmuuttujat

Ylä- tai alaraja koskee vain type -parametri kovariaanisen tai ristiriitaisen viittauksen osoittamat esimerkit. Siinä tapauksessa että Generic covariantReference; ja Yleinen sopivaReferenssi;, voimme luoda ja viitata eri kohteisiin Yleinen ilmentymät.

Menetelmän parametrille ja tulostyypille (kuten kohteelle tulo ja ulostulo yleisen tyypin parametrityypit). Mielivaltainen objekti, joka on yhteensopiva Alatyyppi voidaan välittää menetelmän parametrina kirjoittaa(), kuten edellä on määritelty.

 contravariantReference.write (uusi SubType ()); // OK contravariantReference.write (uusi SubSubType ()); // OK liian contravariantReference.write (uusi SuperType ()); // tyypin virhe ((Generic) contravariantReference) .write (new SuperType ()); // OK 

Ristiriitojen takia on mahdollista välittää parametri kirjoittaa(). Tämä on ristiriidassa kovariaanisen (myös rajattoman) yleismerkityypin kanssa.

Tulostyypin tilanne ei muutu sitomalla: lukea() tuottaa edelleen tyypin tuloksen ?, yhteensopiva vain Esine:

 Objekti o = contravariantReference.read (); SubType st = contravariantReference.read (); // tyyppivirhe 

Viimeinen rivi tuottaa virheen, vaikka olemme ilmoittaneet a Viittaus tyypin Yleinen.

Tulostyyppi on yhteensopiva toisen tyypin kanssa vain jälkeen viitetyyppi on muunnettu nimenomaisesti:

 SuperSuperType sst = ((yleinen) contravariantReference) .luku (); sst = (SuperSuperType) contravariantReference.read (); // vaarallinen vaihtoehto 

Esimerkit edellisistä luetteloista osoittavat, että luku- tai kirjoitusoikeus tyypin muuttujaan parametri käyttäytyy samalla tavalla riippumatta siitä, tapahtuuko menetelmä (luku ja kirjoitus) vai suoraan (esimerkkien tiedot).

Tyyppiparametrin muuttujien lukeminen ja kirjoittaminen

Taulukko 1 osoittaa, että lukeminen Esine muuttuja on aina mahdollista, koska jokainen luokka ja jokerimerkki ovat yhteensopivia Esine. Kirjoittaminen Esine on mahdollista vain sopivan heijastuksen jälkeen asianmukaisen valamisen jälkeen, koska Esine ei ole yhteensopiva jokerimerkin kanssa. Lukeminen ilman heittämistä sopimattomaan muuttujaan on mahdollista kovariaanisen viitteen avulla. Kirjoittaminen on mahdollista kiistanalaisella viitteellä.

Taulukko 1. Tyyppiparametrin muuttujien lukeminen ja kirjoittaminen

käsittelyssä

(syöttö)

lukea

Esine

kirjoittaa

Esine

lukea

supertyyppi

kirjoittaa

supertyyppi

lukea

alatyyppi

kirjoittaa

alatyyppi

Jokerimerkki

?

OK Virhe Heittää Heittää Heittää Heittää

Kovaria

? jatkuu

OK Virhe OK Heittää Heittää Heittää

Suonensisäinen

super

OK Heittää Heittää Heittää Heittää OK

Taulukon 1 rivit viittaavat eräänlainen viiteja sarakkeet tietojen tyyppi olla käytettävissä. Otsikot "supertype" ja "subtype" osoittavat jokerimerkkejä. Merkintä "valettu" tarkoittaa, että viite on valettava. Esimerkki "OK" neljässä viimeisessä sarakkeessa viittaa kovariaation ja ristiriitaisuuden tyypillisiin tapauksiin.

Katso tämän artikkelin lopusta taulukon systemaattinen testausohjelma, jossa on yksityiskohtaiset selitykset.

Objektien luominen

Toisaalta et voi luoda jokerimerkkityyppejä, koska ne ovat abstrakteja. Toisaalta voit luoda vain rajoittamattoman yleismerkkityypin taulukko-objekteja. Et kuitenkaan voi luoda muita yleisiä esimerkkejä.

 Generic [] genericArray = uusi Generic [20]; // tyypin virhe Generic [] wildcardArray = new Generic [20]; // OK genericArray = (Generic []) wildcardArray; // tarkistamaton muunnos genericArray [0] = new Generic (); genericArray [0] = uusi yleinen (); // tyyppi virhe wildcardArray [0] = uusi yleinen (); // OK 

Taulukoiden kovarianssista johtuen jokerimerkkijonotyyppi Yleinen [] on kaikkien ilmentymien taulukotyypin supertyyppi; sen vuoksi yllä olevan koodin viimeisellä rivillä osoittaminen on mahdollista.

Yleisessä luokassa emme voi luoda tyypin parametrin objekteja. Esimerkiksi rakentajan ArrayList toteutus, taulukko-objektin on oltava tyyppiä Esine[] luomisen jälkeen. Voimme sitten muuntaa sen tyyppiparametrin taulukotyypiksi:

 luokka MyArrayList toteuttaa luettelon {private final E [] sisältöä; MyArrayList (int-koko) {content = new E [size]; // type error content = (E []) new Object [size]; // kiertotapa} ...} 

Saat turvallisemman kiertotavan ohittamalla Luokka todellisen tyypin parametrin arvo rakentajalle:

 sisältö = (E []) java.lang.reflect.Array.newInstance(myClass, koko); 

Useita tyypin parametreja

Geneerisellä tyypillä voi olla useampi kuin yksi tyypin parametri. Tyyppiparametrit eivät muuta kovarianssin ja ristiriitaisuuden käyttäytymistä, ja useita tyypin parametreja voi esiintyä yhdessä, kuten alla on esitetty:

 luokan G {} G viite; viite = uusi G (); // ilman varianssiviitettä = uusi G (); // yhdessä- ja ristiriitaisuudella 

Yleinen käyttöliittymä java.util.Kartta käytetään usein esimerkkinä monityyppisille parametreille. Käyttöliittymässä on kaksi tyyppiparametriä, yksi avaimelle ja toinen arvolle. On hyödyllistä liittää objekteja esimerkiksi avaimiin, jotta voimme löytää ne helpommin. Puhelinluettelo on esimerkki a Kartta objekti, joka käyttää useita tyyppisiä parametreja: tilaajan nimi on avain, puhelinnumero on arvo.

Käyttöliittymän toteutus java.util.HashMap on konstruktori mielivaltaisen muuntamiseksi Kartta objekti assosiaatiotaulukkoon:

 julkinen HashMap (kartta m) ... 

Kovarianssin takia parametriobjektin tyypin parametrin ei tarvitse tässä tapauksessa vastata tarkkaa tyypin parametriluokkaa K ja V. Sen sijaan se voidaan mukauttaa kovarianssilla:

 Kartta-asiakkaat; ... yhteystiedot = uusi HashMap (asiakkaat); // kovariantti 

Tässä, Id on supertyyppi Asiakasnumeroja Henkilö on supertyyppi Asiakas.

Menetelmien vaihtelu

Olemme puhuneet tyyppien varianssista; nyt siirrytään hieman helpompaan aiheeseen.