Kapan menggunakan kelas vs primitif di Jawa?

54

Saya melihat bahwa Java memiliki Boolean (kelas) vs boolean (primitif). Demikian juga, ada Integer (kelas) vs int (primitif). Apa praktik terbaik tentang kapan menggunakan versi primitif vs kelas? Haruskah saya pada dasarnya selalu menggunakan versi kelas kecuali saya memiliki alasan (kinerja?) Khusus untuk tidak? Apa cara yang paling umum dan diterima untuk menggunakan masing-masing?

Casey Patton
sumber
Menurut saya itu bukan kelas, itu kotak. Mereka hanya ada di sana sehingga Anda dapat menggunakan primitif di mana objek diperlukan, yaitu dalam koleksi. Anda tidak dapat menambahkan dua bilangan bulat (Anda bisa berpura-pura tetapi java benar-benar auto boxing | unboxing nilai untuk Anda).
stonemetal

Jawaban:

47

Dalam Item 5, dari Java Efektif, Joshua Bloch mengatakan

Pelajarannya jelas: lebih suka primitif daripada primitif kotak, dan hati-hati dengan autoboxing yang tidak disengaja .

Salah satu penggunaan yang baik untuk kelas adalah ketika menggunakannya sebagai tipe generik (termasuk kelas Koleksi, seperti daftar dan peta) atau ketika Anda ingin mengubahnya ke tipe lain tanpa casting implisit (misalnya Integerkelas memiliki metode doubleValue()atau byteValue().

Sunting: Alasan Joshua Bloch adalah:

// Hideously slow program! Can you spot the object creation?
public static void main(String[] args) {
    Long sum = 0L;
    for (long i = 0; i < Integer.MAX_VALUE; i++) {
         sum += i;
    }
    System.out.println(sum);
}

Program ini mendapatkan jawaban yang benar, tetapi jauh lebih lambat dari yang seharusnya, karena kesalahan ketik satu karakter. Variabel sumdinyatakan sebagai Longbukan long, yang berarti bahwa program membangun sekitar 2 ^ 31 Longcontoh yang tidak perlu (kira-kira satu untuk setiap kali long iditambahkan ke Long sum). Mengubah deklarasi jumlah dari Longuntuk longmengurangi runtime dari 43 detik menjadi 6,8 detik pada mesin saya.

m3th0dman
sumber
2
Akan membantu jika Anda mencantumkan alasan Bloch, daripada hanya mengutip kesimpulannya!
vaughandroid
@ Baqueta Saya sudah mengedit posting. Alasannya adalah kinerja.
m3th0dman
Itu sedikit lebih jelas, terima kasih. Saya merasa dibenarkan untuk memposting jawaban saya sendiri sekarang. :)
vaughandroid
Anda mungkin menemukan arsitektur lmax menarik - "Butuh sedikit lebih pintar untuk naik urutan besarnya. Ada beberapa hal yang tim LMAX temukan membantu untuk sampai ke sana. Salah satunya adalah menulis implementasi kustom dari koleksi java yang dirancang untuk menjadi cache-friendly dan berhati-hati dengan sampah. Contoh dari ini adalah menggunakan kode java primitif sebagai kunci hashmap dengan implementasi Map yang didukung array khusus yang ditulis . "
Sepertinya sesuatu yang harus ditangani JIT
deFreitas
28

Praktik standar adalah untuk pergi dengan primitif, kecuali jika Anda berurusan dengan obat generik (pastikan Anda sadar akan autoboxing & unboxing !).

Ada sejumlah alasan bagus untuk mengikuti konvensi:

1. Anda menghindari kesalahan sederhana:

Ada beberapa kasus halus, non-intuitif yang sering menangkap pemula. Bahkan coders berpengalaman menyelinap dan membuat kesalahan ini kadang-kadang (mudah-mudahan ini akan diikuti dengan bersumpah ketika mereka men-debug kode dan menemukan kesalahan!).

Kesalahan yang paling umum adalah menggunakan a == balih-alih a.equals(b). Orang-orang terbiasa melakukannya a == bdengan primitif sehingga mudah dilakukan ketika Anda menggunakan pembungkus Obyek.

Integer a = new Integer(2);
Integer b = new Integer(2);
if (a == b) { // Should be a.equals(b)
    // This never gets executed.
}
Integer c = Integer.valueOf(2);
Integer d = Integer.valueOf(2);
if (c == d) { // Should be a.equals(b), but happens to work with these particular values!
    // This will get executed
}
Integer e = 1000;
Integer f = 1000;
if (e == f) { // Should be a.equals(b)
    // Whether this gets executed depends on which compiler you use!
}

2. Keterbacaan:

Perhatikan dua contoh berikut. Kebanyakan orang akan mengatakan yang kedua lebih mudah dibaca.

Integer a = 2;
Integer b = 2;
if (!a.equals(b)) {
    // ...
}
int c = 2;
int d = 2;
if (c != d) {
    // ...
}

3. Kinerja:

Faktanya adalah itu adalah lambat untuk menggunakan pembungkus Obyek untuk primitif daripada hanya menggunakan primitif. Anda menambahkan biaya instantiasi objek, panggilan metode, dll. Untuk hal-hal yang Anda gunakan di semua tempat .

Knuth's "... katakan sekitar 97% dari waktu: optimasi prematur adalah akar dari semua kejahatan" kutipan tidak benar-benar berlaku di sini Dia berbicara tentang optimisasi yang membuat kode (atau sistem) lebih rumit - jika Anda setuju dengan poin # 2, ini adalah optimasi yang membuat kode lebih rumit!

4. Ini konvensi:

Jika Anda membuat pilihan gaya yang berbeda hingga 99% dari programmer Java lain di luar sana, ada 2 kelemahan:

  • Anda akan menemukan kode orang lain lebih sulit untuk dibaca. 99% dari contoh / tutorial / dll di luar sana akan menggunakan primitif. Setiap kali Anda membacanya, Anda akan memiliki pemikiran kognitif tambahan tentang bagaimana penampilannya dengan gaya yang Anda gunakan.
  • Orang lain akan merasa kode Anda lebih sulit dibaca. Setiap kali Anda mengajukan pertanyaan pada Stack Overflow, Anda harus menyaring jawaban / komentar yang menanyakan "mengapa Anda tidak menggunakan primitif?". Jika Anda tidak percaya kepada saya, lihat saja pertarungan yang dimiliki orang-orang mengenai hal-hal seperti penempatan braket, yang bahkan tidak memengaruhi kode yang dihasilkan!

Biasanya saya akan membuat daftar poin tandingan, tapi jujur ​​saya tidak bisa memikirkan alasan bagus untuk tidak mengikuti konvensi di sini!

vaughandroid
sumber
2
Jangan menganjurkan orang untuk membandingkan objek ==. Objek harus dibandingkan dengan equals().
Tulains Córdova
2
@ user61852 Saya tidak menyarankan itu sebagai hal yang harus dilakukan, tetapi sebagai kesalahan umum yang dilakukan! Haruskah saya membuatnya lebih jelas?
vaughandroid
Ya, Anda tidak menyebutkan bahwa objek harus dibandingkan dengan equals()... Anda memberi mereka solusi sehingga membandingkan objek dengan ==menghasilkan hasil yang diharapkan.
Tulains Córdova
Poin bagus. Mengubahnya.
vaughandroid
Saya menambahkan equals()ke potongan kode kedua dan mengubah suara saya.
Tulains Córdova
12

Biasanya saya pergi dengan primitif. Namun, satu kekhasan menggunakan kelas seperti Integerdan Booleanadalah kemungkinan menugaskan nullke variabel-variabel tersebut. Tentu saja, ini berarti Anda harus melakukan nullpemeriksaan setiap saat, tetapi masih lebih baik untuk mendapatkan NullPointerException daripada memiliki kesalahan logika karena menggunakan beberapa intatau booleanvariabel yang belum diinisialisasi dengan benar.

Tentu saja, karena Java 8 Anda dapat (dan mungkin harus) melangkah lebih jauh dan bukannya misalnya IntegerAnda bisa menggunakan Optional<Integer>untuk variabel yang mungkin atau mungkin tidak memiliki nilai.

Selain itu, ia memperkenalkan kemungkinan untuk digunakan nulluntuk menetapkan variabel-variabel itu nilai " tidak dikenal " atau " wildcard ". Ini bisa berguna dalam beberapa situasi, misalnya, dalam Logika Ternary . Atau Anda mungkin ingin memeriksa apakah objek tertentu cocok dengan beberapa templat; dalam hal ini Anda bisa menggunakan nullvariabel-variabel dalam templat yang dapat memiliki nilai apa pun di objek.

tobias_k
sumber
2
(Bukan downvote saya, tapi ...) Java sudah mendeteksi variabel yang tidak diinisialisasi, dan tidak akan membiarkan Anda membaca variabel sampai setiap jalur kode yang mengarah ke penggunaannya telah secara definitif memberikan nilai. Jadi Anda tidak mendapatkan banyak dari kemampuan untuk menetapkan nullsebagai default. Sebaliknya: Anda akan lebih baik tidak "menginisialisasi" variabel sama sekali. Menetapkan nilai default apa pun, bahkan null, menutup kompiler ... tetapi juga membuatnya tidak mendeteksi kurangnya tugas yang berguna di sepanjang semua jalur kode. Dengan demikian, kesalahan yang bisa ditangkap oleh kompiler, lolos ke runtime.
cHao
@ cHao Tapi bagaimana jika tidak ada nilai default yang masuk akal untuk menginisialisasi variabel? Anda bisa mengaturnya ke 0.0, atau -1, atau Integer.MAX_VALUE, atau False, tetapi pada akhirnya Anda tidak tahu apakah itu nilai default, atau nilai aktual yang ditugaskan untuk variabel itu. Dalam kasus di mana ini penting, memiliki nullnilai mungkin lebih jelas.
tobias_k
Ini tidak jelas, hanya lebih mudah untuk memberitahu kompiler untuk tidak memperingatkan Anda tentang logika yang tidak jelas sampai bug telah menyebar. : P Dalam kasus di mana tidak ada default yang masuk akal, jangan menginisialisasi variabel. Hanya atur begitu Anda memiliki nilai yang masuk akal untuk diletakkan di sana. Ini memungkinkan Java menghentikan Anda pada waktu kompilasi jika nilai Anda mungkin tidak diatur dengan benar.
cao
@ cHao maksud saya, mungkin ada kasus di mana Anda tidak dapat menginisialisasi variabel dan Anda harus menghadapinya saat runtime. Dalam kasus seperti itu, "default" seperti "null", yang dapat dengan jelas diidentifikasi sebagai default, atau "tidak diinisiasi", mungkin lebih baik daripada inisialisasi waktu kompilasi yang mungkin juga merupakan nilai yang valid.
tobias_k
Apakah Anda memiliki kasus seperti itu dalam pikiran? Kasus-kasus yang dapat saya pikirkan adalah pada batas antarmuka (misalnya: sebagai parameter atau tipe kembali) ... tetapi bahkan di sana, mereka akan jauh lebih baik jika dibungkus. (Naked nulldatang dengan sejumlah masalah, termasuk null paranoia.) Dalam suatu fungsi, variabel yang mungkin sebenarnya tidak diinisialisasi pada titik penggunaan biasanya menunjukkan kasus yang tidak tertutup. (Analisis penugasan pasti bersifat sederhana, jadi kemungkinan positif yang salah mungkin terjadi. Namun, Anda sering dapat menyelesaikannya dengan menyederhanakan logikanya.)
cHao
2

Dalam kata-kata awam:

Anda menggunakan pembungkus saat Anda perlu menambahkan sesuatu ke koleksi.

Koleksi tidak dapat menampung primitif.

Tulains Córdova
sumber
0

Fitur Java autoboxing seperti yang ditunjukkan oleh m3th0dman. Pikirkan di tingkat serendah mungkin dan Anda akan melihat bahwa autoboxing (masuk atau keluar) nilai primitif akan menyiratkan siklus jam yang dihabiskan dalam beberapa tugas yang tidak Anda butuhkan jika Anda bekerja dengan tipe data asli di sekitar aplikasi Anda.

Sebagai aturan umum, Anda harus mencoba menggunakan tipe data asli bila memungkinkan.

Silvarion
sumber