Programovanie

Iterácia zbierok v Jave

Kedykoľvek budete mať zbierku vecí, budete potrebovať nejaký mechanizmus na systematické prechádzanie cez položky v tejto zbierke. Ako každodenný príklad považujme televízne diaľkové ovládanie, ktoré nám umožňuje iterovať cez rôzne televízne kanály. Podobne vo svete programovania potrebujeme mechanizmus na systematické iterovanie prostredníctvom zbierky softvérových objektov. Java obsahuje rôzne mechanizmy pre iteráciu, vrátane index (pre iteráciu cez pole), kurzor (na iteráciu nad výsledkami databázového dotazu), vyčíslenie (v skorších verziách Java) a iterátor (v novších verziách Java).

Iterátorový vzor

An iterátor je mechanizmus, ktorý umožňuje postupný prístup ku všetkým prvkom kolekcie, pričom s každým prvkom sa vykonáva určitá operácia. Iterátor v podstate poskytuje prostriedky na „opakovanie“ nad zapuzdrenou zbierkou objektov. Medzi príklady použitia iterátorov patria

  • Navštívte každý súbor v adresári (alias priečinok) a zobraziť jeho názov.
  • Navštívte každý uzol v grafe a zistite, či je z daného uzla dosiahnuteľný.
  • Navštívte každého zákazníka v rade (napríklad simuláciou linky v banke) a zistite, ako dlho čakal.
  • Navštívte každý uzol v abstraktnom strome syntaxe kompilátora (ktorý produkuje syntaktický analyzátor) a vykonajte sémantickú kontrolu alebo generovanie kódu. (V tejto súvislosti môžete použiť aj vzor Návštevník.)

Pre použitie iterátorov platia určité zásady: Spravidla by malo byť možné mať rozpracovaných niekoľko prechodov súčasne; to znamená, že iterátor by mal umožniť koncept vnorené opakovanie. Iterátor by mal byť tiež nedeštruktívny v tom zmysle, že proces iterácie by sám osebe nemal meniť kolekciu. Samozrejme, operácia vykonávaná s prvkami v kolekcii by mohla pravdepodobne zmeniť niektoré z prvkov. Môže byť tiež možné, aby iterátor podporoval odstránenie prvku z kolekcie alebo vloženie nového prvku do konkrétneho bodu v kolekcii, ale takéto zmeny by mali byť v rámci programu explicitné a nemali by byť vedľajším produktom iterácie. V niektorých prípadoch budete tiež musieť mať iterátory s rôznymi metódami prechodu; napríklad prechod stromu predobjednávkou a po nej, alebo priechod hĺbky ako prvý a po celej šírke grafu.

Iterácia zložitých dátových štruktúr

Prvýkrát som sa naučil programovať v ranej verzii FORTRANU, kde jedinou možnosťou štruktúrovania údajov bolo pole. Rýchlo som sa naučil, ako iterovať cez pole pomocou indexu a DO-slučky. Odtiaľ bol už len krátky mentálny skok k myšlienke použitia spoločného indexu do viacerých polí na simuláciu množstva záznamov. Väčšina programovacích jazykov má podobné vlastnosti ako polia a podporuje priame cyklovanie cez polia. Ale moderné programovacie jazyky tiež podporujú zložitejšie dátové štruktúry, ako sú zoznamy, množiny, mapy a stromy, kde sú možnosti sprístupnené verejnými metódami, ale interné podrobnosti sú skryté v súkromných častiach triedy. Programátori musia byť schopní prechádzať prvkami týchto dátových štruktúr bez toho, aby odhalili ich vnútornú štruktúru, čo je účelom iterátorov.

Iterátory a gang štyroch návrhových vzorov

Podľa Gangu štyroch (pozri nižšie) sa Návrhový vzor iterátora je vzor správania, ktorého kľúčovou myšlienkou je „prevziať zodpovednosť za prístup a prechod zo zoznamu [vyd. think collection] objekt a vložte ho do iteračného objektu. "Tento článok nie je tak o iterátorskom vzore, ako o tom, ako sa iterátory používajú v praxi. Úplné pokrytie tohto vzoru by vyžadovalo diskusiu o tom, ako by bol iterátor navrhnutý, účastníci ( objekty a triedy) v dizajne, možné alternatívne návrhy a kompromisy rôznych alternatív návrhu. Radšej by som sa zameral na to, ako sa v praxi používajú iterátory, ale ukážem vám niekoľko zdrojov na preskúmanie vzoru Iterator a vzorov návrhu všeobecne:

  • Dizajnové vzory: Prvky opakovane použiteľného objektovo orientovaného softvéru (Addison-Wesley Professional, 1994), autormi Erich Gamma, Richard Helm, Ralph Johnson a John Vlissides (tiež známy ako Gang štyroch alebo jednoducho GoF) je definitívnym zdrojom pre učenie sa návrhových vzorov. Aj keď kniha vyšla prvýkrát v roku 1994, zostáva klasickou, o čom svedčí skutočnosť, že tu bolo viac ako 40 výtlačkov.
  • Bob Tarr, prednášajúci na Marylandskej univerzite v okrese Baltimore, má vynikajúci súbor snímok pre svoj kurz návrhových vzorov, vrátane jeho uvedenia do vzoru Iterator.
  • Séria JavaWorld od Davida Gearyho Dizajnové vzory Java predstavuje veľa dizajnových vzorov Gang of Four vrátane vzorov Singleton, Observer a Composite. Aj na serveri JavaWorld obsahuje najnovší trojdielny prehľad návrhových vzorov Jeffa Friesena sprievodcu vzormi GoF.

Aktívne iterátory vs pasívne iterátory

Existujú dva všeobecné prístupy k implementácii iterátora v závislosti od toho, kto riadi iteráciu. Za aktívny iterátor (taktiež známy ako explicitný iterátor alebo externý iterátor), klient riadi iteráciu v tom zmysle, že si klient vytvorí iterátor, povie mu, kedy má prejsť na ďalší prvok, otestuje, či bol každý prvok navštívený atď. Tento prístup je bežný v jazykoch ako C ++ a práve tomuto prístupu sa v knihe GoF venuje najväčšia pozornosť. Aj keď iterátory v Jave mali rôzne podoby, použitie aktívneho iterátora bolo v podstate jedinou životaschopnou voľbou pred Java 8.

Pre pasívny iterátor (tiež známy ako implicitný iterátor, interný iterátoralebo iterátor spätného volania), iterátor sám riadi iteráciu. Klient v podstate hovorí iterátorovi: „vykonajte túto operáciu s prvkami v kolekcii.“ Tento prístup je bežný v jazykoch, ako je LISP, ktoré poskytujú anonymné funkcie alebo uzávierky. S vydaním Java 8 je tento prístup k iterácii rozumnou alternatívou pre programátorov Java.

Názvové schémy Java 8

Aj keď to nie je také zlé ako Windows (NT, 2000, XP, VISTA, 7, 8, ...), história verzií Java obsahuje niekoľko pomenovacích schém. Na začiatok by sme mali štandardné vydanie Java označovať ako „JDK“, „J2SE“ alebo „Java SE“? Čísla verzií Javy začínali celkom jednoducho - 1.0, 1.1 atď. - ale všetko sa zmenilo vo verzii 1.5, ktorá bola označená ako Java (alebo JDK) 5. Keď hovorím o skorších verziách Javy, používam frázy ako „Java 1.0“ alebo „Java 1.1, „ale po piatej verzii Java používam frázy ako„ Java 5 “alebo„ Java 8. “

Na ilustráciu rôznych prístupov k iterácii v prostredí Java potrebujem príklad zbierky a niečo, čo je potrebné urobiť s jej prvkami. Na úvodnú časť tohto článku použijem zbierku reťazcov predstavujúcich názvy vecí. Pre každé meno v zbierke jednoducho vytlačím jeho hodnotu na štandardný výstup. Tieto základné myšlienky sa dajú ľahko rozšíriť na zbierky komplikovanejších objektov (napríklad zamestnancov) a kde je spracovanie každého objektu trochu viac zapojené (napríklad zvýšenie každého zamestnanca s vysokým hodnotením o 4,5 percenta).

Ďalšie formy iterácie v prostredí Java 8

Zameriavam sa na iteráciu zbierok, ale v Jave existujú aj iné, špecializovanejšie formy iterácie. Môžete napríklad použiť JDBC Sada výsledkov iterovať nad riadkami vrátenými z dotazu SELECT do relačnej databázy alebo použiť a Skener iterovať cez vstupný zdroj.

Iterácia s triedou Enumeration

V prostredí Java 1.0 a 1.1 boli dve primárne triedy zbierky Vektor a Hashtable, a návrhový vzor Iterátora bol implementovaný v triede s názvom Vymenovanie. Spätne to bolo pre triedu zlé meno. Nezamieňajte triedu Vymenovanie s konceptom typy enum, ktoré sa objavili až v Jave 5. Dnes obe Vektor a Hashtable sú druhové triedy, ale vtedy generické systémy neboli súčasťou jazyka Java. Kód na spracovanie vektora reťazcov pomocou Vymenovanie bude vyzerať niečo ako výpis č. 1.

Výpis 1. Pomocou výčtu opakujeme vektor reťazcov

 Názvy vektorov = new Vector (); // ... pridať nejaké mená do zbierky Enumeration e = names.elements (); while (e.hasMoreElements ()) {Názov reťazca = (Reťazec) e.nextElement (); System.out.println (názov); } 

Iterácia s triedou Iterator

Java 1.2 predstavila triedy kolekcie, ktoré všetci poznáme a milujeme, a návrhový vzor Iterator bol implementovaný v triede s príslušným menom Iterátor. Pretože sme ešte nemali generiku v Jave 1.2, casting objektu vráteného z Iterátor bolo stále potrebné. V prípade verzií Java verzie 1.2 až 1.4 sa opakovanie zoznamu reťazcov môže podobať záznamu č. 2.

Výpis 2. Použitie iterátora na iteráciu zoznamu reťazcov

 Názvy zoznamu = new LinkedList (); // ... pridať nejaké mená do kolekcie Iterator i = names.iterator (); while (i.hasNext ()) {Názov reťazca = (Reťazec) i.next (); System.out.println (názov); } 

Iterácia s generikami a vylepšená slučka for-loop

Java 5 nám dala generické rozhranie Iterablea vylepšená slučka for-loop. Vylepšená slučka for-loop je jedným z mojich najobľúbenejších malých doplnkov Java. Vytvorenie iterátora a výzvy k jeho hasNext () a Ďalšie() metódy nie sú výslovne vyjadrené v kódexe, ale stále prebiehajú v zákulisí. Aj keď je teda kód kompaktnejší, stále používame aktívny iterátor. V prípade použitia Java 5 by náš príklad vyzeral asi ako to, čo vidíte v zozname 3.

Zoznam 3. Používanie generík a vylepšenej slučky for-loop na iteráciu zoznamu reťazcov

 Názvy zoznamu = new LinkedList (); // ... pridať nejaké mená do kolekcie pre (Názov reťazca: názvy) System.out.println (meno); 

Java 7 nám poskytla operátora diamantov, ktorý znižuje výrečnosť generík. Časy, keď bolo treba opakovať typ, ktorý sa použil na vytvorenie inštancie generickej triedy po vyvolaní kódu, boli preč Nový operátor! V prostredí Java 7 by sme mohli prvý riadok v zozname 3 vyššie zjednodušiť na nasledujúce:

 Názvy zoznamu = new LinkedList (); 

Mierny chrapot proti generikom

Dizajn programovacieho jazyka zahŕňa kompromisy medzi výhodami jazykových funkcií oproti zložitosti, ktorú kladú na syntax a sémantiku jazyka. Pokiaľ ide o generiká, nie som presvedčený, že výhody prevažujú nad zložitosťou. Generics vyriešil problém, ktorý som s Javou nemal. Všeobecne súhlasím s názorom Kena Arnolda, keď tvrdí: "Generické lieky sú chybou. Toto nie je problém založený na technických nezhodách. Je to zásadný problém s jazykovým dizajnom [...] Zložitosť Javy bola prepĺňaná tým, čo sa mi zdá relatívne malá výhoda. ““

Našťastie, zatiaľ čo navrhovanie a implementácia generických tried môže byť niekedy príliš komplikované, zistil som, že použitie generických tried v praxi je zvyčajne jednoduché.

Iterácia metódou forEach ()

Predtým, ako sa pustíme do iteračných funkcií Java 8, poďme sa zamyslieť nad tým, čo nie je v poriadku s kódom zobrazeným v predchádzajúcich zoznamoch, čo je vlastne nič. V súčasnosti nasadených aplikáciách, ktoré používajú aktívne iterátory podobné tým, ktoré sú uvedené v mojich zoznamoch, sú milióny riadkov kódu Java. Java 8 jednoducho poskytuje ďalšie funkcie a nové spôsoby vykonávania iterácie. Pre niektoré scenáre môžu byť nové spôsoby lepšie.

Hlavné nové funkcie v prostredí Java 8 sa zameriavajú na výrazy lambda spolu so súvisiacimi funkciami, ako sú streamy, odkazy na metódy a funkčné rozhrania. Tieto nové funkcie v prostredí Java 8 nám umožňujú vážne zvážiť použitie pasívnych iterátorov namiesto bežných konvenčných iterátorov. Najmä Iterable rozhranie poskytuje pasívny iterátor v podobe predvolenej metódy s názvom pre každý().

A predvolená metóda, ďalšia nová funkcia v prostredí Java 8, je metóda v rozhraní so štandardnou implementáciou. V takom prípade pre každý() metóda sa v skutočnosti implementuje pomocou aktívneho iterátora podobným spôsobom, aký ste videli v zozname 3.

Triedy zbierok, ktoré sa implementujú Iterable (napríklad všetky triedy zoznamov a skupín) majú teraz a pre každý() metóda. Táto metóda trvá jeden parameter, ktorým je funkčné rozhranie. Skutočný parameter sa preto odovzdal pre každý() metóda je kandidátom na výraz lambda. Pomocou funkcií Java 8 by sa náš bežiaci príklad vyvinul do formy zobrazenej v zozname 4.

Zoznam 4. Iterácia v prostredí Java 8 pomocou metódy forEach ()

 Názvy zoznamu = new LinkedList (); // ... pridať nejaké názvy do zbierky names.forEach (name -> System.out.println (name)); 

Všimnite si rozdiel medzi pasívnym iterátorom v zozname 4 a aktívnym iterátorom v predchádzajúcich troch zoznamoch. V prvých troch výpisoch riadi iteráciu štruktúra slučky a počas každého prechodu slučkou sa zo zoznamu získa objekt a potom sa vytlačí. V zozname 4 neexistuje žiadna explicitná slučka. Jednoducho to povieme pre každý() metóda, čo robiť s objektmi v zozname - v takom prípade jednoducho vytlačíme objekt. Kontrola iterácie spočíva v pre každý() metóda.

Iterácia s tokmi Java

Teraz zvážme, že urobíme niečo trochu viac ako len vytlačenie mien z nášho zoznamu. Predpokladajme napríklad, že chceme spočítať počet mien, ktoré sa začínajú písmenom A. Mohli by sme implementovať komplikovanejšiu logiku ako súčasť výrazu lambda, alebo by sme mohli použiť nové rozhranie Stream API v Java 8. Zoberme si druhý prístup.

$config[zx-auto] not found$config[zx-overlay] not found