Aina kun sinulla on kokoelma asioita, tarvitset jonkin mekanismin, jotta voit siirtyä järjestelmällisesti kyseisen kokoelman kohteisiin. Harkitse päivittäisenä esimerkkinä television kaukosäädintä, jonka avulla voimme toistaa eri televisiokanavien kautta. Samoin ohjelmointimaailmassa tarvitsemme mekanismin systemaattiseen iterointiin ohjelmistoobjektien kokoelman kautta. Java sisältää useita iterointimekanismeja, mukaan lukien indeksi (taulukon iteroimiseksi), kohdistin (tietokantakyselyn tulosten iteroimiseksi), luettelointi (Java: n varhaisissa versioissa) ja iteraattori (uudemmissa Java-versioissa).
Iterator-kuvio
An iteraattori on mekanismi, joka sallii kokoelman kaikkien elementtien pääsyn peräkkäin, jokaiselle elementille suoritetaan jokin toiminto. Pohjimmiltaan iteraattori tarjoaa keinon "silmukoida" kapseloidun esineiden kokoelman yli. Esimerkkejä iteraattorien käytöstä ovat
- Käy jokaisessa hakemiston tiedostossa (alias kansio) ja näytä sen nimi.
- Käy jokaisessa kaavion solmussa ja selvitä, onko se tavoitettavissa tietystä solmusta.
- Käy jokaisen jonossa olevan asiakkaan luona (esimerkiksi simuloi linjaa pankissa) ja selvitä, kuinka kauan hän on odottanut.
- Käy kääntäjän abstraktin syntaksipuun jokaisessa solmussa (jonka jäsennin tuottaa) ja suorita semanttinen tarkistus tai koodin luonti. (Voit käyttää myös Visitor-mallia tässä yhteydessä.)
Tietyt periaatteet koskevat iteraattoreiden käyttöä: Yleensä sinun pitäisi pystyä olemaan käynnissä useita läpikulkuja samanaikaisesti; toisin sanoen iteraattorin tulisi sallia käsite sisäkkäinen silmukointi. Iteraattorin tulisi olla myös tuhoamaton siinä mielessä, että iterointitoimen ei pitäisi itsessään muuttaa kokoelmaa. Tietysti kokoelman elementeille suoritettava toiminto voi mahdollisesti muuttaa joitain elementtejä. Iteraattori saattaa myös olla mahdollista tukea elementin poistamista kokoelmasta tai uuden elementin lisäämistä kokoelman tiettyyn kohtaan, mutta tällaisten muutosten tulisi olla nimenomaisia ohjelmassa eivätkä iteraation sivutuote. Joissakin tapauksissa tarvitset myös iteraattoreita, joilla on erilaiset läpimenomenetelmät; esimerkiksi puun ennakkotilaus ja jälkitilaus tai kaavion syvyys ensin ja leveys ensimmäinen.
Toistuvat monimutkaiset tietorakenteet
Olen ensin oppinut ohjelmoimaan FORTRANin varhaisessa versiossa, jossa ainoa tietojen jäsentämiskyky oli matriisi. Opin nopeasti iteroimaan matriisin yli indeksin ja DO-silmukan avulla. Sieltä se oli vain lyhyt henkinen ajatus ajatuksesta käyttää yhteistä hakemistoa useampaan ryhmään tietueiden simuloimiseksi. Useimmilla ohjelmointikielillä on samanlaiset ominaisuudet kuin matriiseilla, ja ne tukevat suoraa silmukointia matriisien yli. Mutta nykyaikaiset ohjelmointikielet tukevat myös monimutkaisempia tietorakenteita, kuten luetteloita, sarjoja, karttoja ja puita, joissa ominaisuudet asetetaan saataville julkisilla menetelmillä, mutta sisäiset yksityiskohdat ovat piilossa luokan yksityisissä osissa. Ohjelmoijien on kyettävä kulkemaan näiden tietorakenteiden elementit paljastamatta sisäistä rakennettaan, mikä on iteraattoreiden tarkoitus.
Iteraattorit ja neljän ryhmän muotoilu
Neljän jengin (katso alla) mukaan Iteraattorin suunnittelukuvio on käyttäytymismalli, jonka pääidea on "ottaa vastuu pääsystä ja liikkumisesta pois luettelosta [toim. ajatella kokoelma] -objekti ja laita se iteraattoriobjektiin. "Tämä artikkeli ei koske niin paljon iteraattorimallia kuin iteraattorien käyttöä käytännössä. Kuvion kattaminen kokonaan edellyttäisi keskustelua iteraattorin suunnittelusta, osallistujat ( kohteet ja luokat) suunnittelussa, mahdollisissa vaihtoehtoisissa malleissa ja erilaisten suunnitteluvaihtoehtojen kompromisseissa. Keskityisin mieluummin iteraattorien käytäntöön, mutta osoitan muutamaan resurssiin iteraattorimallin ja suunnittelumallien tutkimiseen yleisesti:
- Suunnittelumallit: Uudelleenkäytettävien olio-ohjelmistojen elementit (Addison-Wesley Professional, 1994), kirjoittaneet Erich Gamma, Richard Helm, Ralph Johnson ja John Vlissides (tunnetaan myös nimellä Neljän jengi tai yksinkertaisesti GoF) on lopullinen resurssi suunnittelumallien oppimiseen. Vaikka kirja julkaistiin ensimmäisen kerran vuonna 1994, se on edelleen klassikko, mistä on osoituksena se, että painoksia on ollut yli 40.
- Mary Tarlandin Baltimoren piirikunnan yliopiston luennoitsijalla Bob Tarrilla on erinomainen diasarja suunnittelumallien kurssilleen, mukaan lukien esittely Iterator-malliin.
- David Gearyn JavaWorld-sarja Java-suunnittelumallit esittelee monia Gang of Four -malleja, mukaan lukien Singleton-, Observer- ja Composite-mallit. Myös JavaWorldissa Jeff Friesenin uudempi kolmiosainen yleiskatsaus suunnittelumalleista sisältää oppaan GoF-kuvioista.
Aktiiviset iteraattorit vs. passiiviset iteraattorit
Iteraattorin toteuttamiseen on kaksi yleistä lähestymistapaa riippuen siitä, kuka hallitsee iteraatiota. Sillä aktiivinen iteraattori (tunnetaan myös nimenomainen iteraattori tai ulkoinen iteraattori), asiakas ohjaa iteraatiota siinä mielessä, että asiakas luo iteraattorin, kertoo sille, milloin edetä seuraavaan elementtiin, testaa, onko jokaisessa elementissä käynyt, ja niin edelleen. Tämä lähestymistapa on yleinen kielillä, kuten C ++, ja juuri tämä lähestymistapa saa eniten huomiota GoF-kirjassa. Vaikka Java-iteraattorit ovat olleet eri muodoissa, aktiivisen iteraattorin käyttö oli pohjimmiltaan ainoa toteuttamiskelpoinen vaihtoehto ennen Java 8: ta.
A passiivinen iteraattori (tunnetaan myös nimellä implisiittinen iteraattori, sisäinen iteraattoritai soittopyynnön iteraattori), iteraattori itse ohjaa iteraatiota. Asiakas sanoo lähinnä iteraattorille: "Suorita tämä toiminto kokoelman elementeille". Tämä lähestymistapa on yleinen LISP: n kaltaisilla kielillä, jotka tarjoavat nimettömiä toimintoja tai sulkemisia. Java 8: n julkaisemisen myötä tämä lähestymistapa iterointiin on nyt järkevä vaihtoehto Java-ohjelmoijille.
Java 8 -nimitysjärjestelmät
Vaikka Java ei ole yhtä huono kuin Windows (NT, 2000, XP, VISTA, 7, 8, ...), Javan versiohistoria sisältää useita nimeämissuunnitelmia. Pitäisikö meidän aluksi viitata Java-standardiversioon "JDK", "J2SE" tai "Java SE"? Java-versionumerot alkoivat melko suoraviivaisina - 1.0, 1.1 jne. - mutta kaikki muuttui versiolla 1.5, joka oli tuotenimi Java (tai JDK) 5. Viitatessani Java-varhaisiin versioihin käytän lauseita kuten "Java 1.0" tai "Java" 1.1, "mutta Java-viidennen version jälkeen käytän lauseita kuten" Java 5 "tai" Java 8. "
Tarvitsen esimerkin kokoelmasta ja jotain, joka on tehtävä sen elementeillä havainnollistamaan Java: n iteraation eri lähestymistapoja. Tämän artikkelin alkuosassa käytän joukkoa merkkijonoja, jotka edustavat asioiden nimiä. Tulostan vain jokaiselle kokoelman nimelle sen arvon vakiotulosteeseen. Nämä perusideat laajennetaan helposti monimutkaisempien esineiden (kuten työntekijöiden) kokoelmiin ja missä jokaisen objektin käsittely on hieman enemmän mukana (kuten antaa jokaiselle erittäin arvostetulle työntekijälle 4,5 prosentin korotus).
Muut iteraation muodot Java 8: ssa
Keskityn kokoelmien iterointiin, mutta Javalla on muita, erikoistuneempia iterointimuotoja. Voit esimerkiksi käyttää JDBC: tä ResultSet
iteroida SELECT-kyselystä relaatiotietokantaan palautettujen rivien yli tai käyttää a Skanneri
toistaa tulolähteen yli.
Toistaminen Enumeration-luokan kanssa
Java 1.0: ssa ja 1.1: ssä kaksi ensisijaista kokoeluluokkaa olivat Vektori
ja Hashtable
, ja Iterator-suunnittelumalli toteutettiin nimeltään luokassa Luettelointi
. Jälkikäteen tämä oli luokan huono nimi. Älä sekoita luokkaa Luettelointi
käsitteen kanssa enum-tyypit, joka ilmestyi vasta Java 5. Tänään molemmat Vektori
ja Hashtable
ovat yleisiä luokkia, mutta tuolloin yleiset eivät olleet osa Java-kieltä. Koodi merkkijonovektorin käsittelemiseksi Luettelointi
näyttää jotain listalta 1.
Listaus 1. Laskennan käyttäminen iteroimaan merkkijonevektorin yli
Vektorien nimet = uusi vektori (); // ... lisää joitain nimiä kokoelmaan Luettelo e = nimet.elementit (); while (e.hasMoreElements ()) {String name = (String) e.nextElement (); System.out.println (nimi); }
Iteraatio Iterator-luokan kanssa
Java 1.2 esitteli kokoelmaluokat, jotka me kaikki tunnemme ja rakastamme, ja Iterator-suunnittelumalli toteutettiin luokassa, joka nimettiin asianmukaisesti Iteraattori
. Koska Java 1.2: ssa ei vielä ollut geneerisiä aineita, Iteraattori
oli edelleen tarpeen. Java-versioissa 1.2 - 1.4 iteroituminen merkkijonoluettelosta saattaa muistuttaa listaa 2.
Listaus 2. Iteraattorin käyttäminen iteroitumaan merkkijonoluettelosta
Luettelonimet = new LinkedList (); // ... lisää joitain nimiä kokoelmaan Iterator i = names.iterator (); while (i.hasNext ()) {String name = (String) i.next (); System.out.println (nimi); }
Kertaus geneerisillä tuotteilla ja parannettu for-loop
Java 5 antoi meille käyttöliittymän geneerisiä aineita Iterable
ja parannettu for-loop. Parannettu for-loop on yksi suosikkini pienistä lisäyksistä Javalaan. Iteraattorin luominen ja kutsuu sitä hasNext ()
ja Seuraava()
menetelmiä ei ole nimenomaisesti ilmaistu koodissa, mutta ne tapahtuvat silti kulissien takana. Joten vaikka koodi on pienempi, käytämme edelleen aktiivista iteraattoria. Java 5: n avulla esimerkkimme näyttäisi siltä, kuin mitä näet Listing 3: ssa.
Listaus 3. Geneeristen ominaisuuksien ja parannetun for-loopin käyttäminen iteroitumaan merkkijonoluettelosta
Luettelonimet = new LinkedList (); // ... lisää joitain nimiä kokoelmaan (String name: names) System.out.println (name);
Java 7 antoi meille timanttioperaattorin, mikä vähentää geneeristen tuotteiden sujuvuutta. Menneet ovat päivät, jolloin jouduttiin toistamaan tyyppi, jota käytettiin yleisluokan ilmentämiseksi sen jälkeen, kun Uusi
operaattori! Java 7: ssä voimme yksinkertaistaa yllä olevan luettelon 3 ensimmäisen rivin seuraavasti:
Luettelonimet = new LinkedList ();
Lievä pilkkaus geneerisiä lääkkeitä vastaan
Ohjelmointikielen suunnittelu sisältää kompromisseja kieliominaisuuksien etujen ja kielen syntaksin ja semantiikan monimutkaisuuden välillä. Geneeristen lääkkeiden osalta en ole vakuuttunut siitä, että hyödyt ylittävät monimutkaisuuden. Generics ratkaisi ongelman, jota minulla ei ollut Java-sovelluksen kanssa. Olen yleensä samaa mieltä Ken Arnoldin mielipiteen kanssa, kun hän toteaa: "Yleisvalmisteet ovat virhe. Tämä ei ole teknisiin erimielisyyksiin perustuva ongelma. Se on perustavanlaatuinen kielisuunnitteluongelma [...] Javan monimutkaisuus on turvattu siihen, mikä minusta tuntuu suhteellisen pieni etu. "
Onneksi, vaikka yleisluokkien suunnittelu ja toteutus voi joskus olla liian monimutkaista, olen huomannut, että yleisluokkien käyttö käytännössä on yleensä yksinkertaista.
Toisto forEach () -menetelmällä
Ennen kuin tarkastellaan Java 8: n iterointiominaisuuksia, pohditaan, mikä on vialla edellisissä luetteloissa näytetyssä koodissa - mikä ei ole mitään oikeaa. Tällä hetkellä käytössä olevissa sovelluksissa on miljoonia Java-koodiriviä, jotka käyttävät aktiivisia iteraattoreita, jotka ovat samanlaisia kuin luetteloissani. Java 8 tarjoaa yksinkertaisesti lisäominaisuuksia ja uusia tapoja suorittaa iterointi. Joissakin tilanteissa uudet tavat voivat olla parempia.
Java 8: n tärkeimmät uudet ominaisuudet keskittyvät lambda-lausekkeisiin sekä niihin liittyviin ominaisuuksiin, kuten virrat, menetelmäviitteet ja toiminnalliset käyttöliittymät. Nämä Java 8: n uudet ominaisuudet antavat meille mahdollisuuden harkita vakavasti passiivisten iteraattorien käyttöä perinteisempien aktiivisten iteraattorien sijaan. Erityisesti Iterable
käyttöliittymä tarjoaa passiivisen iteraattorin oletusmenetelmän muodossa jokaiselle()
.
A oletustapa, toinen uusi ominaisuus Java 8: ssa, on menetelmä käyttöliittymässä oletustoteutuksella. Tässä tapauksessa jokaiselle()
menetelmä toteutetaan tosiasiallisesti käyttämällä aktiivista iteraattoria samalla tavalla kuin mitä näet luettelossa 3.
Kokoelmaluokat, jotka toteutetaan Iterable
(esimerkiksi kaikissa luettelo- ja joukko-luokissa) on nyt a jokaiselle()
menetelmä. Tämä menetelmä ottaa yhden parametrin, joka on toiminnallinen rajapinta. Siksi varsinainen parametri välitettiin jokaiselle()
menetelmä on ehdokas lambda-ilmentymälle. Käyttämällä Java 8: n ominaisuuksia juokseva esimerkkimme kehittyy Listing 4: ssä esitettyyn muotoon.
Listaus 4. Iteraatio Java 8: ssa forEach () -menetelmällä
Luettelonimet = new LinkedList (); // ... lisää joitain nimiä kokoelmien nimiin.forEach (nimi -> System.out.println (nimi));
Huomaa ero luettelon 4 passiivisen iteraattorin ja kolmen edellisen luettelon aktiivisen iteraattorin välillä. Ensimmäisessä kolmessa luettelossa silmukkarakenne ohjaa iterointia, ja jokaisen silmukan läpi kulkevan objektin noudetaan luettelosta ja tulostetaan sitten. Luettelossa 4 ei ole nimenomaista silmukkaa. Kerromme yksinkertaisesti jokaiselle()
menetelmä mitä tehdä luettelossa oleville kohteille - tässä tapauksessa yksinkertaisesti tulostamme objektin. Iteraation hallinta on jokaiselle()
menetelmä.
Toisto Java-virroilla
Harkitaan nyt jotain tekemistä hieman enemmän kuin pelkkä luettelossa olevien nimien tulostaminen. Oletetaan esimerkiksi, että haluamme laskea kirjaimella alkavien nimien määrän A. Voisimme toteuttaa monimutkaisemman logiikan osana lambda-lauseketta tai voisimme käyttää Java 8: n uutta Stream API: ta. Otetaan jälkimmäinen lähestymistapa.