Cara menggunakan enum yang berjimat di Jawa

Kod Java yang menggunakan jenis enumerasi tradisional bermasalah. Java 5 memberi kami alternatif yang lebih baik dalam bentuk enum jenis selamat. Dalam artikel ini, saya memperkenalkan anda kepada enumerasi jenis dan jenis yang dikira, menunjukkan cara mendeklarasikan enum jenis keselamatan dan menggunakannya dalam pernyataan beralih, dan membincangkan penyesuaian enum jenis simpanan dengan menambahkan data dan tingkah laku. Saya menyelesaikan artikel dengan meneroka kelas.java.lang.Enum

muat turun Dapatkan kod Muat turun kod sumber untuk contoh dalam tutorial Java 101 ini. Dicipta oleh Jeff Friesen untuk JavaWorld /.

Dari jenis yang dihitung hingga jumlah yang selamat

Satu jenis yang disebut satu persatu menentukan satu set pemalar berkaitan seperti yang nilai-nilainya. Contohnya termasuk seminggu hari, arah kompas utara / selatan / timur / barat, denominasi syiling mata wang, dan jenis token penganalisis leksikal.

Jenis yang dihitung secara tradisional telah dilaksanakan sebagai urutan pemalar integer, yang ditunjukkan oleh set pemalar arah berikut:

int statik akhir DIR_NORTH = 0; int statik akhir DIR_WEST = 1; int akhir statik DIR_EAST = 2; int akhir statik DIR_SOUTH = 3;

Terdapat beberapa masalah dengan pendekatan ini:

  • Kekurangan keselamatan jenis: Oleh kerana pemalar jenis yang dihitung hanyalah bilangan bulat, bilangan bulat apa pun dapat ditentukan di mana pemalar itu diperlukan. Selanjutnya, penambahan, pengurangan, dan operasi matematik lain dapat dilakukan pada pemalar ini; sebagai contoh, (DIR_NORTH + DIR_EAST) / DIR_SOUTH), yang tidak bermakna.
  • Namespace tidak ada: Pemalar jenis yang dihitung mesti diawali dengan beberapa jenis (mudah-mudahan) pengecam unik (contohnya DIR_) untuk mengelakkan perlanggaran dengan pemalar jenis yang dihitung lain.
  • Kerapuhan: Kerana pemalar jenis yang dihitung dikumpulkan ke dalam fail kelas di mana nilai harfiahnya disimpan (dalam kumpulan tetap), mengubah nilai pemalar memerlukan fail kelas ini dan fail kelas aplikasi yang bergantung padanya dibina semula. Jika tidak, tingkah laku tidak ditentukan akan berlaku pada waktu runtime.
  • Kekurangan maklumat: Apabila pemalar dicetak, nilai integernya akan dikeluarkan. Output ini tidak memberitahu anda tentang apa yang ditunjukkan oleh nilai integer. Ia bahkan tidak mengenal pasti jenis penghitung yang dimiliki pemalarnya.

Anda boleh mengelakkan masalah "kekurangan keselamatan jenis" dan "kekurangan maklumat" dengan menggunakan java.lang.Stringpemalar. Sebagai contoh, anda mungkin menentukan static final String DIR_NORTH = "NORTH";. Walaupun nilai pemalar lebih bermakna, Stringpemalar berdasarkan tetap mengalami "ruang nama tidak ada" dan masalah kerapuhan. Selain itu, tidak seperti perbandingan bilangan bulat, anda tidak dapat membandingkan nilai rentetan dengan operator ==dan !=(yang hanya membandingkan rujukan).

Masalah tersebut menyebabkan pembangun mencipta alternatif berasaskan kelas yang dikenali sebagai Typesafe Enum . Corak ini telah banyak dijelaskan dan dikritik. Joshua Bloch memperkenalkan corak dalam Item 21 dari Panduan Bahasa Pemrograman Java yang Berkesan (Addison-Wesley, 2001) dan menyatakan bahawa ia mempunyai beberapa masalah; iaitu bahawa mengagumkan untuk menggabungkan pemalar enum jenis selamat ke dalam set, dan pemalar pencacahan tidak dapat digunakan dalam switchpernyataan.

Pertimbangkan contoh berikut corak enum yang selamat. Yang Suitmenunjukkan kelas bagaimana anda boleh menggunakan alternatif berasaskan kelas untuk memperkenalkan jenis yang disebut satu persatu yang menggambarkan empat saman kad (kelab, berlian, hati, dan penyodok):

Suit kelas akhir awam // Tidak boleh dapat kelas Suit subkelas. {public static final Suit CLUBS = Suit baru (); Suit akhir awam statik DIAMONDS = Suit baru (); Suit akhir awam statik HEARTS = Suit baru (); saman akhir awam statik SPADES = Suit baru (); pakaian peribadi () {} // Tidak boleh memperkenalkan pemalar tambahan. }

Untuk menggunakan kelas ini, anda akan memperkenalkan Suitpemboleh ubah dan memberikannya kepada salah satu Suitpemalar, seperti berikut:

Sut baju = Suit.DIAMONDS;

Anda mungkin mahu menyoal siasat suitdalam switchpernyataan seperti ini:

tukar (saman) {case Suit.CLUBS: System.out.println ("kelab"); rehat; kes Suit.DIAMONDS: System.out.println ("berlian"); rehat; kes Suit.HEARTS: System.out.println ("hati"); rehat; kes Suit.SPADES: System.out.println ("spades"); }

Namun, ketika penyusun Java bertemu Suit.CLUBS, ia melaporkan kesalahan yang menyatakan bahawa ekspresi tetap diperlukan. Anda mungkin cuba mengatasi masalahnya seperti berikut:

tukar (saman) {case CLUBS: System.out.println ("kelab"); rehat; kes DIAMONDS: System.out.println ("berlian"); rehat; kes HATI: System.out.println ("hati"); rehat; kes SPADES: System.out.println ("spades"); }

Walau bagaimanapun, ketika penyusun bertemu CLUBS, ia akan melaporkan kesalahan yang menyatakan bahawa ia tidak dapat mencari simbol. Dan walaupun anda diletakkan Suitdalam satu pakej, diimport pakej, dan statik diimport pemalar ini, pengkompil akan mengadu bahawa ia tidak boleh menukar Suitke intapabila menghadapi suitdalam switch(suit). Mengenai masing-masing case, penyusun juga akan melaporkan bahawa ekspresi tetap diperlukan.

Java tidak menyokong corak Typesafe Enum dengan switchpernyataan. Namun, ia memperkenalkan ciri bahasa enum yang aman untuk merangkumi manfaat corak sambil menyelesaikan masalahnya , dan ciri ini memang menyokong switch.

Menyatakan enum jenis selamat dan menggunakannya dalam pernyataan beralih

Deklarasi enum jenis selamat dalam kod Java kelihatan seperti rakan-rakannya dalam bahasa C, C ++, dan C #:

enum Arah {UTARA, BARAT, TIMUR, SELATAN}

Pengisytiharan ini menggunakan kata kunci enumuntuk diperkenalkan Directionsebagai enum jenis keselamatan (kelas khas), di mana kaedah sewenang-wenang dapat ditambahkan dan antara muka sewenang-wenang dapat dilaksanakan. Yang NORTH, WEST, EAST, dan SOUTHpemalar enum dilaksanakan sebagai badan kelas berterusan khusus yang menentukan kelas tanpa nama melanjutkan yang disertakan Directionkelas.

Directiondan enums typesafe lain melanjutkan  dan mewarisi pelbagai kaedah, termasuk , dan , dari kelas ini. Kami akan meneroka kemudian dalam artikel ini.Enum values()toString()compareTo()Enum

Penyenaraian 1 menyatakan enum yang disebutkan di atas dan menggunakannya dalam switchpernyataan. Ia juga menunjukkan bagaimana membandingkan dua pemalar enum, untuk menentukan pemalar mana yang datang sebelum pemalar yang lain.

Penyenaraian 1: TEDemo.java(versi 1)

kelas awam TEDemo {enum Direction {NORTH, WEST, EAST, SOUTH} public static void main (String [] args) {for (int i = 0; i <Direction.values ​​(). length; i ++) {Direction d = Direction .values ​​() [i]; System.out.println (d); suis (d) {case NORTH: System.out.println ("Pindah ke utara"); rehat; kes BARAT: System.out.println ("Pindah ke barat"); rehat; kes EAST: System.out.println ("Pindah ke timur"); rehat; kes SELATAN: System.out.println ("Pindah ke selatan"); rehat; lalai: tegaskan false: "arah tidak diketahui"; }} System.out.println (Direction.NORTH.compareTo (Direction.SOUTH)); }}

Penyenaraian 1 mengisytiharkan Directionenum jenis selamat dan berulang pada anggota tetapnya, yang values()kembali. Untuk setiap nilai, switchpernyataan (dipertingkatkan untuk menyokong enum jenis selamat) memilih caseyang sesuai dengan nilai  d dan mengeluarkan mesej yang sesuai. (Anda tidak awalan pemalar enum, contohnya, NORTHdengan jenis enumnya.) Akhir sekali, Penyenaraian 1 menilai Direction.NORTH.compareTo(Direction.SOUTH)untuk menentukan sama NORTHada sebelumnya SOUTH.

Susun kod sumber seperti berikut:

javac TEDemo.java

Jalankan aplikasi yang disusun seperti berikut:

java TEDemo

Anda harus memerhatikan output berikut:

UTARA Bergerak ke utara BARAT Bergerak ke barat TIMUR Pindah ke timur SELATAN Bergerak ke selatan -3

Hasilnya menunjukkan bahawa toString()kaedah yang diwarisi mengembalikan nama pemalar enum, dan yang NORTHsebelumnya muncul SOUTHdalam perbandingan pemalar enum ini.

Menambah data dan tingkah laku ke enum jenis selamat

Anda boleh menambahkan data (dalam bentuk bidang) dan tingkah laku (dalam bentuk kaedah) ke enum yang jenis. Contohnya, andaikan anda perlu memperkenalkan enum untuk duit syiling Kanada, dan kelas ini mesti menyediakan kaedah untuk mengembalikan jumlah nikel, sen, jumlah, atau dolar yang terdapat dalam jumlah wang sewenang-wenangnya. Penyenaraian 2 menunjukkan kepada anda cara menyelesaikan tugas ini.

Penyenaraian 2: TEDemo.java(versi 2)

enum Coin {NICKEL (5), // pemalar mesti muncul pertama DIME (10), QUARTER (25), DOLLAR (100); // titik koma diperlukan nilai int swastaInPennies; Duit syiling (int valueInPennies) {this.valueInPennies = valueInPennies; } int toCoins (int pennies) {pulangkan wang / valueInPennies; }} kelas awam TEDemo {public static void main (String [] args) {if (args.length! = 1) {System.err.println ("use: java TEDemo numberInPennies"); kembali; } int pennies = Integer.parseInt (args [0]); untuk (int i = 0; i <Coin.values ​​(). length; i ++) System.out.println (pennies + "pennies mengandung" + Coin.values ​​() [i] .toCoins (sen) + "" + Coin .values ​​() [i] .toString (). toLowerCase () + "s"); }}

Penyenaraian 2 pertama menyatakan Coinenum. Senarai pemalar parameter mengenal pasti empat jenis duit syiling. Argumen yang disampaikan kepada setiap pemalar mewakili jumlah wang yang diwakili oleh duit syiling.

Argumen yang diserahkan kepada setiap pemalar sebenarnya diserahkan kepada Coin(int valueInPennies)konstruktor, yang menyimpan argumen dalam valuesInPenniesbidang contoh. Pemboleh ubah ini diakses dari dalam toCoins()kaedah contoh. Ia terbahagi kepada jumlah beberapa sen diserahkan kepada toCoin()'s penniesparameter, dan kaedah ini pulangan hasil, yang berlaku untuk menjadi bilangan syiling dalam denominasi kewangan digambarkan oleh Coinmalar.

Pada ketika ini, anda telah mengetahui bahawa anda boleh menyatakan medan contoh, pembina, dan kaedah contoh dalam enum jenis keselamatan. Lagipun, enum jenis keselamatan pada dasarnya adalah kelas khas Java.

Kaedah TEDemokelas main()terlebih dahulu mengesahkan bahawa argumen baris perintah tunggal telah ditentukan. Argumen ini ditukar menjadi bilangan bulat dengan memanggil kaedah java.lang.Integerkelas parseInt(), yang menguraikan nilai argumen rentetan menjadi integer (atau melemparkan pengecualian apabila input tidak sah dikesan). Saya akan mempunyai lebih banyak maklumat tentang Integerdan kelas sepupunya dalam artikel Java 101 yang akan datang .

Moving forward, main() iterates over Coin’s constants. Because these constants are stored in a Coin[] array, main() evaluates Coin.values().length to determine the length of this array. For each iteration of loop index i, main() evaluates Coin.values()[i] to access the Coin constant. It invokes each of toCoins() and toString() on this constant, which further proves that Coin is a special kind of class.

Compile the source code as follows:

javac TEDemo.java

Run the compiled application as follows:

java TEDemo 198

You should observe the following output:

198 pennies contains 39 nickels 198 pennies contains 19 dimes 198 pennies contains 7 quarters 198 pennies contains 1 dollars

Exploring the Enum class

The Java compiler considers enum to be syntactic sugar. Upon encountering a typesafe enum declaration, it generates a class whose name is specified by the declaration. This class subclasses the abstract Enum class, which serves as the base class for all typesafe enums.

Enum’s formal type parameter list looks ghastly, but it’s not that hard to understand. For example, in the context of Coin extends Enum, you would interpret this formal type parameter list as follows:

  • Any subclass of Enum must supply an actual type argument to Enum. For example, Coin’s header specifies Enum.
  • The actual type argument must be a subclass of Enum. For example, Coin is a subclass of Enum.
  • A subclass of Enum (such as Coin) must follow the idiom that it supplies its own name (Coin) as an actual type argument.

Examine Enum’s Java documentation and you’ll discover that it overrides java.lang.Object's clone(), equals(), finalize(), hashCode(), and toString() methods. Except for toString(), all of these overriding methods are declared final so that they cannot be overridden in a subclass:

  • clone() is overridden to prevent constants from being cloned so that there is never more than one copy of a constant; otherwise, constants could not be compared via == and !=.
  • equals()diganti untuk membandingkan pemalar melalui rujukannya. Pemalar dengan identiti yang sama ( ==) mesti mempunyai kandungan yang sama ( equals()), dan identiti yang berbeza menyiratkan isi yang berbeza.
  • finalize() diganti untuk memastikan bahawa pemalar tidak dapat diselesaikan.
  • hashCode()diganti kerana equals()diganti.
  • toString() diganti untuk mengembalikan nama pemalar.

Enumjuga menyediakan kaedahnya sendiri. Kaedah-kaedah ini termasuk finalcompareTo() ( Enumalat yang java.lang.Comparableinterface), getDeclaringClass(), name(), dan ordinal()kaedah: