Nilai di Java memiliki tipe yang tetap sejak dideklarasikan, tetapi nilainya boleh berpindah antartipe melalui konversi. Topik ini memilah konversi menjadi dua arah, yaitu widening yang berjalan otomatis karena tujuannya lebih luas, dan narrowing yang wajib ditulis sebagai casting karena berisiko memangkas informasi. Perilaku overflow pada bilangan bulat ditutup dengan contoh nyata, sehingga hasil yang terlihat aneh dapat dijelaskan mekanismenya dan tidak dikira bug.
Dua arah konversi tipe#
Konversi widening memindahkan nilai ke tipe yang jangkauannya lebih luas, misalnya int ke long atau long ke double, dan kompiler melakukannya tanpa diminta. Konversi narrowing bergerak sebaliknya, misalnya double ke int, dan wajib ditulis eksplisit dalam tanda kurung, misalnya (int) nilai. Pembeda keduanya bukan selera, melainkan risiko kehilangan informasi, karena penyempitan bisa memangkas desimal, memotong bit, atau mengubah nilai sama sekali.
Kompiler menegakkan pembedaan ini dengan tegas. Baris penugasan yang mempersempit tanpa casting ditolak dengan pesan possible lossy conversion, sedangkan pelebaran tidak pernah memunculkan peringatan apa pun. Dengan begitu, setiap baris yang berisiko kehilangan informasi selalu tampak jelas di dalam kode.
Gambar berikut memetakan jalan konversi otomatis antartipe primitif bilangan. Pelebaran mengikuti arah panah, sedangkan setiap lompatan berlawanan arah memerlukan casting yang ditulis sendiri.
Panah byte menuju short menuju int menuju long terlihat wajar, tetapi ada dua jalur yang perlu dicermati. char ikut berkonversi otomatis ke int karena yang disimpannya adalah kode karakter, dan long menuju float tetap dihitung pelebaran meskipun bisa kehilangan presisi, karena jangkauan float memang lebih luas.
Konversi otomatis ke tipe yang lebih luas#
Konversi otomatis terjadi setiap kali nilai bertipe sempit diberikan ke variabel bertipe lebih luas, baik pada deklarasi, penugasan, maupun pemanggilan metode. Kompiler memeriksa arahnya terhadap aturan pelebaran, dan bila jalurnya sah, tidak ada notasi tambahan yang diminta sama sekali.
public class KonversiLebar {
public static void main(String[] args) {
int jumlah = 1300;
long jumlahLuas = jumlah; // int ke long, otomatis
double rasio = jumlah; // int ke double, otomatis
char huruf = 'A';
int kodeHuruf = huruf; // char ke int, otomatis
long besar = 2_000_000_001L;
float kecil = besar; // long ke float, otomatis tetapi kehilangan presisi
System.out.println("int : " + jumlah);
System.out.println("long : " + jumlahLuas);
System.out.println("double : " + rasio);
System.out.println("char : " + huruf);
System.out.println("kode huruf : " + kodeHuruf);
System.out.println("float : " + kecil);
}
}int : 1300
long : 1300
double : 1300.0
char : A
kode huruf : 65
float : 2.0E9Empat baris pertama berjalan tanpa kejutan, termasuk char berisi A yang menjadi 65 karena yang dipindahkan adalah kode Unicodenya. Baris terakhir menunjukkan sisi yang jarang disangka, yaitu long berisi 2000000001 yang menjadi 2.0E9 setelah berpindah ke float. Konversi ini tetap sah sebagai pelebaran karena jangkauan float lebih luas daripada long, tetapi float hanya menyimpan sekitar tujuh digit signifikan, sehingga angka satu di posisi terakhir hilang, dan 2.0E9 adalah notasi ilmiah untuk 2000000000.
Pelebaran juga bekerja diam-diam di dalam ekspresi campuran. Ketika int dijumlahkan dengan long, operand int dinaikkan menjadi long terlebih dahulu, dan hasilnya pun bertipe long. Mekanisme inilah yang membuat penulisan 365L * 24 * 60 * 60 * 100 pada bagian luapan efektif, karena satu operand long menaikkan seluruh perhitungannya sebelum perkalian dilakukan.
Penyempitan dengan casting eksplisit#
Casting eksplisit ditulis dengan menaruh nama tipe dalam tanda kurung di depan nilainya, misalnya (int) atau (byte). Bentuk ini menyatakan bahwa penyempitan disengaja, sehingga kompiler tidak menolak dan tidak memberi peringatan, sementara risikonya ditanggung penulisnya. Tanpa cast, baris seperti int x = 91.7; ditolak kompiler dengan pesan possible lossy conversion, sehingga penyempitan tidak pernah terjadi tanpa sengaja. Tiga perilaku yang perlu dipegang dari contoh berikut adalah pemangkasan ke arah nol, pemotongan bit, dan perputaran nilai.
public class KonversiSempit {
public static void main(String[] args) {
double nilaiMentah = 91.7;
int nilaiBulat = (int) nilaiMentah; // desimal dipangkas, bukan dibulatkan
double minus = -91.7;
int dipangkas = (int) minus; // dipangkas ke arah nol
long jumlahPenduduk = 8_100_000_000L;
int jumlahInt = (int) jumlahPenduduk; // melebihi batas int, nilainya bergeser
int nilai = 200;
byte level = (byte) nilai; // melewati batas byte
System.out.println("(int) 91.7 : " + nilaiBulat);
System.out.println("(int) -91.7 : " + dipangkas);
System.out.println("(int) 8100000000L : " + jumlahInt);
System.out.println("(byte) 200 : " + level);
}
}(int) 91.7 : 91
(int) -91.7 : -91
(int) 8100000000L : -489934592
(byte) 200 : -56Pemangkasan berlaku untuk pecahan, yaitu (int) 91.7 menghasilkan 91 dan (int) -91.7 menghasilkan -91, jadi selalu ke arah nol dan bukan ke bilangan terdekat. Pemotongan berlaku saat nilainya melampaui jangkauan tujuan, yaitu 8100000000 yang tidak muat dalam int menghasilkan -489934592 karena hanya 32 bit terakhir yang diambil. Perputaran serupa terjadi pada (byte) 200 yang melewati jangkauan byte dari -128 sampai 127 dan berpaling menjadi -56.
Luapan nilai pada bilangan bulat#
Aritmetika bilangan bulat di Java tidak pernah melampaui jangkauan tipenya, karena hasilnya berputar dalam representasi pelengkap dua, perilaku yang disebut overflow. Penjumlahan dua int tetap menghasilkan int, sehingga bila hasilnya melewati 2147483647, angka itu melompat ke nilai paling negatif tanpa error apa pun.
public class LuapanInteger {
public static void main(String[] args) {
int maks = Integer.MAX_VALUE;
System.out.println("Nilai maksimum int : " + maks);
System.out.println("maks + 1 : " + (maks + 1));
System.out.println("maks + 2 : " + (maks + 2));
}
}Nilai maksimum int : 2147483647
maks + 1 : -2147483648
maks + 2 : -2147483647Cara paling sederhana menghindari luapan adalah memperluas tipe hitungannya. Menambahkan akhiran L pada salah satu operand, misalnya 365L * 24 * 60 * 60 * 100, membuat seluruh perhitungan berjalan dalam long dengan jangkauan hingga 9223372036854775807. Bila luapan justru harus terdeteksi, metode Math.addExact dan Math.multiplyExact melempar exception alih-alih berputar diam-diam.
Perputaran ini bukan perilaku satu JVM tertentu, melainkan aturan spesifikasi bahasa, sehingga hasilnya identik di platform mana pun program dijalankan. Konstanta Integer.MAX_VALUE dan Long.MAX_VALUE menyediakan batas itu tanpa perlu dihafal, dan keduanya dipakai pada contoh di atas.
Menulis casting secara sadar#
Casting yang sehat punya alasan yang bisa diucapkan dalam satu kalimat. Memangkas desimal karena jumlah orang tidak mungkin pecahan, mengambil kode karakter, atau memaksa pembagian pecahan dengan (double), semuanya alasan yang jelas. Contoh berikut memperlihatkan pola terakhir, yaitu mengubah satu operand menjadi double agar pembagiannya menghasilkan pecahan.
// tidak-dijalankan
int totalNilai = 17;
int jumlahSoal = 5;
double rataRata = (double) totalNilai / jumlahSoal; // 3.4, bukan 3Casting yang sebaiknya ditunda adalah yang tujuannya membungkam kompiler tanpa memeriksa nilainya. Menyimpan (byte) 200 agar barisnya lolos kompilasi hanya memindahkan masalah ke saat program berjalan, dengan nilai yang sudah tidak bermakna. Sebelum menulis cast penyempitan, pastikan jangkauan nilainya diketahui, misalnya skor yang memang berada di antara 0 sampai 100.
Kebiasaan yang menolong adalah memilih tipe sejak deklarasi sehingga banyak cast tidak perlu ditulis. Menyimpan rata-rata dalam double sejak awal menghilangkan cast berulang, sedangkan menyimpan jumlah orang dalam int menghilangkan godaan memangkas desimalnya. Casting yang tersisa dalam kode pun menjadi sinyal yang mudah diaudit, karena jumlahnya sedikit dan setiap barisnya punya alasan yang bisa dibaca.
Kesalahan umum#
- Mengira
(int)membulatkan ke bilangan terdekat. Hasilnya selalu dipangkas ke arah nol, sehingga(int) 91.7bernilai 91 dan(int) -91.7bernilai -91. GunakanMath.rounduntuk pembulatan, dengan catatan hasilnya bertipelong. - Menyimpan hasil penjumlahan dua
byteataushortkembali ke tipenya tanpa casting. Ekspresia + bdengan duabytedipromosikan menjadiintlebih dahulu, sehinggabyte c = a + b;ditolak kompiler dengan pesanincompatible types. Simpan hasilnya keintatau tulis(byte)secara sadar. - Menghitung nilai besar dalam
int, misalnya jumlah detik seratus tahun melalui60 * 60 * 24 * 365 * 100. Semua operandnya bertipeintsehingga hasilnya meluap dan berpaling negatif. Tambahkan akhiranLpada salah satu operand agar hitungan berjalan dalamlong. - Menganggap pelebaran selalu mempertahankan nilai persis. Konversi
longmenujufloatsah sebagai pelebaran, tetapi 2000000001 berubah menjadi 2000000000 karena presisifloatterbatas.
Ringkasan#
- Konversi otomatis (widening) mengikuti jalur
byte,short,int,long,float,doublesertacharkeint, dan berjalan tanpa notasi tambahan. - Konversi penyempitan (narrowing) wajib ditulis sebagai casting, memangkas pecahan ke arah nol, dan memotong bit saat nilai melampaui jangkauan tujuan.
- Aritmetika bilangan bulat meluap tanpa error, sehingga hitungan besar memakai
long, atauMath.addExactdanMath.multiplyExactbila luapan harus terdeteksi. - Casting eksplisit ditulis dengan alasan yang jelas, dan pelebaran menuju
floattetap dapat kehilangan presisi.
Referensi#
- Conversions and Contexts, JLS SE 21 §5 — Oracle — Aturan lengkap konversi pelebaran dan penyempitan beserta konteks pemakaiannya.
- Primitive Types and Values, JLS SE 21 §4.2 — Oracle — Spesifikasi tipe bilangan bulat dan perilaku perputaran aritmetikanya.
- Class Math — Oracle
— Dokumentasi
Math.round,Math.addExact, danMath.multiplyExact.