Konversi Tipe dan Casting

Membedakan widening dan narrowing, menulis casting secara sadar, serta memahami perilaku overflow.

Daftar isi

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.

charbyteshortintlongfloatdoublekonversi otomatis (widening) mengikuti arah panah tanpa notasi tambahansetiap lompatan berlawanan arah memerlukan casting eksplisit (narrowing)
Hierarki konversi otomatis antartipe primitif

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.

java KonversiLebar.java
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);
    }
}
text Keluaran
int        : 1300
long       : 1300
double     : 1300.0
char       : A
kode huruf : 65
float      : 2.0E9

Empat 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.

java KonversiSempit.java
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);
    }
}
text Keluaran
(int) 91.7         : 91
(int) -91.7        : -91
(int) 8100000000L  : -489934592
(byte) 200         : -56

Pemangkasan 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.

java LuapanInteger.java
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));
    }
}
text Keluaran
Nilai maksimum int : 2147483647
maks + 1           : -2147483648
maks + 2           : -2147483647

Cara 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.

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.

java Contoh: memaksa pembagian pecahan dengan casting
// tidak-dijalankan
int totalNilai = 17;
int jumlahSoal = 5;
double rataRata = (double) totalNilai / jumlahSoal; // 3.4, bukan 3

Casting 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.7 bernilai 91 dan (int) -91.7 bernilai -91. Gunakan Math.round untuk pembulatan, dengan catatan hasilnya bertipe long.
  • Menyimpan hasil penjumlahan dua byte atau short kembali ke tipenya tanpa casting. Ekspresi a + b dengan dua byte dipromosikan menjadi int lebih dahulu, sehingga byte c = a + b; ditolak kompiler dengan pesan incompatible types. Simpan hasilnya ke int atau tulis (byte) secara sadar.
  • Menghitung nilai besar dalam int, misalnya jumlah detik seratus tahun melalui 60 * 60 * 24 * 365 * 100. Semua operandnya bertipe int sehingga hasilnya meluap dan berpaling negatif. Tambahkan akhiran L pada salah satu operand agar hitungan berjalan dalam long.
  • Menganggap pelebaran selalu mempertahankan nilai persis. Konversi long menuju float sah sebagai pelebaran, tetapi 2000000001 berubah menjadi 2000000000 karena presisi float terbatas.

Ringkasan#

  • Konversi otomatis (widening) mengikuti jalur byte, short, int, long, float, double serta char ke int, 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, atau Math.addExact dan Math.multiplyExact bila luapan harus terdeteksi.
  • Casting eksplisit ditulis dengan alasan yang jelas, dan pelebaran menuju float tetap dapat kehilangan presisi.

Referensi#