Cara menggunakan enum typesafe di Java

Kode Java yang menggunakan tipe enumerasi tradisional bermasalah. Java 5 memberi kami alternatif yang lebih baik dalam bentuk enum yang aman. Pada artikel ini, saya memperkenalkan Anda ke enumerated types dan typesafe enum, menunjukkan kepada Anda bagaimana mendeklarasikan enum typesafe dan menggunakannya dalam pernyataan switch, dan mendiskusikan penyesuaian enum typesafe dengan menambahkan data dan behaviors. Saya mengakhiri artikel dengan menjelajahi kelas.java.lang.Enum

unduh Dapatkan kodenya Unduh kode sumber untuk contoh dalam tutorial Java 101 ini. Dibuat oleh Jeff Friesen untuk JavaWorld /.

Dari jenis yang disebutkan hingga enum yang aman

Sebuah jenis disebutkan menetapkan satu set konstanta terkait sebagai nilai-nilainya. Contohnya termasuk minggu hari, arah kompas utara / selatan / timur / barat standar, denominasi koin mata uang, dan jenis token penganalisis leksikal.

Tipe terenumerasi secara tradisional telah diimplementasikan sebagai urutan konstanta integer, yang ditunjukkan oleh rangkaian konstanta arah berikut:

static final int DIR_NORTH = 0; static final int DIR_WEST = 1; static final int DIR_EAST = 2; static final int DIR_SOUTH = 3;

Ada beberapa masalah dengan pendekatan ini:

  • Kurangnya keamanan tipe: Karena konstanta tipe yang disebutkan hanyalah bilangan bulat, bilangan bulat apa pun dapat ditentukan di mana konstanta diperlukan. Selanjutnya, penjumlahan, pengurangan, dan operasi matematika lainnya dapat dilakukan pada konstanta ini; misalnya, (DIR_NORTH + DIR_EAST) / DIR_SOUTH), yang tidak ada artinya.
  • Namespace tidak ada: Konstanta tipe enumerasi harus diawali dengan beberapa jenis (semoga) pengenal unik (misalnya, DIR_) untuk mencegah benturan dengan konstanta tipe enumerasi lain.
  • Kerapuhan: Karena konstanta tipe enumerasi dikompilasi ke dalam file kelas tempat nilai literalnya disimpan (dalam kumpulan konstan), mengubah nilai konstanta mengharuskan file kelas ini dan file kelas aplikasi yang bergantung padanya untuk dibangun kembali. Jika tidak, perilaku yang tidak ditentukan akan terjadi pada waktu proses.
  • Kekurangan informasi: Ketika sebuah konstanta dicetak, nilai integernya akan keluar. Output ini tidak memberi tahu Anda apa pun tentang apa yang diwakili oleh nilai integer. Ia bahkan tidak mengidentifikasi tipe enumerasi yang memiliki konstanta.

Anda dapat menghindari masalah "kurangnya keamanan tipe" dan "kurangnya informasi" dengan menggunakan java.lang.Stringkonstanta. Misalnya, Anda mungkin menentukan static final String DIR_NORTH = "NORTH";. Meskipun nilai konstanta lebih bermakna, Stringkonstanta berbasis masih mengalami masalah "namespace tidak hadir" dan kerapuhan. Selain itu, tidak seperti perbandingan integer, Anda tidak dapat membandingkan nilai string dengan operator ==and !=(yang hanya membandingkan referensi).

Masalah-masalah ini menyebabkan pengembang menciptakan alternatif berbasis kelas yang dikenal sebagai Enum Typeafe . Pola ini telah dideskripsikan dan dikritik secara luas. Joshua Bloch memperkenalkan pola pada Butir 21 dari Panduan Bahasa Pemrograman Java Efektif miliknya (Addison-Wesley, 2001) dan mencatat bahwa pola tersebut memiliki beberapa masalah; yaitu canggung untuk menggabungkan konstanta enum typesafe ke dalam kumpulan, dan konstanta enumerasi tidak dapat digunakan dalam switchpernyataan.

Perhatikan contoh pola enum aman tipe berikut. The Suitkelas menunjukkan bagaimana Anda dapat menggunakan alternatif berbasis kelas untuk memperkenalkan jenis dicacah yang menggambarkan empat sesuai kartu (klub, berlian, hati, dan sekop):

setelan kelas akhir publik // Seharusnya tidak dapat setelan subkelas. {CLUBS Suit akhir public static = Suit baru (); Gugatan akhir public static DIAMONDS = Suit baru (); HATI Gugatan publik static final = Suit baru (); SPADES setelan akhir public static = Suit baru (); private Suit () {} // Seharusnya tidak bisa memasukkan konstanta tambahan. }

Untuk menggunakan kelas ini, Anda akan memperkenalkan Suitvariabel dan menugaskannya ke salah satu Suitkonstanta, sebagai berikut:

Setelan jas = Suit.DIAMONDS;

Anda mungkin ingin menginterogasi suitdalam switchpernyataan seperti ini:

switch (suit) {case Suit.CLUBS: System.out.println ("klub"); istirahat; case Suit.DIAMONDS: System.out.println ("berlian"); istirahat; case Suit.HEARTS: System.out.println ("heart"); istirahat; case Suit.SPADES: System.out.println ("sekop"); }

Namun, ketika kompilator Java menemukan Suit.CLUBS, ia melaporkan kesalahan yang menyatakan bahwa ekspresi konstan diperlukan. Anda dapat mencoba mengatasi masalah tersebut sebagai berikut:

switch (suit) {case CLUBS: System.out.println ("klub"); istirahat; case DIAMONDS: System.out.println ("berlian"); istirahat; case HEARTS: System.out.println ("heart"); istirahat; case SPADES: System.out.println ("sekop"); }

Namun, ketika kompilator bertemu CLUBS, ia akan melaporkan kesalahan yang menyatakan bahwa ia tidak dapat menemukan simbolnya. Dan bahkan jika Anda menempatkan Suitdalam sebuah paket, mengimpor paket, dan mengimpor konstanta ini secara statis, kompilator akan mengeluh bahwa ia tidak dapat dikonversi Suitke intketika bertemu suitdi switch(suit). Mengenai masing-masing case, kompilator juga akan melaporkan bahwa ekspresi konstan diperlukan.

Java tidak mendukung pola Typeafe Enum dengan switchpernyataan. Namun, itu memperkenalkan fitur bahasa enum yang aman untuk merangkum manfaat pola sambil menyelesaikan masalahnya, dan fitur ini mendukung switch.

Mendeklarasikan enum aman jenis dan menggunakannya dalam pernyataan sakelar

Deklarasi enum aman tipe sederhana dalam kode Java terlihat seperti padanannya dalam bahasa C, C ++, dan C #:

enum Arah {NORTH, WEST, EAST, SOUTH}

Deklarasi ini menggunakan kata kunci enumuntuk diperkenalkan Directionsebagai enum jenis aman (jenis kelas khusus), di mana metode arbitrer dapat ditambahkan dan antarmuka arbitrer dapat diterapkan. The NORTH, WEST, EAST, dan SOUTHkonstanta enum diimplementasikan sebagai badan kelas konstan-spesifik yang mendefinisikan kelas anonim memperluas melampirkan Directionkelas.

Directiondan enum typesafe lainnya memperpanjang  dan mewarisi berbagai metode, termasuk , , dan , dari kelas ini. Kami akan menjelajahi nanti di artikel ini.Enum values()toString()compareTo()Enum

Kode 1 mendeklarasikan enum tersebut dan menggunakannya dalam sebuah switchpernyataan. Ini juga menunjukkan bagaimana membandingkan dua konstanta enum, untuk menentukan konstanta mana yang datang sebelum konstanta lainnya.

Daftar 1: TEDemo.java(versi 1)

public class 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); switch (d) {case NORTH: System.out.println ("Move north"); istirahat; case WEST: System.out.println ("Pindah ke barat"); istirahat; case EAST: System.out.println ("Pindah ke timur"); istirahat; case SELATAN: System.out.println ("Pindah ke selatan"); istirahat; default: assert false: "arah tidak diketahui"; }} System.out.println (Direction.NORTH.compareTo (Direction.SOUTH)); }}

Kode 1 mendeklarasikan Directionenum typesafe dan mengulangi anggota konstannya, yang values()mengembalikan. Untuk setiap nilai, switchpernyataan (ditingkatkan untuk mendukung enum yang aman) memilih caseyang sesuai dengan nilai  d dan mengeluarkan pesan yang sesuai. (Anda tidak mengawali konstanta enum, misalnya NORTH, dengan tipe enumnya.) Terakhir, Listing 1 mengevaluasi Direction.NORTH.compareTo(Direction.SOUTH)untuk menentukan apakah NORTHdatang sebelumnya SOUTH.

Kompilasi kode sumber sebagai berikut:

javac TEDemo.java

Jalankan aplikasi yang dikompilasi sebagai berikut:

java TEDemo

Anda harus mengamati keluaran berikut:

UTARA Pindah utara WEST Pindah ke barat TIMUR Pindah timur SELATAN Pindah ke selatan -3

Keluarannya menunjukkan bahwa toString()metode yang diwarisi mengembalikan nama konstanta enum, dan yang NORTHmuncul sebelumnya SOUTHdalam perbandingan konstanta enum ini.

Menambahkan data dan perilaku ke enum yang aman

Anda dapat menambahkan data (dalam bentuk kolom) dan perilaku (dalam bentuk metode) ke enum typesafe. Misalnya, Anda perlu memasukkan enum untuk koin Kanada, dan kelas ini harus menyediakan sarana untuk mengembalikan jumlah nikel, sen, seperempat, atau dolar yang terkandung dalam jumlah sen yang berubah-ubah. Kode 2 menunjukkan kepada Anda bagaimana menyelesaikan tugas ini.

Daftar 2: TEDemo.java(versi 2)

enum Coin {NICKEL (5), // konstanta harus muncul terlebih dahulu DIME (10), QUARTER (25), DOLLAR (100); // titik koma diperlukan private final int valueInPennies; Koin (int valueInPennies) {this.valueInPennies = valueInPennies; } int toCoins (int pennies) {return pennies / valueInPennies; }} TEDemo kelas publik {public static void main (String [] args) {if (args.length! = 1) {System.err.println ("penggunaan: java TEDemo numberInPennies"); kembali; } int pennies = Integer.parseInt (args [0]); untuk (int i = 0; i <Coin.values ​​(). length; i ++) System.out.println (sen + "pennies berisi" + Coin.values ​​() [i] .toCoins (pennies) + "" + Coin .values ​​() [i] .toString (). toLowerCase () + "s"); }}

Kode 2 pertama kali menyatakan Coinenum. Daftar konstanta berparameter mengidentifikasi empat jenis koin. Argumen yang diteruskan ke setiap konstanta mewakili jumlah sen yang diwakili oleh koin.

Argumen yang diteruskan ke setiap konstanta sebenarnya diteruskan ke Coin(int valueInPennies)konstruktor, yang menyimpan argumen di valuesInPenniesbidang instance. Variabel ini diakses dari dalam toCoins()metode instance. Ini terbagi menjadi jumlah uang yang dikirimkan ke toCoin()'s penniesparameter, dan metode ini kembali hasil, yang kebetulan jumlah koin dalam denominasi moneter dijelaskan oleh Coinkonstan.

Pada titik ini, Anda telah menemukan bahwa Anda dapat mendeklarasikan kolom instance, konstruktor, dan metode instance dalam enum yang aman. Lagipula, enum jenis aman pada dasarnya adalah jenis kelas Java khusus.

Metode TEDemokelas main()pertama-tama memverifikasi bahwa argumen baris perintah tunggal telah ditentukan. Argumen ini diubah menjadi bilangan bulat dengan memanggil metode java.lang.Integerkelas parseInt(), yang mengurai nilai argumen stringnya menjadi bilangan bulat (atau memunculkan pengecualian saat masukan yang tidak valid terdeteksi). Saya akan berbicara lebih banyak tentang Integerdan kelas sepupunya di artikel Java 101 mendatang .

Bergerak maju, main()mengulangi Coinkonstanta. Karena konstanta ini disimpan dalam Coin[]larik, main()evaluasi Coin.values().lengthuntuk menentukan panjang larik ini. Untuk setiap iterasi indeks loop i, main()mengevaluasi Coin.values()[i]untuk mengakses Coinkonstanta. Itu memanggil masing-masing toCoins()dan toString()pada konstanta ini, yang selanjutnya membuktikan bahwa itu Coinadalah jenis kelas khusus.

Kompilasi kode sumber sebagai berikut:

javac TEDemo.java

Jalankan aplikasi yang dikompilasi sebagai berikut:

java TEDemo 198

Anda harus mengamati keluaran berikut:

198 pennies berisi 39 nikel 198 pennies berisi 19 dime 198 pennies berisi 7 perempat 198 pennies berisi 1 dollar

Menjelajahi kelasEnum

Compiler Java dianggap enumsebagai gula sintaksis. Setelah menemukan deklarasi enum yang aman, ia menghasilkan kelas yang namanya ditentukan oleh deklarasi tersebut. Kelas ini membuat subkelas dari kelas abstrak , yang berfungsi sebagai kelas dasar untuk semua enum typeafe.Enum

EnumDaftar parameter tipe formal terlihat mengerikan, tetapi tidak terlalu sulit untuk dipahami. Misalnya, dalam konteks Coin extends Enum, Anda akan menafsirkan daftar parameter tipe formal ini sebagai berikut:

  • Setiap subclass dari Enumharus menyediakan argumen tipe aktual ke Enum. Misalnya, Cointajuk menentukan Enum.
  • Argumen tipe sebenarnya harus merupakan subclass dari Enum. Misalnya, Coinadalah subclass dari 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 konstanta melalui referensinya. Konstanta dengan identitas yang sama ( ==) harus memiliki konten yang sama ( equals()), dan identitas yang berbeda menyiratkan konten yang berbeda.
  • finalize() diganti untuk memastikan bahwa konstanta tidak dapat diselesaikan.
  • hashCode()diganti karena equals()diganti.
  • toString() diganti untuk mengembalikan nama konstanta.

Enumjuga menyediakan metodenya sendiri. Metode ini meliputi finalcompareTo() ( Enumalat yang java.lang.Comparableinterface), getDeclaringClass(), name(), dan ordinal()metode: