Mengulangi koleksi di Jawa

Kapan pun Anda memiliki koleksi barang, Anda akan memerlukan mekanisme untuk secara sistematis menelusuri barang-barang dalam koleksi itu. Sebagai contoh sehari-hari, pertimbangkan remote control televisi, yang memungkinkan kita beralih melalui berbagai saluran televisi. Demikian pula, dalam dunia pemrograman, kita membutuhkan mekanisme untuk mengulang secara sistematis melalui kumpulan objek perangkat lunak. Java menyertakan berbagai mekanisme untuk iterasi, termasuk indeks (untuk melakukan iterasi melalui larik), kursor (untuk melakukan iterasi pada hasil kueri database), enumerasi (di versi awal Java), dan iterator (di versi Java yang lebih baru).

Pola Iterator

Sebuah iterator adalah mekanisme yang mengizinkan semua elemen koleksi untuk diakses secara berurutan, dengan beberapa operasi dilakukan pada setiap elemen. Pada dasarnya, sebuah iterator menyediakan sarana untuk "mengulang" kumpulan objek yang dikemas. Contoh penggunaan iterator termasuk

  • Kunjungi setiap file dalam direktori ( alias folder) dan tampilkan namanya.
  • Kunjungi setiap node dalam grafik dan tentukan apakah dapat dijangkau dari node tertentu.
  • Kunjungi setiap pelanggan dalam antrian (misalnya, simulasi antrean di bank) dan cari tahu berapa lama dia telah menunggu.
  • Kunjungi setiap node dalam pohon sintaksis abstrak compiler (yang dihasilkan oleh parser) dan lakukan pemeriksaan semantik atau pembuatan kode. (Anda juga dapat menggunakan pola Pengunjung dalam konteks ini.)

Prinsip-prinsip tertentu berlaku untuk penggunaan iterator: Secara umum, Anda harus dapat memiliki beberapa traversal yang sedang berlangsung pada waktu yang sama; artinya, iterator harus mengizinkan konsep perulangan bersarang. Sebuah iterator juga harus nondestructive dalam arti bahwa tindakan iterasi tidak dengan sendirinya mengubah koleksi. Tentu saja operasi yang dilakukan pada elemen dalam sebuah koleksi mungkin dapat mengubah beberapa elemen. Mungkin juga iterator mendukung penghapusan elemen dari koleksi atau menyisipkan elemen baru pada titik tertentu dalam koleksi, tetapi perubahan tersebut harus eksplisit dalam program dan bukan produk sampingan dari iterasi. Dalam beberapa kasus, Anda juga perlu memiliki iterator dengan metode traversal yang berbeda; misalnya, penjelajahan preorder dan postorder traversal pohon, atau depth-first dan breadth-first traversal dari sebuah grafik.

Iterasi struktur data yang kompleks

Saya pertama kali belajar memprogram dalam versi awal FORTRAN, di mana satu-satunya kemampuan penataan data adalah sebuah array. Saya dengan cepat belajar bagaimana mengulang array menggunakan indeks dan DO-loop. Dari sana, hanya ada lompatan mental singkat ke gagasan menggunakan indeks umum menjadi beberapa larik untuk mensimulasikan larik catatan. Sebagian besar bahasa pemrograman memiliki fitur yang mirip dengan array, dan mendukung perulangan langsung melalui array. Tetapi bahasa pemrograman modern juga mendukung struktur data yang lebih kompleks seperti daftar, set, peta, dan pohon, di mana kapabilitas tersedia melalui metode publik tetapi detail internalnya disembunyikan di bagian privat kelas. Pemrogram harus dapat melintasi elemen struktur data ini tanpa mengekspos struktur internalnya, yang merupakan tujuan dari iterator.

Iterator dan pola desain Gang of Four

Menurut Gang of Four (lihat di bawah), pola desain Iterator adalah pola perilaku, yang ide utamanya adalah "mengambil tanggung jawab untuk mengakses dan menjelajahi objek list [ ed. Think collection ] dan memasukkannya ke dalam iterator obyek." Artikel ini tidak sebanyak tentang pola Iterator melainkan tentang bagaimana iterator digunakan dalam praktiknya. Untuk sepenuhnya menutupi pola akan membutuhkan diskusi bagaimana sebuah iterator akan dirancang, peserta (objek dan kelas) dalam desain, kemungkinan desain alternatif, dan pengorbanan dari alternatif desain yang berbeda. Saya lebih suka fokus pada bagaimana iterator digunakan dalam praktiknya, tetapi saya akan mengarahkan Anda ke beberapa sumber daya untuk menyelidiki pola Iterator dan pola desain secara umum:

  • Pola Desain: Elemen Perangkat Lunak Berorientasi Objek yang Dapat Digunakan Kembali (Addison-Wesley Professional, 1994) yang ditulis oleh Erich Gamma, Richard Helm, Ralph Johnson, dan John Vlissides (juga dikenal sebagai Gang of Four atau hanya GoF) adalah sumber daya yang pasti untuk pembelajaran tentang pola desain. Meskipun pertama kali diterbitkan pada tahun 1994, buku tersebut tetap klasik, terbukti dengan sudah ada lebih dari 40 cetakan.
  • Bob Tarr, dosen di University of Maryland Baltimore County, memiliki sekumpulan slide yang sangat bagus untuk kursusnya tentang pola desain, termasuk pengantar pola Iteratornya.
  • Seri JavaWorld David Geary, Java Design Patterns memperkenalkan banyak pola desain Gang of Four, termasuk pola Singleton, Observer, dan Composite. Juga di JavaWorld, gambaran umum tiga bagian terbaru dari Jeff Friesen tentang pola desain mencakup panduan untuk pola GoF.

Iterator aktif vs iterator pasif

Ada dua pendekatan umum untuk mengimplementasikan iterator bergantung pada siapa yang mengontrol iterasi. Untuk iterator aktif (juga dikenal sebagai iterator eksplisit atau iterator eksternal ), klien mengontrol iterasi dalam arti bahwa klien membuat iterator, memberi tahu kapan harus maju ke elemen berikutnya, menguji untuk melihat apakah setiap elemen telah dikunjungi, dan seterusnya. Pendekatan ini umum dalam bahasa seperti C ++, dan pendekatan inilah yang paling banyak mendapat perhatian dalam buku GoF. Meskipun iterator di Java memiliki bentuk yang berbeda, menggunakan iterator aktif pada dasarnya adalah satu-satunya pilihan yang layak sebelum Java 8.

Untuk iterator pasif (juga dikenal sebagai iterator implisit , iterator internal , atau iterator callback ), iterator itu sendiri mengontrol iterasinya. Klien pada dasarnya mengatakan kepada iterator, "lakukan operasi ini pada elemen dalam koleksi." Pendekatan ini umum dilakukan dalam bahasa seperti LISP yang menyediakan fungsi atau penutupan anonim. Dengan dirilisnya Java 8, pendekatan iterasi ini sekarang menjadi alternatif yang masuk akal untuk programmer Java.

Skema penamaan Java 8

Meskipun tidak seburuk Windows (NT, 2000, XP, VISTA, 7, 8, ...) riwayat versi Java mencakup beberapa skema penamaan. Untuk memulai, haruskah kita mengacu pada edisi standar Java sebagai "JDK", "J2SE", atau "Java SE"? Nomor versi Java dimulai dengan sangat mudah - 1.0, 1.1, dll. - tetapi semuanya berubah dengan versi 1.5, yang diberi merek Java (atau JDK) 5. Saat mengacu pada versi awal Java, saya menggunakan frasa seperti "Java 1.0" atau "Java 1.1, "tetapi setelah versi kelima Java saya menggunakan frasa seperti" Java 5 "atau" Java 8. "

Untuk mengilustrasikan berbagai pendekatan iterasi di Java, saya memerlukan contoh koleksi dan sesuatu yang perlu dilakukan dengan elemennya. Untuk bagian awal artikel ini saya akan menggunakan kumpulan string yang mewakili nama-nama benda. Untuk setiap nama dalam koleksi, saya hanya akan mencetak nilainya ke keluaran standar. Ide-ide dasar ini dengan mudah diperluas ke koleksi objek yang lebih rumit (seperti karyawan), dan di mana pemrosesan untuk setiap objek sedikit lebih terlibat (seperti memberi setiap karyawan berperingkat tinggi kenaikan 4,5 persen).

Bentuk lain dari iterasi di Jawa 8

Saya fokus pada iterasi atas koleksi, tetapi ada bentuk iterasi lain yang lebih khusus di Java. Misalnya, Anda mungkin menggunakan JDBC ResultSetuntuk mengulangi baris yang dikembalikan dari kueri SELECT ke database relasional, atau menggunakan Scanneruntuk mengulangi sumber input.

Iterasi dengan kelas Pencacahan

Di Java 1.0 dan 1.1, dua kelas koleksi utama adalah Vectordan Hashtable, dan pola desain Iterator diimplementasikan dalam kelas yang disebut Enumeration. Dalam retrospeksi ini adalah nama yang buruk untuk kelas. Jangan bingung kelas Enumerationdengan konsep tipe enum , yang tidak muncul sampai Java 5. Saat ini keduanya Vectordan Hashtablemerupakan kelas generik, tapi dulu generik bukan bagian dari bahasa Java. Kode untuk memproses vektor string menggunakan Enumerationakan terlihat seperti Kode 1.

Kode 1. Menggunakan enumerasi untuk mengulangi vektor string

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

Iterasi 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 pertimbangkan untuk melakukan sesuatu yang sedikit lebih rumit daripada sekadar mencetak nama dalam daftar kita. Anggaplah, misalnya, bahwa kita ingin menghitung jumlah nama yang dimulai dengan huruf A . Kita bisa mengimplementasikan logika yang lebih rumit sebagai bagian dari ekspresi lambda, atau kita bisa menggunakan API Stream baru Java 8. Mari kita ambil pendekatan yang terakhir.