Gunakan == (atau! =) Untuk Membandingkan Jumlah Java

Sebilangan besar pengembang Java baru dengan cepat mengetahui bahawa mereka biasanya membandingkan Java Strings menggunakan String.equals (Object) daripada menggunakan ==. Ini ditekankan dan diperkuat kepada pembangun baru berulang kali kerana mereka hampir selalu bermaksud membandingkan kandungan String (watak sebenarnya yang membentuk String) daripada identiti String (alamatnya dalam ingatan). Saya berpendapat bahawa kita harus memperkuatkan tanggapan yang ==boleh digunakan dan bukannya Enum.equals (Objek). Saya memberikan alasan saya untuk penegasan ini dalam baki catatan ini.

Terdapat empat sebab yang saya percaya menggunakan ==untuk membandingkan enum Java hampir selalu lebih baik daripada menggunakan kaedah "sama":

  1. Yang ==pada enums menyediakan perbandingan yang sama dijangka (kandungan) sebagaiequals
  2. Yang ==pada enums boleh dikatakan lebih mudah dibaca (kurang lantung) daripadaequals
  3. Yang ==pada enums lebih null selamat daripadaequals
  4. Yang ==pada enums menyediakan menyusun masa (statik) memeriksa bukannya runtime semakan

Sebab kedua yang disenaraikan di atas ("boleh dikatakan lebih mudah dibaca") jelas merupakan masalah pendapat, tetapi bahagian mengenai "kurang verbose" dapat disepakati. Sebab pertama yang saya lebih gemari ==ketika membandingkan enum adalah akibat bagaimana Spesifikasi Bahasa Java menerangkan enum. Bahagian 8.9 ("Jumlah") menyatakan:

Ini adalah kesilapan masa kompilasi untuk berusaha secara jelas menunjukkan jenis enum. Kaedah klon terakhir di Enum memastikan bahawa pemalar enum tidak pernah dapat diklon, dan perlakuan khas oleh mekanisme bersiri memastikan bahawa kejadian pendua tidak pernah dibuat sebagai hasil deserialisasi. Contoh reflektif jenis enum dilarang. Bersama-sama, keempat-empat perkara ini memastikan bahawa tidak ada contoh jenis enum yang wujud melebihi yang ditentukan oleh pemalar enum.

Oleh kerana hanya terdapat satu contoh setiap pemalar enum, dibenarkan menggunakan operator == sebagai ganti kaedah sama dengan membandingkan dua rujukan objek jika diketahui bahawa sekurang-kurangnya satu daripadanya merujuk kepada pemalar enum. (Kaedah sama di Enum adalah kaedah terakhir yang hanya menggunakan super. Sama dengan argumennya dan mengembalikan hasilnya, sehingga melakukan perbandingan identiti.)

Petikan dari spesifikasi yang ditunjukkan di atas menunjukkan dan kemudian secara eksplisit menyatakan bahawa adalah selamat untuk menggunakan ==operator untuk membandingkan dua enum kerana tidak ada cara bahawa terdapat lebih dari satu contoh pemalar enum yang sama.

Kelebihan keempat ==lebih .equalsapabila membandingkan enums mempunyai kaitan dengan keselamatan menyusun masa. Penggunaan ==kekuatan untuk menyusun pemeriksaan masa yang lebih ketat daripada itu .equalskerana Object.equals (Object) mesti, secara kontrak, sewenang-wenangnya Object. Semasa menggunakan bahasa yang ditaip secara statik seperti Java, saya yakin dapat memanfaatkan kelebihan menaip statik ini sebanyak mungkin. Jika tidak, saya akan menggunakan bahasa yang ditaip secara dinamik. Saya percaya bahawa salah satu tema Java Efektif yang berulang adalah: lebih suka memeriksa jenis statik jika boleh.

Sebagai contoh, anggaplah saya mempunyai enum khusus yang dipanggil Fruitdan saya cuba membandingkannya dengan kelas java.awt.Color. Menggunakan ==operator membolehkan saya mendapat ralat waktu kompilasi (termasuk pemberitahuan awal di IDE Java kegemaran saya) mengenai masalah tersebut. Berikut adalah senarai kod yang cuba membandingkan enum khusus dengan kelas JDK menggunakan ==pengendali:

/** * Indicate if provided Color is a watermelon. * * This method's implementation is commented out to avoid a compiler error * that legitimately disallows == to compare two objects that are not and * cannot be the same thing ever. * * @param candidateColor Color that will never be a watermelon. * @return Should never be true. */ public boolean isColorWatermelon(java.awt.Color candidateColor) { // This comparison of Fruit to Color will lead to compiler error: // error: incomparable types: Fruit and Color return Fruit.WATERMELON == candidateColor; } 

Kesalahan penyusun ditunjukkan dalam tangkapan skrin yang akan datang.

Walaupun saya tidak gemar akan kesilapan, saya lebih suka kesalahan itu ditangkap secara statik pada waktu kompilasi dan bukannya bergantung pada liputan waktu proses. Sekiranya saya menggunakan equalskaedah untuk perbandingan ini, kodnya akan dikompilasi dengan baik, tetapi kaedah tersebut akan selalu dikembalikan falsekeliru kerana tidak mungkin dustin.examples.Fruitenum akan sama dengan java.awt.Colorkelas. Saya tidak mengesyorkannya, tetapi berikut adalah kaedah perbandingan menggunakan .equals:

/** * Indicate whether provided Color is a Raspberry. This is utter nonsense * because a Color can never be equal to a Fruit, but the compiler allows this * check and only a runtime determination can indicate that they are not * equal even though they can never be equal. This is how NOT to do things. * * @param candidateColor Color that will never be a raspberry. * @return {@code false}. Always. */ public boolean isColorRaspberry(java.awt.Color candidateColor) { // // DON'T DO THIS: Waste of effort and misleading code!!!!!!!! // return Fruit.RASPBERRY.equals(candidateColor); } 

Perkara "baik" mengenai perkara di atas adalah kekurangan kesilapan masa kompilasi. Ia menyusun dengan indah. Malangnya, ini dibayar dengan harga yang berpotensi tinggi.

Kelebihan terakhir yang saya senaraikan menggunakan ==dan bukannya Enum.equalsmembandingkan enum adalah mengelakkan NullPointerException yang ditakuti. Seperti yang saya nyatakan dalam Pengendalian Java NullPointerException yang Berkesan, saya biasanya ingin mengelakkan perkara yang tidak dijangka NullPointerException. Terdapat sekumpulan situasi yang terhad di mana saya benar-benar ingin keberadaan nol diperlakukan sebagai kes yang luar biasa, tetapi sering kali saya lebih suka melaporkan masalah yang lebih baik. Kelebihan membandingkan enum ==adalah dengan nol boleh dibandingkan dengan enum bukan nol tanpa menemui NullPointerException(NPE). Hasil perbandingan ini, jelas, adalah false.

Salah satu cara untuk mengelakkan NPE semasa menggunakan .equals(Object)adalah menggunakan equalskaedah terhadap pemalar enum atau enum bukan nol yang diketahui dan kemudian meneruskan potensi enum watak yang boleh dipersoalkan (mungkin nol) sebagai parameter kepada equalskaedah tersebut. Ini sering dilakukan selama bertahun-tahun di Jawa dengan String untuk mengelakkan NPE. Walau bagaimanapun, dengan ==pengendali, urutan perbandingan tidak menjadi masalah. Saya suka itu.

Saya telah membuat hujah saya dan sekarang saya beralih ke beberapa contoh kod. Penyenaraian seterusnya adalah merealisasikan enum Fruit hipotesis yang disebutkan sebelumnya.

Buah.java

package dustin.examples; public enum Fruit { APPLE, BANANA, BLACKBERRY, BLUEBERRY, CHERRY, GRAPE, KIWI, MANGO, ORANGE, RASPBERRY, STRAWBERRY, TOMATO, WATERMELON } 

Penyenaraian kod seterusnya adalah kelas Java sederhana yang menyediakan kaedah untuk mengesan jika enum atau objek tertentu adalah buah tertentu. Saya biasanya meletakkan cek seperti ini di enum itu sendiri, tetapi mereka berfungsi lebih baik di kelas yang berasingan di sini untuk tujuan ilustrasi dan demonstrasi saya. Kelas ini termasuk kedua-dua kaedah yang ditunjukkan sebelum ini untuk membandingkan Fruituntuk Colordengan kedua-dua ==dan equals. Sudah tentu, kaedah yang digunakan ==untuk membandingkan enum dengan kelas harus memberi bahagian itu untuk dikompilasi dengan betul.

EnumComparisonMain.java

package dustin.examples; public class EnumComparisonMain { /** * Indicate whether provided fruit is a watermelon ({@code true} or not * ({@code false}). * * @param candidateFruit Fruit that may or may not be a watermelon; null is * perfectly acceptable (bring it on!). * @return {@code true} if provided fruit is watermelon; {@code false} if * provided fruit is NOT a watermelon. */ public boolean isFruitWatermelon(Fruit candidateFruit) { return candidateFruit == Fruit.WATERMELON; } /** * Indicate whether provided object is a Fruit.WATERMELON ({@code true}) or * not ({@code false}). * * @param candidateObject Object that may or may not be a watermelon and may * not even be a Fruit! * @return {@code true} if provided object is a Fruit.WATERMELON; * {@code false} if provided object is not Fruit.WATERMELON. */ public boolean isObjectWatermelon(Object candidateObject) { return candidateObject == Fruit.WATERMELON; } /** * Indicate if provided Color is a watermelon. * * This method's implementation is commented out to avoid a compiler error * that legitimately disallows == to compare two objects that are not and * cannot be the same thing ever. * * @param candidateColor Color that will never be a watermelon. * @return Should never be true. */ public boolean isColorWatermelon(java.awt.Color candidateColor) { // Had to comment out comparison of Fruit to Color to avoid compiler error: // error: incomparable types: Fruit and Color return /*Fruit.WATERMELON == candidateColor*/ false; } /** * Indicate whether provided fruit is a strawberry ({@code true}) or not * ({@code false}). * * @param candidateFruit Fruit that may or may not be a strawberry; null is * perfectly acceptable (bring it on!). * @return {@code true} if provided fruit is strawberry; {@code false} if * provided fruit is NOT strawberry. */ public boolean isFruitStrawberry(Fruit candidateFruit) { return Fruit.STRAWBERRY == candidateFruit; } /** * Indicate whether provided fruit is a raspberry ({@code true}) or not * ({@code false}). * * @param candidateFruit Fruit that may or may not be a raspberry; null is * completely and entirely unacceptable; please don't pass null, please, * please, please. * @return {@code true} if provided fruit is raspberry; {@code false} if * provided fruit is NOT raspberry. */ public boolean isFruitRaspberry(Fruit candidateFruit) { return candidateFruit.equals(Fruit.RASPBERRY); } /** * Indicate whether provided Object is a Fruit.RASPBERRY ({@code true}) or * not ({@code false}). * * @param candidateObject Object that may or may not be a Raspberry and may * or may not even be a Fruit! * @return {@code true} if provided Object is a Fruit.RASPBERRY; {@code false} * if it is not a Fruit or not a raspberry. */ public boolean isObjectRaspberry(Object candidateObject) { return candidateObject.equals(Fruit.RASPBERRY); } /** * Indicate whether provided Color is a Raspberry. This is utter nonsense * because a Color can never be equal to a Fruit, but the compiler allows this * check and only a runtime determination can indicate that they are not * equal even though they can never be equal. This is how NOT to do things. * * @param candidateColor Color that will never be a raspberry. * @return {@code false}. Always. */ public boolean isColorRaspberry(java.awt.Color candidateColor) { // // DON'T DO THIS: Waste of effort and misleading code!!!!!!!! // return Fruit.RASPBERRY.equals(candidateColor); } /** * Indicate whether provided fruit is a grape ({@code true}) or not * ({@code false}). * * @param candidateFruit Fruit that may or may not be a grape; null is * perfectly acceptable (bring it on!). * @return {@code true} if provided fruit is a grape; {@code false} if * provided fruit is NOT a grape. */ public boolean isFruitGrape(Fruit candidateFruit) { return Fruit.GRAPE.equals(candidateFruit); } } 

Saya memutuskan untuk mendekati demonstrasi idea yang ditangkap dalam kaedah di atas melalui ujian unit. Khususnya, saya menggunakan GroovyTestCase Groovy. Kelas itu untuk menggunakan pengujian unit berkuasa Groovy ada dalam senarai kod seterusnya.

EnumComparisonTest.groovy