Pengulangan lebih banyak koleksi di Jawa

Bila-bila masa anda mempunyai koleksi barang, anda memerlukan beberapa mekanisme untuk melangkah secara sistematik walaupun item dalam koleksi itu. Sebagai contoh sehari-hari, pertimbangkan alat kawalan jauh televisyen, yang memungkinkan kita berulang melalui pelbagai saluran televisyen. Begitu juga, dalam dunia pengaturcaraan, kita memerlukan mekanisme untuk melakukan iterasi secara sistematik melalui koleksi objek perisian. Java merangkumi pelbagai mekanisme untuk iterasi, termasuk indeks (untuk iterasi pada array), kursor (untuk iterasi atas hasil kueri pangkalan data), penghitungan (dalam versi awal Java), dan iterator (dalam versi Java yang lebih baru).

Corak Iterator

An iterator adalah satu mekanisme yang membenarkan semua unsur-unsur koleksi untuk diakses secara berurutan, dengan beberapa operasi yang dilakukan pada setiap elemen. Pada dasarnya, iterator menyediakan cara "looping" ke atas koleksi objek yang dikemas. Contoh penggunaan iterator merangkumi

  • Lawati setiap fail dalam direktori ( folder aka ) dan paparkan namanya.
  • Lawati setiap nod dalam grafik dan tentukan sama ada node tersebut dapat dicapai dari nod tertentu.
  • Kunjungi setiap pelanggan dalam barisan (misalnya, mensimulasikan garis di bank) dan ketahui berapa lama dia menunggu.
  • Lawati setiap simpul di pohon sintaks abstrak penyusun (yang dihasilkan oleh penghurai) dan lakukan semantik semakan atau penjanaan kod. (Anda juga boleh menggunakan corak Pelawat dalam konteks ini.)

Prinsip-prinsip tertentu berlaku untuk penggunaan iterator: Secara amnya, anda mungkin dapat melakukan banyak perjalanan dalam masa yang sama; iaitu, iterator harus membenarkan konsep looping bersarang. Pengulangan juga tidak boleh merosakkan dalam arti bahawa tindakan lelaran tidak boleh mengubah koleksi itu sendiri. Sudah tentu operasi yang dilakukan pada elemen dalam koleksi mungkin dapat mengubah beberapa elemen. Mungkin juga mungkin bagi iterator untuk menyokong mengeluarkan elemen dari koleksi atau memasukkan elemen baru pada titik tertentu dalam koleksi, tetapi perubahan tersebut harus jelas dalam program dan bukan produk sampingan dari iterasi. Dalam beberapa kes, anda juga perlu mempunyai iterator dengan kaedah melintasi yang berbeza; contohnya, melintasi sebilangan pokok dan pasca pesanan, atau melintasi graf pertama-kedalaman dan lebar pertama.

Mengulangi struktur data yang kompleks

Saya mula-mula belajar untuk memprogram di versi awal FORTRAN, di mana satu-satunya kemampuan penyusunan data adalah array. Saya dengan cepat belajar bagaimana melakukan lelaran pada array menggunakan indeks dan gelung DO. Dari situ hanya lompatan mental singkat untuk idea menggunakan indeks umum ke dalam beberapa susunan untuk mensimulasikan pelbagai catatan. Sebilangan besar bahasa pengaturcaraan mempunyai ciri-ciri yang serupa dengan tatasusunan, dan mereka menyokong perulangan langsung daripada tatasusunan. Tetapi bahasa pengaturcaraan moden juga menyokong struktur data yang lebih kompleks seperti senarai, set, peta, dan pokok, di mana kemampuannya disediakan melalui kaedah awam tetapi perincian dalaman tersembunyi di bahagian peribadi kelas. Pengaturcara perlu dapat melintasi elemen struktur data ini tanpa mendedahkan struktur dalamannya, yang merupakan tujuan pengulangan.

Corak reka bentuk Iterator dan Gang of Four

Menurut Gang of Four (lihat di bawah), corak reka bentuk Iterator adalah pola tingkah laku, yang idea utamanya adalah "mengambil tanggungjawab untuk mengakses dan melintasi keluar dari senarai [ ed. Think koleksi ] objek dan memasukkannya ke dalam iterator objek. " Artikel ini tidak banyak mengenai corak Iterator tetapi mengenai bagaimana lelaran digunakan dalam praktik. Untuk merangkumi sepenuhnya corak ini memerlukan perbincangan bagaimana iterator akan dirancang, peserta (objek dan kelas) dalam reka bentuk, kemungkinan reka bentuk alternatif, dan pertukaran alternatif reka bentuk yang berbeza. Saya lebih suka menumpukan pada bagaimana iterator digunakan dalam praktik, tetapi saya akan menunjukkan kepada anda beberapa sumber untuk menyiasat corak dan corak reka bentuk Iterator secara umum:

  • Corak Reka Bentuk: Elemen Perisian Berorientasikan Objek yang Boleh Digunakan Semula (Addison-Wesley Professional, 1994) yang ditulis oleh Erich Gamma, Richard Helm, Ralph Johnson, dan John Vlissides (juga dikenali sebagai Gang of Four atau hanya GoF) adalah sumber pasti untuk pembelajaran mengenai corak reka bentuk. Walaupun buku ini pertama kali diterbitkan pada tahun 1994, buku ini tetap klasik, seperti yang dibuktikan oleh fakta bahawa terdapat lebih dari 40 cetakan.
  • Bob Tarr, seorang pensyarah di University of Maryland Baltimore County, mempunyai slaid yang sangat baik untuk kursus mengenai corak reka bentuk, termasuk pengenalannya kepada corak Iterator.
  • Siri JavaWorld David Geary Java Design Patterns memperkenalkan banyak corak reka bentuk Gang of Four, termasuk corak Singleton, Observer, dan Composite. Juga di JavaWorld, gambaran keseluruhan tiga bahagian Jeff Friesen mengenai corak reka bentuk termasuk panduan untuk corak GoF.

Iterator aktif vs iterator pasif

Terdapat dua pendekatan umum untuk melaksanakan iterator bergantung pada siapa yang mengawal lelaran. Untuk iterator aktif (juga dikenali sebagai iterator eksplisit atau iterator luaran ), klien mengawal lelaran dalam arti bahawa pelanggan membuat iterator, memberitahu bila harus maju ke elemen seterusnya, menguji untuk melihat apakah setiap elemen telah dikunjungi, dan sebagainya. Pendekatan ini biasa dalam bahasa seperti C ++, dan pendekatan inilah yang paling mendapat perhatian dalam buku GoF. Walaupun iterator di Java telah mengambil bentuk yang berbeda, menggunakan iterator aktif pada dasarnya adalah satu-satunya pilihan yang layak sebelum Java 8.

Untuk iterator pasif (juga dikenali sebagai iterator tersirat , iterator dalaman , atau iterator callback ), iterator itu sendiri mengawal lelaran. Pelanggan pada dasarnya mengatakan kepada iterator, "lakukan operasi ini pada elemen dalam koleksi." Pendekatan ini biasa dalam bahasa seperti LISP yang menyediakan fungsi atau penutupan tanpa nama. Dengan pembebasan Java 8, pendekatan iterasi ini sekarang menjadi alternatif yang masuk akal bagi pengaturcara Java.

Skema penamaan Java 8

Walaupun tidak seburuk Windows (NT, 2000, XP, VISTA, 7, 8, ...) Sejarah versi Java merangkumi beberapa skema penamaan. Untuk memulai, haruskah kita menyebut edisi standar Java sebagai "JDK," "J2SE," atau "Java SE"? Nombor versi Java bermula cukup mudah - 1.0, 1.1, dll. - tetapi semuanya berubah dengan versi 1.5, yang berjenama Java (atau JDK) 5. Ketika merujuk pada versi awal Java, saya menggunakan frasa seperti "Java 1.0" atau "Java 1.1, "tetapi setelah Java versi kelima, saya menggunakan frasa seperti" Java 5 "atau" Java 8. "

Untuk menggambarkan pelbagai pendekatan iterasi di Jawa, saya memerlukan contoh koleksi dan sesuatu yang perlu dilakukan dengan unsur-unsurnya. Untuk bahagian awal artikel ini saya akan menggunakan koleksi rentetan yang mewakili nama benda. Untuk setiap nama dalam koleksi, saya hanya akan mencetak nilainya ke output standard. Idea-idea asas ini dengan mudah diperluas ke koleksi objek yang lebih rumit (seperti pekerja), dan di mana pemprosesan untuk setiap objek sedikit lebih terlibat (seperti memberi kenaikan gaji kepada setiap pekerja yang diberi nilai 4.5%)

Bentuk lelaran lain di Jawa 8

Saya memfokuskan pada iterasi ke atas koleksi, tetapi ada bentuk lelaran lain yang lebih khusus di Jawa. Sebagai contoh, anda mungkin menggunakan JDBC ResultSetuntuk melakukan iterasi pada baris yang dikembalikan dari pertanyaan SELECT ke pangkalan data hubungan, atau menggunakan Scanneruntuk melakukan iterasi ke atas sumber input.

Pengulangan dengan kelas Penghitung

Di Java 1.0 dan 1.1, dua kelas koleksi utama adalah Vectordan Hashtable, dan corak reka bentuk Iterator dilaksanakan dalam kelas yang disebut Enumeration. Jika dilihat semula, ini adalah nama buruk bagi kelas. Jangan mengelirukan kelas Enumerationdengan konsep jenis enum , yang tidak muncul hingga Java 5. Hari ini keduanya Vectordan Hashtablemerupakan kelas generik, tetapi pada masa itu generik bukan sebahagian daripada bahasa Java. Kod untuk memproses vektor rentetan menggunakan Enumerationakan kelihatan seperti Penyenaraian 1.

Penyenaraian 1. Menggunakan penghitungan untuk mengulang vektor rentetan

 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); } 

Pengulangan dengan kelas Iterator

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

Sekarang mari kita mempertimbangkan untuk melakukan sesuatu yang lebih terlibat daripada sekadar mencetak nama dalam senarai kita. Katakan, sebagai contoh, yang kita mahu mengira jumlah nama yang bermula dengan huruf A . Kami dapat menerapkan logik yang lebih rumit sebagai bagian dari ungkapan lambda, atau kami dapat menggunakan API Stream baru Java 8. Mari kita mengambil pendekatan yang terakhir.