Ohjelmointi

Kerääntyy Java-kokoelmien yli

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 Iterableja 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.