Programavimas

Kartojasi per „Java“ kolekcijas

Bet kada turėsite daiktų kolekciją, jums reikės mechanizmo, kad sistemingai pereitumėte per tos kolekcijos daiktus. Kaip kasdienį pavyzdį laikykite televizoriaus nuotolinio valdymo pultą, kuris leidžia mums kartoti įvairius televizijos kanalus. Panašiai ir programavimo pasaulyje mums reikia mechanizmo, kad sistemingai kartotume programinės įrangos objektų rinkinį. Java apima įvairius iteracijos mechanizmus, įskaitant indeksas (už masyvo kartojimą), žymeklis (norint pakartoti duomenų bazės užklausos rezultatus), surašymas (ankstyvosiose „Java“ versijose) ir iteratorius (naujesnėse „Java“ versijose).

„Iterator“ modelis

An iteratorius yra mechanizmas, leidžiantis nuosekliai pasiekti visus kolekcijos elementus, atliekant tam tikrą operaciją su kiekvienu elementu. Iš esmės iteratorius suteikia galimybę „susieti“ per uždarą objektų rinkinį. Iteratorių naudojimo pavyzdžiai:

  • Aplankykite kiekvieną kataloge esantį failą (dar žinomas aplanką) ir parodykite jo pavadinimą.
  • Apsilankykite kiekviename diagramoje esančiame mazge ir nustatykite, ar jis pasiekiamas iš nurodyto mazgo.
  • Aplankykite kiekvieną eilėje esantį klientą (pavyzdžiui, imituodami eilutę banke) ir sužinokite, kiek laiko jis laukė.
  • Apsilankykite kiekviename kompiliatoriaus abstrakčios sintaksės medžio mazge (kurį sukuria analizatorius) ir atlikite semantinį tikrinimą arba kodų generavimą. (Šiame kontekste taip pat galite naudoti „Visitor“ modelį.)

Tam tikri iteratorių naudojimo principai galioja: Apskritai turėtumėte turėti galimybę kelis kartus pereiti vienu metu; tai iteratorius turėtų leisti sampratą įdėtos kilpos. Kartotojas taip pat neturėtų būti niokojantis ta prasme, kad kartojimo veiksmas pats savaime neturėtų pakeisti rinkinio. Žinoma, kolekcijos elementams atliekama operacija gali pakeisti kai kuriuos elementus. Taip pat gali būti, kad iteratorius gali palaikyti elemento pašalinimą iš kolekcijos arba naujo elemento įterpimą tam tikrame kolekcijos taške, tačiau tokie pakeitimai turėtų būti aiškūs programoje, o ne iteracijos šalutinis produktas. Kai kuriais atvejais jums taip pat reikės iteratorių su skirtingais perėjimo būdais; pvz., išankstinis ir vėlesnis medžio perėjimas arba grafiko perkėlimas į gylį ir į plotį.

Kartojamos sudėtingos duomenų struktūros

Aš pirmą kartą išmokau programuoti ankstesnėje FORTRAN versijoje, kur vienintelė duomenų struktūrizavimo galimybė buvo masyvas. Aš greitai sužinojau, kaip kartoti masyvą, naudojant indeksą ir DO kilpą. Iš ten tai buvo tik trumpas protinis šuolis į idėją naudoti bendrą indeksą į kelis masyvus, kad imituotų įrašų masyvą. Daugumos programavimo kalbų funkcijos yra panašios į masyvus, ir jos palaiko paprastą kilpą per masyvus. Tačiau šiuolaikinės programavimo kalbos taip pat palaiko sudėtingesnes duomenų struktūras, tokias kaip sąrašai, rinkiniai, žemėlapiai ir medžiai, kur galimybės yra prieinamos viešais metodais, tačiau vidinė informacija yra paslėpta privačiose klasės dalyse. Programuotojai turi sugebėti pereiti šių duomenų struktūrų elementus, neatskleisdami jų vidinės struktūros, o tai yra iteratorių paskirtis.

Iteratoriai ir keturių dizaino modelių gauja

Pasak Keturių gaujos (žr. Toliau), Iteratoriaus dizaino modelis yra elgesio modelis, kurio pagrindinė idėja yra „prisiimti atsakomybę už prieigą ir perėjimą iš sąrašo [red. manau kolekcija] objektą ir įdėkite jį į iteratoriaus objektą. "Šis straipsnis yra ne tiek apie„ Iterator “modelį, kiek apie tai, kaip praktikoje naudojami iteratoriai. Norint visiškai padengti modelį, reikės aptarti, kaip bus sukurtas iteratorius, dalyviai ( objektai ir klasės), galimus alternatyvius dizainus ir skirtingų dizaino alternatyvų kompromisus. Aš norėčiau sutelkti dėmesį į tai, kaip praktikoje naudojami iteratoriai, bet aš atkreipsiu dėmesį į keletą šaltinių, skirtų „Iterator“ modeliui ir dizaino modeliams tirti paprastai:

  • Dizaino modeliai: daugkartinio naudojimo objektų programinės įrangos elementai (Addison-Wesley Professional, 1994), parašyti Ericho Gammos, Richardo Helmo, Ralpho Johnsono ir Johno Vlissideso (dar vadinamo „Keturių gauja“ arba tiesiog „GoF“), yra galutinis šaltinis, norint sužinoti apie dizaino modelius. Nors knyga pirmą kartą buvo išleista 1994 m., Ji išlieka klasika, tai įrodo faktas, kad buvo daugiau nei 40 spaudinių.
  • Merilendo universiteto Baltimorės apygardos dėstytojas Bobas Tarras turi puikų skaidrių rinkinį, skirtą savo kurso apie dizaino modelius, įskaitant įvadą į „Iterator“ modelį.
  • Davido Geary „JavaWorld“ serija „Java“ dizaino modeliai pristato daugelį „Gang of Four“ dizaino modelių, įskaitant „Singleton“, „Observer“ ir „Composite“ modelius. Be to, „JavaWorld“, naujausioje Jeffo Frieseno trijų dalių dizaino modelių apžvalgoje pateikiamas „GoF“ modelių vadovas.

Aktyvūs kartotojai ir pasyvūs kartotojai

Yra du bendri iteratoriaus įgyvendinimo būdai, atsižvelgiant į tai, kas kontroliuoja iteraciją. Tam, kad aktyvus iteratorius (taip pat žinomas kaip aiškus iteratorius arba išorinis iteratorius), klientas kontroliuoja iteraciją ta prasme, kad klientas sukuria iteratorių, nurodo, kada reikia pereiti prie kito elemento, testuoja, ar visi elementai nebuvo aplankyti ir pan. Šis požiūris yra būdingas tokioms kalboms kaip C ++, ir būtent GoF knygoje jam skiriamas didžiausias dėmesys. Nors „Java“ iteratoriai buvo įvairių formų, aktyviojo iteratoriaus naudojimas iš esmės buvo vienintelė perspektyvi galimybė prieš „Java 8“.

Dėl pasyvus iteratorius (taip pat žinomas kaip numanomas iteratorius, vidinis iteratoriusarba atgalinio skambinimo iteratorius), iteratorius pats kontroliuoja iteraciją. Klientas iš esmės sako iteratoriui: „atlikite šią operaciją su kolekcijos elementais“. Šis požiūris yra įprastas tokiose kalbose kaip LISP, kurios teikia anonimines funkcijas arba uždaro. Išleidus „Java 8“, šis iteracijos būdas dabar yra pagrįsta alternatyva „Java“ programuotojams.

„Java 8“ vardų suteikimo schemos

Nors „Java“ versijų istorijoje nėra taip blogai, kaip „Windows“ (NT, 2000, XP, VISTA, 7, 8, ...), yra kelios pavadinimų schemos. Norėdami pradėti, turėtume „Java“ standartinį leidimą vadinti „JDK“, „J2SE“ ar „Java SE“? „Java“ versijų numeriai prasidėjo gana nesudėtingai - 1.0, 1.1 ir t. T., Tačiau viskas pasikeitė nuo 1.5 versijos, kuri buvo pavadinta „Java“ (arba JDK) 5. Kalbėdamas apie ankstyvąsias „Java“ versijas, naudoju tokias frazes kaip „Java 1.0“ arba „Java“ 1.1, "bet po penktos" Java "versijos aš naudoju tokias frazes kaip" Java 5 "arba" Java 8. "

Norėdami iliustruoti įvairius „Java“ iteracijos metodus, man reikia kolekcijos pavyzdžio ir kažko, ką reikia padaryti su jos elementais. Pradinėje šio straipsnio dalyje naudosiu stygų rinkinį, žymintį daiktų pavadinimus. Kiekvienam kolekcijos pavadinimui paprasčiausiai atspausdinsiu jo vertę standartine išvestimi. Šios pagrindinės idėjos lengvai pritaikomos sudėtingesnių objektų kolekcijoms (pvz., Darbuotojams) ir ten, kur kiekvieno objekto apdorojimas yra šiek tiek labiau susijęs (pavyzdžiui, kiekvienam aukštai įvertintam darbuotojui suteikiama 4,5 proc. Padidinimas).

Kitos iteracijos formos „Java 8“

Aš daugiausia dėmesio skiriu kartojimui per kolekcijas, tačiau „Java“ yra ir kitų, labiau specializuotų iteracijos formų. Pavyzdžiui, galite naudoti JDBC Rezultato rinkinys kartoti eilutes, grąžintas iš SELECT užklausos į reliacinę duomenų bazę, arba naudoti a Skaitytuvas kartoti įvesties šaltinį.

Kartojimas su Surašymo klase

„Java 1.0“ ir „1.1“ versijose buvo dvi pagrindinės rinkimo klasės Vektorius ir „Hashtable“, o „Iterator“ dizaino modelis buvo įgyvendintas vadinamoje klasėje Surašymas. Žvelgiant atgal, tai buvo blogas klasės pavadinimas. Nepainiokite klasės Surašymas su sąvoka enum tipai, kuris nebuvo rodomas iki „Java 5.“. Šiandien abu Vektorius ir „Hashtable“ yra bendrinės klasės, tačiau tada bendrieji nebuvo „Java“ kalbos dalis. Kodas, skirtas apdoroti eilučių vektorių naudojant Surašymas atrodytų panašiai kaip 1 sąrašas.

Sąrašas 1. Naudojant surašymą, norint pakartoti eilučių vektorių

 Vektorių pavadinimai = naujas vektorius (); // ... pridėti keletą vardų prie kolekcijos Surašymas e = vardai.elementai (); while (e.hasMoreElements ()) {String name = (String) e.nextElement (); System.out.println (vardas); } 

Kartojimas su „Iterator“ klase

„Java 1.2“ pristatė kolekcijų klases, kurias visi žinome ir mėgstame, o „Iterator“ dizaino modelis buvo įgyvendintas klasėje, tinkamai pavadintoje Iteratorius. Kadangi dar neturėjome generinių programų „Java 1.2“, iš „ Iteratorius vis tiek buvo reikalinga. „Java“ versijose 1.2–1.4 eilių per eilutes sąrašas gali būti panašus į 2 sąrašą.

Sąrašas 2. Iterator naudojimas norint pakartoti eilučių sąrašą

 Sąrašo pavadinimai = new LinkedList (); // ... pridėti keletą vardų prie kolekcijos Iterator i = vardai.iterator (); while (i.hasNext ()) {String name = (String) i.next (); System.out.println (vardas); } 

Kartojimas su generiniais vaistais ir patobulinta for-loop

„Java 5“ suteikė mums sąsają su generikais Pasikartojantisir patobulinta „for-loop“. Patobulinta „for-loop“ yra mano mėgstamiausias mažas „Java“ priedas. Iteratoriaus kūrimas ir skambučiai į jį hasNext () ir Kitas() metodai nėra aiškiai išreikšti kodekse, tačiau jie vis tiek vyksta užkulisiuose. Taigi, nors kodas yra kompaktiškesnis, vis tiek naudojame aktyvų iteratorių. Naudojant „Java 5“, mūsų pavyzdys atrodys panašus į tai, ką matote 3 sąraše.

Sąrašas 3. Naudojant generinius ir patobulintą „for-loop“, norint pakartoti eilučių sąrašą

 Sąrašo pavadinimai = new LinkedList (); // ... pridėti keletą pavadinimų prie (String name: names) kolekcijos System.out.println (vardas); 

„Java 7“ suteikė mums deimantų operatorių, kuris sumažina generikų paprastumą. Praėjo tie laikai, kai reikėjo pakartoti tipą, naudojamą generinei klasei sukurti, pasikvietus naujas operatorius! „Java 7“ pirmąją 3 sąrašo eilutę galėtume supaprastinti taip:

 Sąrašo pavadinimai = new LinkedList (); 

Švelnus pašaipa prieš generinius vaistus

Programavimo kalbos dizainas apima kompromisus tarp kalbos ypatybių naudos ir sudėtingumo, kurį jie daro kalbos sintaksei ir semantikai. Generikams nesu įsitikinęs, kad nauda yra didesnė už sudėtingumą. „Generics“ išsprendė problemą, kurios aš neturėjau su „Java“. Aš paprastai sutinku su Keno Arnoldo nuomone, kai jis teigia: "Generikai yra klaida. Tai nėra problema, pagrįsta techniniais nesutarimais. Tai yra pagrindinė kalbos dizaino problema [...]" Java "sudėtingumas buvo papildytas tuo, kas man atrodo palyginti nedidelė nauda “.

Laimei, nors generinių klasių kūrimas ir įgyvendinimas kartais gali būti pernelyg sudėtingas, pastebėjau, kad praktikoje bendrų klasių naudojimas paprastai yra paprastas.

Kartojimas naudojant forEach () metodą

Prieš gilindamiesi į „Java 8“ iteracijos ypatybes, apmąstykime, kas yra blogai su kodu, rodomu ankstesniuose sąrašuose, o tai nėra nieko. Šiuo metu diegiamose programose yra milijonai „Java“ kodo eilučių, kuriose naudojami aktyvūs iteratoriai, panašūs į rodomus mano sąrašuose. „Java 8“ tiesiog suteikia papildomų galimybių ir naujų iteracijos atlikimo būdų. Kai kuriais atvejais nauji būdai gali būti geresni.

Pagrindinės naujos „Java 8“ funkcijos sutelktos į „lambda“ išraiškas, kartu su susijusiomis funkcijomis, tokiomis kaip srautai, metodų nuorodos ir funkcinės sąsajos. Šios naujos „Java 8“ funkcijos leidžia mums rimtai apsvarstyti galimybę naudoti pasyvius iteratorius, o ne labiau įprastus aktyvius iteratorius. Visų pirma Pasikartojantis sąsaja teikia pasyvų iteratorių numatytojo metodo, vadinamo, forma kiekvienam().

A numatytasis metodas, dar viena nauja „Java 8“ funkcija yra metodas sąsajoje su numatytuoju diegimu. Šiuo atveju kiekvienam() metodas iš tikrųjų įgyvendinamas naudojant aktyvų iteratorių panašiai kaip matėte 3 sąraše.

Kolekcijos klasės, kurios įgyvendinamos Pasikartojantis (pavyzdžiui, visos sąrašų ir rinkinių klasės) dabar turi a kiekvienam() metodas. Šis metodas reikalauja vieno parametro, kuris yra funkcinė sąsaja. Todėl tikrasis parametras perduotas kiekvienam() metodas yra kandidatas į lambda išraišką. Naudojant „Java 8“ funkcijas, mūsų vykdomasis pavyzdys pasikeis į 4 sąraše pateiktą formą.

4 sąrašas. „Java 8“ kartojimas naudojant forEach () metodą

 Sąrašo pavadinimai = new LinkedList (); // ... pridėti keletą pavadinimų prie kolekcijos pavadinimų.forEach (vardas -> System.out.println (vardas)); 

Atkreipkite dėmesį į skirtumą tarp pasyvaus iteratoriaus 4 sąraše ir aktyvaus iteratoriaus ankstesniuose trijuose sąrašuose. Pirmuosiuose trijuose sąrašuose ciklo struktūra valdo iteraciją, o kiekvieną kartą einant per kilpą, objektas yra gaunamas iš sąrašo ir tada atspausdinamas. 4 sąraše nėra aiškaus ciklo. Mes paprasčiausiai pasakome kiekvienam() metodas, ką daryti su sąrašo objektais - šiuo atveju mes tiesiog išspausdiname objektą. Iteracijos kontrolė yra kiekvienam() metodas.

Kartojimas su „Java“ srautais

Dabar pasvarstykime, ar padaryti ką nors šiek tiek labiau susijusio, nei paprasčiausiai spausdinti vardus mūsų sąraše. Tarkime, kad, pavyzdžiui, norime suskaičiuoti vardų, prasidedančių raide, skaičių A. Galėtume įgyvendinti sudėtingesnę logiką kaip „lambda“ išraiškos dalį arba naudoti naują „Java 8“ „Stream“ API. Pasirinkime pastarąjį požiūrį.