Berikut adalah daftar 10 praktik pengkodean Java yang lebih halus dari aturan Java Josh Bloch yang efektif. Dibandingkan dengan daftar Josh Bloch yang mudah dipelajari dan berfokus pada situasi harian, daftar ini akan berisi situasi yang melibatkan hal -hal yang tidak biasa dalam desain API/SPI, yang mungkin memiliki dampak besar.
Saya telah menemukan ini saat menulis dan memelihara JOOQ (SQL memodelkan DSL internal di Java). Sebagai DSL internal, Jooq menantang kompiler dan obat generik Java secara luas, menggabungkan obat generik, parameter yang dapat berubah dan kelebihan beban, yang mungkin tidak direkomendasikan oleh Josh Bloch untuk API yang luas.
Izinkan saya berbagi dengan Anda 10 praktik terbaik yang halus untuk pengkodean java:
1. Ingat C ++'s Destructor
Ingat destruktor C ++? Tidak ingat? Maka Anda benar -benar beruntung karena Anda tidak perlu men -debug kebocoran memori karena memori yang dialokasikan setelah penghapusan objek tidak dirilis. Terima kasih kepada Sun/Oracle untuk mekanisme pengumpulan sampah yang diterapkan!
Namun demikian, The Destructor memberikan fitur yang menarik. Itu memahami urutan alokasi terbalik untuk membebaskan memori. Ingatlah bahwa ini adalah kasus di Java, ketika Anda mengoperasikan sintaks kelas destruktor:
Ada berbagai kasus penggunaan lainnya. Berikut adalah contoh konkret tentang cara mengimplementasikan beberapa acara pendengar acara:
@Overridepublic void sebelumnya (EventContext e) {super.beforeEvent (e); // kode super sebelum kode saya} @Overridepublic void afterEvent (eventContext e) {// kode super setelah kode saya super.afterevent (e);} Masalah bersantap filsuf yang terkenal adalah contoh bagus mengapa itu penting. Untuk pertanyaan tentang filsuf makan, silakan periksa tautannya:
http://adit.io/posts/2013-05-11-the-dining-philosophers-problem-with-ron-swanson.html
Aturan : Setiap kali Anda menggunakan sebelum/sesudah, alokasikan/gratis, ambil/kembalikan semantik untuk mengimplementasikan logika, pertimbangkan apakah akan melakukan operasi setelah/gratis/pengembalian dalam urutan terbalik.
Berikan pelanggan metode SPI yang memudahkan mereka untuk menyuntikkan perilaku khusus ke perpustakaan/kode Anda. Berhati -hatilah bahwa penilaian evolusi SPI Anda dapat membingungkan Anda, membuat Anda berpikir Anda (tidak) bermaksud membutuhkan parameter tambahan. Tentu saja, fungsi tidak boleh ditambahkan terlalu dini. Tetapi begitu Anda mempublikasikan SPI Anda, setelah Anda memutuskan untuk mengikuti versi semantik, Anda akan benar -benar menyesal menambahkan parameter tunggal bodoh ke SPI Anda ketika Anda menyadari bahwa dalam beberapa kasus Anda mungkin memerlukan parameter lain:
antarmuka EventListener {// pesan bad void (string message);}Bagaimana jika Anda membutuhkan ID pesan dan sumber pesan? Evolusi API akan mencegah Anda menambahkan parameter ke jenis di atas. Tentu saja, dengan Java 8, Anda dapat menambahkan metode bek yang "membela" keputusan desain Anda yang buruk di masa -masa awal:
Antarmuka EventListener {// Bad Default Void Message (String Message) {Message (Message, Null, Null); } // Lebih baik? batal pesan (pesan string, id integer, sumber pesan sumber);} Perhatikan bahwa sayangnya, metode bek tidak dapat menggunakan pengubah akhir.
Tetapi akan jauh lebih baik untuk menggunakan objek konteks (atau objek parameter) daripada mencemari SPI Anda menggunakan banyak metode.
antarmuka messageContext {string message (); Integer ID (); PesanSource Source ();} Antarmuka EventListener {// Awesome! batal pesan (konteks messageContext);} Anda dapat mengembangkan API MessageContext lebih mudah daripada EventListner SPI, karena beberapa pengguna akan menerapkannya.
Aturan : Setiap kali SPI ditentukan, pertimbangkan untuk menggunakan objek konteks/parameter alih -alih metode penulisan dengan parameter tetap.
Catatan : Ini juga merupakan ide yang baik untuk bertukar hasil melalui tipe Messageresult khusus, yang dapat dibangun menggunakan API Builder. Ini akan sangat meningkatkan fleksibilitas evolusi SPI.
Pemrogram ayunan biasanya hanya menekan beberapa tombol pintas untuk menghasilkan ratusan atau ribuan kelas anonim. Dalam kebanyakan kasus, tidak ada salahnya untuk melakukannya selama antarmuka diikuti dan siklus hidup subtipe SPI tidak dilanggar. Tapi jangan gunakan kelas anonim, lokal atau internal untuk alasan sederhana - mereka akan menyimpan referensi ke kelas eksternal. Karena ke mana pun mereka pergi, kategori eksternal harus mengikuti. Misalnya, jika operasi eksternal kelas lokal tidak tepat, seluruh grafik objek akan mengalami perubahan halus, yang dapat menyebabkan kebocoran memori.
Aturan: Sebelum menulis kelas anonim, lokal atau internal, harap pikirkan dua kali tentang apakah Anda dapat mengubahnya menjadi kelas tingkat atas yang statis atau biasa, sehingga menghindari cara untuk mengembalikan objek mereka ke domain tingkat luar.
Catatan: Gunakan kawat gigi keriting ganda untuk menginisialisasi objek sederhana:
HashMap baru <String, String> () {{put ("1", "a"); put ("2", "b");}}Metode ini menggunakan inisialisasi instance yang dijelaskan dalam spesifikasi JLS §8.6. Itu terlihat bagus di permukaan, tetapi sebenarnya tidak disarankan. Karena jika Anda menggunakan objek hashmap yang sepenuhnya independen, instance tidak akan selalu menyimpan referensi ke objek eksternal. Selain itu, ini akan memungkinkan loader kelas untuk mengelola lebih banyak kelas.
Kecepatan Java8 semakin dekat. Dengan Java 8 membawa ekspresi lambda, apakah Anda suka atau tidak. Meskipun pengguna API Anda mungkin menyukainya, Anda sebaiknya memastikan mereka dapat menggunakannya sesering mungkin. Jadi, kecuali API Anda menerima tipe "skalar" sederhana, seperti int, panjang, string, dan tanggal, biarkan API Anda menerima SAM sesering mungkin.
Apa itu Sam? SAM adalah metode abstrak tunggal [tipe]. Juga dikenal sebagai antarmuka fungsi, itu akan segera dijelaskan sebagai @FunctionalInterface. Ini cocok dengan Aturan 2, EventListener sebenarnya adalah SAM. SAM terbaik hanya memiliki satu parameter, karena ini akan semakin menyederhanakan penulisan ekspresi lambda. Bayangkan menulis
listeners.add (c -> system.out.println (c.message ()));
Untuk mengganti
listeners.add (EventListener baru () {@Override public void pesan (MessageContext c) {System.out.println (c.message ())); }});Bayangkan menangani XML di Joox. Joox berisi banyak Sam:
$ (dokumen) // Temukan elemen dengan id .find (c -> $ (c) .id ()! = null) // Temukan elemen anak -anak mereka .Children (c -> $ (c) .tag (). Equals ("order")) // cetak semua kecocokan .ach (c -> System.out.println ($ (c)))Aturan: Bersikap baik kepada pengguna API Anda, mulailah menulis antarmuka SAM/fungsi mulai sekarang .
CATATAN: Ada banyak blog menarik tentang Java8 Lambda Expressions dan Collections API yang lebih baik:
Saya telah menulis 1 atau 2 artikel tentang Java Nulls, dan juga menjelaskan pengenalan kelas opsional baru di Java 8. Dari sudut pandang akademik atau praktis, topik -topik ini cukup menarik.
Meskipun Null dan NullpointerException masih merupakan cacat di Java pada tahap ini, Anda masih dapat merancang API yang tidak akan memiliki masalah. Saat merancang API, Anda harus menghindari pengembalian nol sebanyak mungkin, karena pengguna Anda dapat memanggil metode dalam rantai:
inisialisasi (someargument) .calculate (data) .dispatch ();
Seperti yang dapat dilihat dari kode di atas, tidak ada metode yang harus mengembalikan null. Faktanya, menggunakan nol biasanya dianggap sebagai heterogenitas yang sebanding. Perpustakaan seperti JQuery atau Joox telah sepenuhnya meninggalkan nol pada objek yang dapat diulang.
NULL biasanya digunakan dalam inisialisasi tertunda. Dalam banyak kasus, inisialisasi yang tertunda juga harus dihindari tanpa mempengaruhi kinerja. Bahkan, jika struktur data yang terlibat terlalu besar, maka tunda inisialisasi harus digunakan dengan hati -hati.
Aturan: Metode harus menghindari pengembalian nol kapan pun mereka. NULL hanya digunakan untuk mewakili semantik "tidak diinisialisasi" atau "tidak ada".
Meskipun tidak apa -apa untuk mengembalikan metode dengan nilai nol dalam beberapa kasus, jangan pernah mengembalikan array kosong atau koleksi kosong! Silakan lihat metode java.io.file.list (), dirancang seperti ini:
Metode ini mengembalikan array string untuk semua file atau direktori di direktori yang ditentukan. Jika direktori kosong, array yang dikembalikan kosong. Kembalikan NULL jika jalur yang ditentukan tidak ada atau kesalahan I/O terjadi.
Oleh karena itu, metode ini biasanya digunakan seperti ini:
File Directory = // ... if (directory.isDirectory ()) {string [] list = directory.list (); if (list! = null) {for (string file: list) {// ...}}}Apakah menurut Anda pemeriksaan nol diperlukan? Sebagian besar operasi I/O akan menghasilkan IOExceptions, tetapi metode ini hanya mengembalikan nol. Null tidak dapat menyimpan pesan kesalahan I/O. Oleh karena itu, desain seperti itu memiliki tiga kekurangan berikut:
Jika Anda melihat masalah dari perspektif koleksi, maka array atau koleksi kosong adalah implementasi terbaik dari "tidak ada". Mengembalikan array atau koleksi kosong tidak memiliki arti penting praktis kecuali digunakan untuk inisialisasi yang tertunda.
Aturan: Array atau koleksi yang dikembalikan tidak boleh nol.
Manfaat HTTP adalah stateless. Semua negara yang relevan ditransfer dalam setiap permintaan dan tanggapan. Ini adalah inti dari penamaan istirahat: transfer negara (transfer negara representasional). Sangat menyenangkan melakukan ini di Java. Pikirkan tentang hal ini dari perspektif Peraturan 2 ketika metode menerima objek parameter negara. Jika negara ditransmisikan melalui objek seperti itu, daripada mengoperasikan keadaan dari luar, segalanya akan lebih mudah. Ambil JDBC sebagai contoh. Contoh berikut membaca kursor dari program yang disimpan.
CallableStatement s = connection.prepareCall("{ ? = ... }");// Verbose manipulation of statement state:s.registerOutParameter(1, cursor);s.setString(2, "abc");s.execute();ResultSet rs = s.getObject(1);// Verbose manipulation of result set state:rs.next();rs.next();Ini membuat JDBC API sangat aneh. Setiap objek stateful dan sulit dioperasikan. Secara khusus, ada dua masalah utama:
Aturan: Lebih banyak implementasi dalam gaya fungsional. Transfer status melalui parameter metode. Sangat sedikit status objek operasional.
Ini adalah cara yang relatif mudah untuk beroperasi. Dalam sistem objek yang relatif kompleks, Anda dapat mencapai peningkatan kinerja yang signifikan, selama Anda pertama kali membuat penilaian yang sama dalam metode Equals () dari semua objek:
@Overridepublic boolean sama (objek lain) {if (this == lainnya) mengembalikan true; // logika penilaian kesetaraan lainnya ...}Perhatikan bahwa pemeriksaan sirkuit pendek lainnya mungkin melibatkan pemeriksaan nilai nol, sehingga mereka juga harus ditambahkan:
@Overridepublic boolean sama (objek lain) {if (this == lainnya) mengembalikan true; if (lain == null) mengembalikan false; // Logika kesetaraan lainnya ...}Aturan: Gunakan sirkuit pendek di semua metode setara () Anda untuk meningkatkan kinerja.
Beberapa orang mungkin tidak setuju dengan ini, karena membuat metode default ke final bertentangan dengan kebiasaan pengembang Java. Tetapi jika Anda memiliki kontrol penuh atas kode, tentu benar untuk membuat metode default ke final:
Ini sangat cocok untuk metode statis, dalam hal ini "overlay" (sebenarnya oklusi) hampir tidak berhasil. Baru -baru ini saya menemukan contoh metode statis naungan yang sangat buruk di Apache Tika. Lihatlah:
TikainputStream memperluas taggedInputStream untuk menutupi metode get statis () dengan implementasi yang relatif berbeda.
Tidak seperti metode konvensional, metode statis tidak dapat ditimpa satu sama lain karena panggilan terikat pada panggilan metode statis pada waktu kompilasi. Jika Anda tidak beruntung, Anda mungkin secara tidak sengaja mendapatkan metode yang salah.
Aturan: Jika Anda memiliki kendali penuh atas API Anda, buat metode sebanyak mungkin default untuk final.
Dalam kesempatan khusus, Anda dapat menggunakan metode parameter variabel "terima-semua" untuk menerima objek ... parameter:
void acceptall (objek ... semua);
Menulis metode seperti itu membawa sedikit nuansa javascript ke ekosistem Java. Tentu saja Anda mungkin ingin membatasi tipe aktual berdasarkan situasi aktual, seperti string…. Karena Anda tidak ingin membatasi terlalu banyak, Anda mungkin berpikir itu ide yang baik untuk menggantikan objek dengan T generik:
void acceptall (t ... all);
Tapi tidak. T akan selalu disimpulkan sebagai objek. Bahkan, Anda mungkin berpikir bahwa obat generik tidak dapat digunakan dalam metode di atas. Lebih penting lagi, Anda mungkin berpikir Anda dapat membebani metode di atas, tetapi Anda tidak dapat:
void acceptall (t ... all); void acceptAll (string pesan, t ... all);
Sepertinya Anda secara opsional dapat menyampaikan pesan string ke metode ini. Tapi apa yang terjadi dengan panggilan ini?
acceptall ("pesan", 123, "ABC");Kompiler menyimpulkan sebagai <? Memperluas serial yang dapat diseriali & sebanding <? >>, Mana yang akan membuat panggilan tidak jelas!
Jadi, setiap kali Anda memiliki tanda tangan "terima-semua" (bahkan jika itu generik), Anda tidak akan pernah bisa mengetiknya secara berlebihan. Konsumen API hanya dapat membiarkan kompiler "secara tidak sengaja" memilih metode "benar" ketika mereka beruntung. Tetapi juga dimungkinkan untuk menggunakan metode penerimaan-semua atau tidak untuk memanggil metode apa pun.
Aturan : Hindari tanda tangan "terima semua" jika memungkinkan. Jika tidak, jangan berlebihan metode seperti itu.
Java adalah binatang buas. Tidak seperti bahasa lain yang lebih idealis, perlahan -lahan berevolusi menjadi seperti sekarang ini. Ini mungkin hal yang baik, karena pada kecepatan pengembangan Java, sudah ada ratusan peringatan, dan peringatan ini hanya dapat dipahami selama bertahun -tahun pengalaman.
Nantikan lebih dari sepuluh daftar teratas tentang topik ini!
Tautan Asli: Terjemahan JOOQ: ImportNew.com - Liken
Tautan Terjemahan: http://www.importnew.com/10138.html
[Harap simpan sumber asli, penerjemah, dan tautan terjemahan saat mencetak ulang. ]