Iteriranje preko zbirki na Javi

Kad god imate zbirku stvari, trebat će vam neki mehanizam za sustavno premještanje predmeta iz te kolekcije. Kao svakodnevni primjer uzmimo televizijski daljinski upravljač koji nam omogućuje iteraciju kroz razne televizijske kanale. Slično tome, u svijetu programiranja potreban nam je mehanizam za sustavno ponavljanje kroz kolekciju softverskih objekata. Java uključuje različite mehanizme za iteraciju, uključujući indeks (za iteraciju preko niza), kursor (za iteriranje rezultata upita baze podataka), nabrajanje (u ranim verzijama Jave) i iterator (u novijim verzijama Jave).

Uzorak Iteratora

Iteratora je mehanizam koji dozvoljava sve elemente zbirci će se pristupiti u nizu, s nekim rad obavlja na svaki element. U osnovi, iterator pruža sredstvo za "petljanje" preko enkapsulirane zbirke predmeta. Primjeri korištenja iteratora uključuju

  • Posjetite svaku datoteku u direktoriju ( aka mapa) i prikažite joj ime.
  • Posjetite svaki čvor u grafu i utvrdite je li dostupan iz određenog čvora.
  • Posjetite svakog kupca u redu (na primjer, simulirajući liniju u banci) i saznajte koliko dugo čeka.
  • Posjetite svaki čvor u apstraktnom stablu sintakse kompajlera (koji stvara parser) i izvedite semantičku provjeru ili generiranje koda. (U ovom kontekstu možete koristiti i obrazac Visitor.)

Određena načela vrijede za upotrebu iteratora: Općenito, trebali biste moći imati više prelazaka u tijeku istovremeno; to jest, iterator treba omogućiti koncept ugniježđenog petlje. Iterator također ne bi trebao biti destruktivan u smislu da čin iteracije sam po sebi ne bi trebao mijenjati zbirku. Naravno da bi operacija koja se izvodi nad elementima u zbirci mogla promijeniti neke od elemenata. Također bi moglo biti moguće da iterator podržava uklanjanje elementa iz zbirke ili umetanje novog elementa u određenoj točki zbirke, ali takve bi promjene trebale biti izričite u programu, a ne nusprodukt iteracije. U nekim slučajevima trebat će vam i iteratori s različitim metodama prelaska; na primjer, preslikavanje stabla pred porudžbinom i postorderom, ili križanje grafa po dubini i širini.

Iteriranje složenih struktura podataka

Prvi sam put naučio programirati u ranoj verziji FORTRAN-a, gdje je jedina sposobnost strukturiranja podataka bio niz. Brzo sam naučio kako itirirati niz pomoću indeksa i DO petlje. Odatle je bio samo kratki mentalni skok prema ideji korištenja zajedničkog indeksa u više nizova za simulaciju niza zapisa. Većina programskih jezika ima značajke slične nizovima i podržavaju izravno prevlačenje po nizovima. Ali moderni programski jezici također podržavaju složenije strukture podataka poput popisa, skupova, karata i stabala, gdje su mogućnosti dostupne javnim metodama, ali unutarnji detalji skriveni su u privatnim dijelovima klase. Programeri trebaju biti sposobni prelaziti elemente tih struktura podataka bez izlaganja njihove unutarnje strukture, što je svrha iteratora.

Iteratori i banda četvorke dizajnerskih uzoraka

Prema Gang of Four (vidi dolje), obrazac dizajna Iterator je obrazac ponašanja, čija je ključna ideja "preuzeti odgovornost za pristup i prelazak s objekta s popisa [ ur. Think collection ] i staviti ga u iterator objekt." Ovaj se članak ne bavi toliko uzorkom Iteratora, koliko uporabom iteratora u praksi. Da bi se u potpunosti pokrio obrazac, bilo bi potrebno razgovarati o tome kako bi iterator bio dizajniran, sudionicima (objektima i klasama) u dizajnu, mogućim alternativnim dizajnom i kompromisima različitih dizajnerskih alternativa. Radije bih se usredotočio na to kako se iteratori koriste u praksi, ali ukazat ću vam na nekoliko izvora za istraživanje uzorka i dizajnera uzorka Iteratora općenito:

  • Uzorci dizajna: elementi višekratno korištenog objektno orijentiranog softvera (Addison-Wesley Professional, 1994.) koji su napisali Erich Gamma, Richard Helm, Ralph Johnson i John Vlissides (također poznat kao Gang of Four ili jednostavno GoF) konačni je resurs za učenje o uzorcima dizajna. Iako je knjiga prvi put objavljena 1994. godine, ostaje klasična, o čemu svjedoči činjenica da je bilo više od 40 tiskanih izdanja.
  • Bob Tarr, predavač na Sveučilištu Maryland u okrugu Baltimore, ima izvrstan niz dijapozitiva za svoj tečaj o dizajnerskim uzorcima, uključujući i uvod u obrazac Iterator.
  • Java dizajnerski uzorci serije JavaWorld Davida Gearyja predstavljaju mnoge dizajnerske uzorke Gang of Four, uključujući uzorke Singleton, Observer i Composite. Također na JavaWorldu, noviji trodijelni pregled obrazaca dizajna Jeffa Friesena uključuje vodič za GoF uzorke.

Aktivni iteratori vs pasivni iteratori

Postoje dva opća pristupa implementaciji iteratora, ovisno o tome tko iteraciju kontrolira. Za aktivni iterator (poznat i kao eksplicitni iterator ili vanjski iterator ), klijent kontrolira iteraciju u smislu da klijent kreira iterator, govori mu kada treba preći na sljedeći element, testira da li je svaki element posjećen, i tako dalje. Ovaj je pristup uobičajen u jezicima kao što je C ++, a pristup je onaj koji dobiva najviše pažnje u knjizi GoF. Iako su iteratori u Javi poprimili različite oblike, upotreba aktivnog iteratora u osnovi je bila jedina održiva opcija prije Java 8.

Za pasivni iterator (poznat i kao implicitni iterator , interni iterator ili iterator povratnog poziva ), iterator sam kontrolira iteraciju. Klijent u osnovi kaže iteratoru, "izvedite ovu operaciju nad elementima u zbirci." Ovaj je pristup uobičajen u jezicima poput LISP-a koji pružaju anonimne funkcije ili zatvaranja. Izlaskom Jave 8, ovaj pristup iteraciji sada je razumna alternativa za Java programere.

Sheme imenovanja Java 8

Iako nije baš toliko loša kao Windows (NT, 2000, XP, VISTA, 7, 8, ...), Javina povijest verzija uključuje nekoliko shema imenovanja. Za početak, trebamo li standardno izdanje Java nazivati ​​"JDK", "J2SE" ili "Java SE"? Brojevi Java inačica započeli su prilično jednostavno - 1.0, 1.1 itd. - ali sve se promijenilo s verzijom 1.5 koja je nosila naziv Java (ili JDK) 5. Kada se referiram na rane verzije Jave, koristim fraze poput "Java 1.0" ili "Java 1.1, "ali nakon pete verzije Jave koristim fraze poput" Java 5 "ili" Java 8. "

Da bih ilustrirao različite pristupe iteraciji u Javi, potreban mi je primjer zbirke i nečega što treba učiniti s njezinim elementima. Za početni dio ovog članka koristit ću zbirku nizova koji predstavljaju imena stvari. Za svako ime u zbirci jednostavno ću ispisati njegovu vrijednost na standardni izlaz. Te se osnovne ideje lako proširuju na zbirke složenijih predmeta (poput zaposlenika) i gdje je obrada svakog predmeta malo više uključena (poput davanja svakom visoko ocijenjenom zaposleniku povišenja od 4,5 posto).

Ostali oblici ponavljanja u Javi 8

I'm focusing on iterating over collections, but there are other, more specialized forms of iteration in Java. For example, you might use a JDBC ResultSet to iterate over the rows returned from a SELECT query to a relational database, or use a Scanner to iterate over an input source.

Iteration with the Enumeration class

In Java 1.0 and 1.1, the two primary collection classes were Vector and Hashtable, and the Iterator design pattern was implemented in a class called Enumeration. In retrospect this was a bad name for the class. Do not confuse the class Enumeration with the concept of enum types, which didn't appear until Java 5. Today both Vector and Hashtable are generic classes, but back then generics were not part of the Java language. The code to process a vector of strings using Enumeration would look something like Listing 1.

Listing 1. Using enumeration to iterate over a vector of strings

 Vector names = new Vector(); // ... add some names to the collection Enumeration e = names.elements(); while (e.hasMoreElements()) { String name = (String) e.nextElement(); System.out.println(name); } 

Iteration with the Iterator class

Java 1.2 introduced the collection classes that we all know and love, and the Iterator design pattern was implemented in a class appropriately named Iterator. Because we didn't yet have generics in Java 1.2, casting an object returned from an Iterator was still necessary. For Java versions 1.2 through 1.4, iterating over a list of strings might resemble Listing 2.

Listing 2. Using an Iterator to iterate over a list of strings

 List names = new LinkedList(); // ... add some names to the collection Iterator i = names.iterator(); while (i.hasNext()) { String name = (String) i.next(); System.out.println(name); } 

Iteration with generics and the enhanced for-loop

Java 5 gave us generics, the interface Iterable, and the enhanced for-loop. The enhanced for-loop is one of my all-time-favorite small additions to Java. The creation of the iterator and calls to its hasNext() and next() methods are not expressed explicitly in the code, but they still take place behind the scenes. Thus, even though the code is more compact, we are still using an active iterator. Using Java 5, our example would look something like what you see in Listing 3.

Listing 3. Using generics and the enhanced for-loop to iterate over a list of strings

 List names = new LinkedList(); // ... add some names to the collection for (String name : names) System.out.println(name); 

Java 7 gave us the diamond operator, which reduces the verbosity of generics. Gone were the days of having to repeat the type used to instantiate the generic class after invoking the new operator! In Java 7 we could simplify the first line in Listing 3 above to the following:

 List names = new LinkedList(); 

A mild rant against generics

The design of a programming language involves tradeoffs between the benefits of language features versus the complexity they impose on the syntax and semantics of the language. For generics, I am not convinced that the benefits outweigh the complexity. Generics solved a problem that I did not have with Java. I generally agree with Ken Arnold's opinion when he states: "Generics are a mistake. This is not a problem based on technical disagreements. It's a fundamental language design problem [...] The complexity of Java has been turbocharged to what seems to me relatively small benefit."

Fortunately, while designing and implementing generic classes can sometimes be overly complicated, I have found that using generic classes in practice is usually straightforward.

Iteration with the forEach() method

Before delving into Java 8 iteration features, let's reflect on what's wrong with the code shown in the previous listings–which is, well, nothing really. There are millions of lines of Java code in currently deployed applications that use active iterators similar to those shown in my listings. Java 8 simply provides additional capabilities and new ways of performing iteration. For some scenarios, the new ways can be better.

The major new features in Java 8 center on lambda expressions, along with related features such as streams, method references, and functional interfaces. These new features in Java 8 allow us to seriously consider using passive iterators instead of the more conventional active iterators. In particular, the Iterable interface provides a passive iterator in the form of a default method called forEach().

A default method, another new feature in Java 8, is a method in an interface with a default implementation. In this case, the forEach() method is actually implemented using an active iterator in a manner similar to what you saw in Listing 3.

Collection classes that implement Iterable (for example, all list and set classes) now have a forEach() method. This method takes a single parameter that is a functional interface. Therefore the actual parameter passed to the forEach() method is a candidate for a lambda expression. Using the features of Java 8, our running example would evolve to the form shown in Listing 4.

Listing 4. Iteration in Java 8 using the forEach() method

 List names = new LinkedList(); // ... add some names to the collection names.forEach(name -> System.out.println(name)); 

Note the difference between the passive iterator in Listing 4 and the active iterator in the previous three listings. In the first three listings, the loop structure controls the iteration, and during each pass through the loop, an object is retrieved from the list and then printed. In Listing 4, there is no explicit loop. We simply tell the forEach() method what to do with the objects in the list — in this case we simply print the object. Control of the iteration resides within the forEach() method.

Iteration with Java streams

Sada razmislimo o tome da učinimo nešto malo više uključeno nego jednostavno ispisivanje imena s našeg popisa. Pretpostavimo, na primjer, da želimo računati broj imena koja počinju sa slovom A . Mogli bismo implementirati složeniju logiku kao dio lambda izraza ili bismo mogli koristiti novi Stream API Java 8. Uzmimo potonji pristup.