Primitif Java versus Objek

1. Ikhtisar

Dalam tutorial ini, kami menunjukkan pro dan kontra penggunaan tipe primitif Java dan padanannya yang dibungkus.

2. Sistem Jenis Java

Java memiliki sistem tipe dua kali lipat yang terdiri dari primitif seperti int , boolean dan tipe referensi seperti Integer, Boolean . Setiap tipe primitif sesuai dengan tipe referensi.

Setiap objek berisi satu nilai dari tipe primitif terkait. The kelas wrapper yang berubah (sehingga negara mereka tidak dapat mengubah sekali objek tersebut dibangun) dan akhir (sehingga kita tidak dapat mewarisi dari mereka).

Di balik terpal, Java melakukan konversi antara tipe primitif dan referensi jika tipe sebenarnya berbeda dari tipe yang dideklarasikan:

Integer j = 1; // autoboxing int i = new Integer(1); // unboxing 

Proses mengubah tipe primitif menjadi referensi disebut autoboxing, proses sebaliknya disebut unboxing.

3. Pro dan Kontra

Keputusan objek apa yang akan digunakan didasarkan pada kinerja aplikasi apa yang kita coba capai, berapa banyak memori yang tersedia yang kita miliki, jumlah memori yang tersedia dan nilai default apa yang harus kita tangani.

Jika kita tidak menghadapi satu pun dari itu, kita dapat mengabaikan pertimbangan ini meskipun perlu diketahui.

3.1. Jejak Memori Item Tunggal

Hanya untuk referensi, variabel tipe primitif memiliki dampak berikut pada memori:

  • boolean - 1 bit
  • byte - 8 bit
  • pendek, char - 16 bit
  • int, float - 32 bit
  • panjang, ganda - 64 bit

Dalam praktiknya, nilai-nilai ini dapat bervariasi bergantung pada implementasi Mesin Virtual. Di VM Oracle, jenis boolean, misalnya, dipetakan ke nilai int 0 dan 1, sehingga dibutuhkan 32 bit, seperti yang dijelaskan di sini: Jenis dan Nilai Primitif.

Variabel jenis ini hidup di tumpukan dan karenanya diakses dengan cepat. Untuk detailnya, kami merekomendasikan tutorial kami tentang model memori Java.

Jenis referensi adalah objek, mereka tinggal di heap dan relatif lambat untuk diakses. Mereka memiliki overhead tertentu terkait rekan primitif mereka.

Nilai konkret dari overhead pada umumnya spesifik JVM. Di sini, kami menyajikan hasil untuk mesin virtual 64-bit dengan parameter berikut:

java 10.0.1 2018-04-17 Java(TM) SE Runtime Environment 18.3 (build 10.0.1+10) Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10.0.1+10, mixed mode)

Untuk mendapatkan struktur internal objek, kita dapat menggunakan alat Tata Letak Objek Java (lihat tutorial kami yang lain tentang cara mendapatkan ukuran objek).

Ternyata satu instance dari tipe referensi pada JVM ini menempati 128 bit kecuali Long dan Double yang menempati 192 bit:

  • Boolean - 128 bit
  • Byte - 128 bit
  • Pendek, Karakter - 128 bit
  • Integer, Float - 128 bit
  • Panjang, Ganda - 192 bit

Kita dapat melihat bahwa satu variabel tipe Boolean menempati ruang sebanyak 128 variabel primitif, sedangkan satu variabel Integer menempati ruang sebanyak empat variabel int .

3.2. Jejak Memori untuk Array

Situasi menjadi lebih menarik jika kita membandingkan berapa banyak memori yang menempati array dari tipe yang dipertimbangkan.

Saat kami membuat array dengan berbagai jumlah elemen untuk setiap jenis, kami mendapatkan plot:

yang mendemonstrasikan bahwa tipe-tipe dikelompokkan menjadi empat keluarga sehubungan dengan bagaimana memori m (s) bergantung pada jumlah elemen dari array:

  • panjang, ganda: m (s) = 128 + 64 s
  • pendek, karakter: m (s) = 128 + 64 [s / 4]
  • byte, boolean: m (s) = 128 + 64 [s / 8]
  • sisanya: m (s) = 128 + 64 [s / 2]

di mana tanda kurung siku menunjukkan fungsi langit-langit standar.

Anehnya, array tipe primitif long dan double mengkonsumsi lebih banyak memori daripada kelas pembungkusnya Long dan Double .

Kita bisa melihat bahwa array elemen tunggal dari tipe primitif hampir selalu lebih mahal (kecuali untuk panjang dan ganda) daripada tipe referensi yang sesuai .

3.3. Performa

Performa kode Java adalah masalah yang tidak kentara, hal itu sangat bergantung pada perangkat keras tempat kode dijalankan, pada kompiler yang mungkin melakukan pengoptimalan tertentu, pada status mesin virtual, pada aktivitas proses lain di sistem operasi.

Seperti yang telah kami sebutkan, tipe primitif tinggal di tumpukan sementara tipe referensi tinggal di heap. Ini adalah faktor dominan yang menentukan seberapa cepat objek diakses.

Untuk mendemonstrasikan seberapa banyak operasi untuk tipe primitif lebih cepat daripada operasi untuk kelas pembungkus, mari buat lima juta elemen array di mana semua elemen sama kecuali yang terakhir; lalu kami melakukan pencarian untuk elemen itu:

while (!pivot.equals(elements[index])) { index++; }

and compare the performance of this operation for the case when the array contains variables of the primitive types and for the case when it contains objects of the reference types.

We use the well-known JMH benchmarking tool (see our tutorial on how to use it), and the results of the lookup operation can be summarized in this chart:

Even for such a simple operation, we can see that it's required more time to perform the operation for wrapper classes.

In case of more complicated operations like summation, multiplication or division, the difference in speed might skyrocket.

3.4. Default Values

Default values of the primitive types are 0 (in the corresponding representation, i.e. 0, 0.0d etc) for numeric types, false for the boolean type, \u0000 for the char type. For the wrapper classes, the default value is null.

It means that the primitive types may acquire values only from their domains, while the reference types might acquire a value (null) that in some sense doesn't belong to their domains.

Though it isn't considered a good practice to leave variables uninitialized, sometimes we might assign a value after its creation.

In such a situation, when a primitive type variable has a value that is equal to its type default one, we should find out whether the variable has been really initialized.

There's no such a problem with a wrapper class variables since the null value is quite an evident indication that the variable hasn't been initialized.

4. Usage

As we've seen, the primitive types are much faster and require much less memory. Therefore, we might want to prefer using them.

On the other hand, current Java language specification doesn't allow usage of primitive types in the parametrized types (generics), in the Java collections or the Reflection API.

Ketika aplikasi kita membutuhkan koleksi dengan banyak elemen, kita harus mempertimbangkan penggunaan array dengan tipe yang lebih “ekonomis”, seperti yang diilustrasikan pada plot di atas.

5. Kesimpulan

Dalam tutorial ini, kami mengilustrasikan bahwa objek di Java lebih lambat dan memiliki dampak memori yang lebih besar daripada analog primitifnya.

Seperti biasa, potongan kode dapat ditemukan di repositori kami di GitHub.