← Kursa Dön
📄 Text · 15 min

Tür Dönüşümü (Type Casting)

Java katı tipli (strongly typed) bir dil. Bir int değişkene String atayamazsın, bir double değeri int'e direkt koyman tehlikeli olabilir. Ama bazen bir tipi diğerine çevirmen gerekir — işte buna tür dönüşümü (type casting) diyoruz.

Bunu şöyle düşün: Farklı boyutta kaplar var. Küçük bardaktaki suyu büyük kovaya kolayca boşaltabilirsin (widening). Ama büyük kovadaki suyu küçük bardağa dökerken taşar (narrowing) — dikkatli olmalısın.


İki Tür Dönüşüm

TürYönGüvenlikCast Gerekli mi?
Widening (Genişletme)Küçük → BüyükGüvenliHayır (otomatik)
Narrowing (Daraltma)Büyük → KüçükRiskliEvet (explicit)

Tip Hiyerarşisi (Küçükten Büyüğe)

byte → short → int → long → float → double
         ↑
        char

Soldan sağa doğru otomatik dönüşüm olur. Sağdan sola explicit cast gerekir.


Widening (Genişletme) — Otomatik Dönüşüm

Küçük tipten büyük tipe dönüşüm güvenlidir ve Java otomatik yapar. Veri kaybı olmaz.

byte b = 42;
short s = b;     // byte → short (otomatik)
int i = s;       // short → int (otomatik)
long l = i;      // int → long (otomatik)
float f = l;     // long → float (otomatik)
double d = f;    // float → double (otomatik)

System.out.println(d); // 42.0
// Pratikte sık karşılaşılan durumlar
int sayi = 100;
double sonuc = sayi; // int → double (otomatik)
System.out.println(sonuc); // 100.0

char c = 'A';
int ascii = c; // char → int (otomatik)
System.out.println(ascii); // 65

Aritmetikte Otomatik Widening

Java, aritmetik işlemlerde operandları otomatik olarak ortak tipe yükseltir:

int a = 10;
double b = 3.0;
double sonuc = a / b; // a otomatik double'a çevrilir
System.out.println(sonuc); // 3.3333...

byte x = 10;
byte y = 20;
// byte z = x + y; // HATA! x + y sonucu int'tir
int z = x + y;     // Doğru

⚠️ Önemli kural: byte, short ve char aritmetik işlemlerde otomatik olarak int'e yükseltilir. Bu yüzden iki byte toplamını byte'a atayamazsın — int veya explicit cast gerekir.

byte a = 10;
byte b = 20;
// byte c = a + b;        // HATA! int → byte otomatik olmaz
byte c = (byte)(a + b);   // OK, explicit cast
int d = a + b;             // OK, int'te kal

Widening'de Dikkat: long → float

long buyukSayi = 123456789012345L;
float f = buyukSayi; // Otomatik ama hassasiyet kaybı!
System.out.println(f);           // 1.23456792E14
System.out.println(buyukSayi);   // 123456789012345

// float sadece ~7 basamak hassasiyet sunar
// long 15+ basamak olabilir → veri kaybı!

💡 Widening "güvenli" deniyor ama long → float dönüşümünde hassasiyet kaybı olabilir. long → double daha güvenli ama o da uç değerlerde sorun çıkarabilir.


Narrowing (Daraltma) — Explicit Cast

Büyük tipten küçük tipe dönüşüm risklidir — veri kaybı olabilir. Java otomatik yapmaz, senin açıkça belirtmen gerekir.

Sözdizimi: (hedefTip) deger

double pi = 3.14159;
int tamsayi = (int) pi; // Ondalık kısım ATILIR (yuvarlanmaz!)
System.out.println(tamsayi); // 3

long buyuk = 130;
byte kucuk = (byte) buyuk; // 130 byte aralığını aşıyor!
System.out.println(kucuk); // -126 (taşma!)

Veri Kaybı Örnekleri

// double → int: ondalık kısım atılır
System.out.println((int) 3.99);  // 3 (yuvarlanmaz!)
System.out.println((int) -2.7);  // -2

// int → byte: taşma olur
System.out.println((byte) 128);  // -128
System.out.println((byte) 256);  // 0
System.out.println((byte) 300);  // 44

// int → short
System.out.println((short) 40000); // -25536

Taşma nasıl çalışır? Narrowing cast'ta Java sadece alt bitleri alır, üst bitleri atar:

int sayi = 300; // 00000000 00000000 00000001 00101100
byte b = (byte) sayi; // Son 8 bit: 00101100 = 44
System.out.println(b); // 44

Güvenli Narrowing — Kontrol Et

long deger = 42;

if (deger >= Byte.MIN_VALUE && deger <= Byte.MAX_VALUE) {
    byte b = (byte) deger;
    System.out.println("Güvenli: " + b);
} else {
    System.out.println("Byte aralığının dışında!");
}
// Java 8+ — Math.toIntExact()
long buyuk = 3000000000L;
try {
    int kucuk = Math.toIntExact(buyuk); // ArithmeticException!
} catch (ArithmeticException e) {
    System.out.println("Long int'e sığmıyor: " + buyuk);
}

char Dönüşümleri

char 16-bit unsigned (işaretsiz) tiptir. Sayısal tiplere çevrilebilir ve sayısal tiplerden çevrilebilir.

char c = 'A';
int i = c;          // char → int (widening, otomatik): 65
double d = c;       // char → double (widening, otomatik): 65.0

int sayi = 66;
char harf = (char) sayi; // int → char (narrowing, explicit)
System.out.println(harf); // 'B'

// Karakter aritmetiği
char buyukA = 'A';
char kucukA = (char)(buyukA + 32); // 65 + 32 = 97 = 'a'
System.out.println(kucukA); // 'a'

⚠️ byte ve short'tan char'a dönüşüm de narrowing sayılır (çünkü char unsigned, diğerleri signed):

short s = 65;
// char c = s;      // HATA! short → char narrowing
char c = (char) s;  // OK

Wrapper Sınıflar

Her primitive tipin bir nesne karşılığı (wrapper class) vardır:

PrimitiveWrapperÖrnek
byteByteByte.valueOf((byte)5)
shortShortShort.valueOf((short)100)
intIntegerInteger.valueOf(42)
longLongLong.valueOf(100L)
floatFloatFloat.valueOf(3.14f)
doubleDoubleDouble.valueOf(2.718)
charCharacterCharacter.valueOf('A')
booleanBooleanBoolean.valueOf(true)

Neden Wrapper'lar Gerekli?

  1. Koleksiyonlar primitive alamaz: List<int> yazamazsın, List<Integer> yazarsın

  2. null değer: Primitive'ler null olamaz, wrapper'lar olabilir

  3. Utility metotlar: Integer.parseInt(), Double.parseDouble() vb.

// Koleksiyonlarda wrapper zorunlu
List<Integer> sayilar = new ArrayList<>();
sayilar.add(42);
sayilar.add(17);

// null olabilir
Integer nullable = null; // OK
// int primitive = null;  // HATA!

Utility Metotlar

// String → Sayı dönüşümü
int sayi = Integer.parseInt("42");
double ondalik = Double.parseDouble("3.14");
long buyuk = Long.parseLong("1000000000");
boolean bool = Boolean.parseBoolean("true");

System.out.println(sayi);    // 42
System.out.println(ondalik); // 3.14

// Sayı → String dönüşümü
String s1 = Integer.toString(42);
String s2 = String.valueOf(42);
String s3 = "" + 42; // Pratik ama ideal değil

// Farklı tabanda gösterim
System.out.println(Integer.toBinaryString(42)); // "101010"
System.out.println(Integer.toHexString(255));   // "ff"
System.out.println(Integer.toOctalString(8));   // "10"

// Farklı tabandan parse etme
int fromHex = Integer.parseInt("ff", 16);  // 255
int fromBin = Integer.parseInt("101010", 2); // 42
int fromOct = Integer.parseInt("52", 8);   // 42

Wrapper Karşılaştırma Metotları

// compare() — iki primitive değer karşılaştır
System.out.println(Integer.compare(10, 20));  // -1 (küçük)
System.out.println(Integer.compare(20, 20));  // 0  (eşit)
System.out.println(Integer.compare(30, 20));  // 1  (büyük)

// max() ve min()
System.out.println(Integer.max(10, 20));  // 20
System.out.println(Integer.min(10, 20));  // 10
System.out.println(Double.max(3.14, 2.72)); // 3.14

// sum()
System.out.println(Integer.sum(10, 20)); // 30
System.out.println(Long.sum(100L, 200L)); // 300

Özel Değer Kontrolleri

// Double özel değerleri
System.out.println(Double.isNaN(0.0 / 0.0));      // true
System.out.println(Double.isInfinite(1.0 / 0.0));  // true
System.out.println(Double.isFinite(42.0));          // true

// Integer sınırları
System.out.println(Integer.MAX_VALUE); // 2147483647
System.out.println(Integer.MIN_VALUE); // -2147483648
System.out.println(Integer.BYTES);     // 4
System.out.println(Integer.SIZE);      // 32 (bit)

parseInt Hata Durumu

try {
    int sayi = Integer.parseInt("abc"); // NumberFormatException!
} catch (NumberFormatException e) {
    System.out.println("Geçersiz sayı formatı");
}

// Boşlukları temizle
String girdi = " 42 ";
int sayi = Integer.parseInt(girdi.trim()); // 42 — OK

Autoboxing ve Unboxing

Java 5+ ile birlikte primitive ↔ wrapper dönüşümleri otomatik yapılır.

Autoboxing: Primitive → Wrapper (otomatik) Unboxing: Wrapper → Primitive (otomatik)

// Autoboxing: int → Integer
Integer sayi = 42; // Java otomatik Integer.valueOf(42) çağırır

// Unboxing: Integer → int
int primitive = sayi; // Java otomatik sayi.intValue() çağırır

// İşlemlerde otomatik
Integer a = 10;
Integer b = 20;
int toplam = a + b; // Unbox → topla → sonuç int
// List ile kullanım
List<Integer> sayilar = new ArrayList<>();
sayilar.add(42);    // Autoboxing: int → Integer
int ilk = sayilar.get(0); // Unboxing: Integer → int

Autoboxing Tuzakları

1. null Unboxing — NullPointerException:

Integer sayi = null;
int x = sayi; // NullPointerException! null unbox edilemez

Bu çok sinsi bir hata. Wrapper null olabilir ama primitive olamaz. Unboxing sırasında null ise patlarsın.

// Güvenli yol
Integer sayi = getSayiFromDb(); // null dönebilir
int x = (sayi != null) ? sayi : 0; // Güvenli

2. == ile Karşılaştırma Tuzağı:

Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true (!)

Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false (!)

⚠️ Java -128 ile 127 arasındaki Integer değerlerini cache'ler (Integer Cache). Bu aralıkta == doğru çalışır gibi görünür ama 128'den itibaren farklı nesneler oluşur.

Her zaman `equals()` kullan:

Integer a = 200;
Integer b = 200;
System.out.println(a.equals(b)); // true — güvenli

3. Performans:

// KÖTÜ — her adımda autoboxing/unboxing
Long toplam = 0L;
for (int i = 0; i < 1000000; i++) {
    toplam += i; // Unbox → topla → autobox (her iterasyonda!)
}

// İYİ — primitive kullan
long toplam2 = 0L;
for (int i = 0; i < 1000000; i++) {
    toplam2 += i; // Saf primitive işlem
}

Döngülerde ve yoğun hesaplamalarda her zaman primitive tercih et. Autoboxing/unboxing bedava değil.

Optional ile Güvenli Wrapper Kullanımı

Java 8+ ile Optional sınıfı null güvenliği sağlar:

import java.util.Optional;

// Integer null olabilir — Optional ile güvenli kullan
Integer sayi = getNullableSayi();
int deger = Optional.ofNullable(sayi).orElse(0);

// Map'ten değer okurken
Map<String, Integer> puanlar = Map.of("Ali", 85, "Veli", 92);
int aliPuani = Optional.ofNullable(puanlar.get("Ali")).orElse(0);   // 85
int ayPuani = Optional.ofNullable(puanlar.get("Ayşe")).orElse(0);  // 0 (yok)

String'den Dönüşümler

Kullanıcı girdisi String olarak gelir, sayıya çevirmen gerekir:

String yasStr = "25";
String fiyatStr = "19.99";
String aktifStr = "true";

int yas = Integer.parseInt(yasStr);
double fiyat = Double.parseDouble(fiyatStr);
boolean aktif = Boolean.parseBoolean(aktifStr);

System.out.println(yas);    // 25
System.out.println(fiyat);  // 19.99
System.out.println(aktif);  // true
// Sayı → String (birden fazla yol)
int sayi = 42;
String s1 = String.valueOf(sayi);     // En temiz yol
String s2 = Integer.toString(sayi);    // Açık dönüşüm
String s3 = "" + sayi;                 // Kısa ama dolaylı
String s4 = String.format("%d", sayi); // Formatla

Pratik Örnek: Tip Güvenli Hesaplama

public class TipGuvenliHesaplama {
    public static void main(String[] args) {
        // Senaryo: Öğrenci not ortalaması
        int not1 = 85;
        int not2 = 92;
        int not3 = 78;
        int toplamNot = not1 + not2 + not3;
        int ogrenciSayisi = 3;
        
        // YANLIŞ: integer division
        double yanlisOrtalama = toplamNot / ogrenciSayisi;
        System.out.println("Yanlış: " + yanlisOrtalama); // 85.0
        
        // DOĞRU: cast ile
        double dogruOrtalama = (double) toplamNot / ogrenciSayisi;
        System.out.println("Doğru: " + dogruOrtalama);   // 85.0
        // (Bu örnekte tesadüfen aynı çıktı, 
        //  ama 10+20+15 / 3 = 15.0 vs 15.0 farkı net)
        
        // Güvenli narrowing
        long uzunDeger = 42L;
        if (uzunDeger >= Integer.MIN_VALUE && 
            uzunDeger <= Integer.MAX_VALUE) {
            int kisa = (int) uzunDeger;
            System.out.println("Güvenli cast: " + kisa);
        }
        
        // String dönüşümü
        String giriş = "95";
        try {
            int not4 = Integer.parseInt(giriş);
            System.out.println("Parsed: " + not4);
        } catch (NumberFormatException e) {
            System.out.println("Geçersiz not!");
        }
    }
}

Nesne Tipi Dönüşümü (Upcasting & Downcasting)

Primitive dönüşümlerin yanında, sınıf hiyerarşisinde de tür dönüşümü vardır. Bunu ileride OOP derslerinde detaylıca göreceğiz ama temel mantığını burada görelim:

// Upcasting — alt sınıf → üst sınıf (otomatik, güvenli)
Object obj = "Merhaba"; // String → Object (otomatik)
Number num = 42;         // Integer → Number (autobox + upcast)

// Downcasting — üst sınıf → alt sınıf (explicit, riskli)
Object obj2 = "Merhaba";
String str = (String) obj2; // OK — gerçekten String

Object obj3 = 42; // Integer (autoboxing)
// String str2 = (String) obj3; // ClassCastException! Integer String değil

// Güvenli downcasting — instanceof ile kontrol et
if (obj3 instanceof String s) {
    System.out.println(s.length());
} else {
    System.out.println("String değil: " + obj3.getClass().getSimpleName());
}

Tip Dönüşüm Zinciri

Bazen birden fazla dönüşüm gerekir:

// String → int → double
String str = "42";
int i = Integer.parseInt(str);
double d = i; // widening

// Kısa yol
double d2 = Integer.parseInt("42"); // parse + widening

// String → double direkt
double d3 = Double.parseDouble("3.14");

// int → String → char dizisi
int sayi = 12345;
char[] rakamlar = String.valueOf(sayi).toCharArray();
System.out.println(rakamlar[0]); // '1'

// Farklı tabanda dönüşüm
String hex = Integer.toHexString(255);     // "ff"
String bin = Integer.toBinaryString(255);   // "11111111"
int fromHex = Integer.parseInt("ff", 16);   // 255
int fromBin = Integer.parseInt("11111111", 2); // 255

Pratik Örnek: Veri Tipi Dönüştürücü Utility

public class TipDonusturucu {
    
    // Güvenli parseInt — hata durumunda varsayılan değer döner
    public static int guvenliParseInt(String str, int varsayilan) {
        if (str == null || str.isBlank()) {
            return varsayilan;
        }
        try {
            return Integer.parseInt(str.strip());
        } catch (NumberFormatException e) {
            return varsayilan;
        }
    }
    
    // Güvenli parseDouble
    public static double guvenliParseDouble(String str, double varsayilan) {
        if (str == null || str.isBlank()) {
            return varsayilan;
        }
        try {
            return Double.parseDouble(str.strip());
        } catch (NumberFormatException e) {
            return varsayilan;
        }
    }
    
    // Güvenli long → int (taşma durumunda clamp)
    public static int clampToInt(long deger) {
        if (deger > Integer.MAX_VALUE) return Integer.MAX_VALUE;
        if (deger < Integer.MIN_VALUE) return Integer.MIN_VALUE;
        return (int) deger;
    }
    
    public static void main(String[] args) {
        System.out.println(guvenliParseInt("42", 0));    // 42
        System.out.println(guvenliParseInt("abc", 0));   // 0
        System.out.println(guvenliParseInt(null, -1));   // -1
        System.out.println(guvenliParseInt("  99  ", 0)); // 99
        
        System.out.println(clampToInt(3000000000L)); // 2147483647
        System.out.println(clampToInt(42L));         // 42
    }
}

Bu tür utility metotlar gerçek projelerde çok sık lazım olur. Kullanıcı girdisi her zaman String gelir ve her zaman geçerli bir sayı olmayabilir.


Dönüşüm Özet Tablosu

Hangi dönüşüm güvenli, hangisi riskli? Hepsini bir arada görelim:

Kaynak → HedefTürOtomatik?Risk
byte → shortWideningEvetYok
short → intWideningEvetYok
int → longWideningEvetYok
int → floatWideningEvetHassasiyet kaybı olabilir
int → doubleWideningEvetYok
long → floatWideningEvetHassasiyet kaybı!
long → doubleWideningEvetHassasiyet kaybı olabilir
double → intNarrowingHayırOndalık kısım kesilir
long → intNarrowingHayırTaşma riski
int → byteNarrowingHayırTaşma riski
String → intParseHayırNumberFormatException
int → StringConversionHayırYok
Integer → intUnboxingEvetNullPointerException
int → IntegerAutoboxingEvetYok
// Güvenli dönüşümler — her zaman çalışır
byte b = 42;
int i = b;        // widening
long l = i;       // widening  
double d = l;     // widening

// Riskli dönüşümler — kontrol et
double pi = 3.14;
int tamsayi = (int) pi;      // 3 (ondalık kayıp)

long buyuk = 3000000000L;
int kucuk = (int) buyuk;     // negatif sayı! (overflow)

// Tehlikeli dönüşümler — exception yakalA
String str = "abc";
// int sayi = Integer.parseInt(str); // NumberFormatException!

Sık Yapılan Hatalar

1. Narrowing'de veri kaybını fark etmemek:

int buyuk = 130;
byte kucuk = (byte) buyuk; // -126! Hata yok, uyarı yok

2. null unboxing:

Integer sayi = null;
int x = sayi; // NullPointerException!

// Güvenli yol
int y = (sayi != null) ? sayi : 0;
// veya Java 9+
int z = java.util.Objects.requireNonNullElse(sayi, 0);

3. Integer cache tuzağı:

Integer a = 200, b = 200;
System.out.println(a == b); // false — equals() kullan!
System.out.println(a.equals(b)); // true

4. double → int yuvarlamaz, keser:

System.out.println((int) 9.99);   // 9, 10 değil!
System.out.println((int) -2.7);   // -2, -3 değil!
System.out.println(Math.round(9.99)); // 10 — yuvarlama istiyorsan
System.out.println(Math.round(9.5));  // 10
System.out.println(Math.round(9.49)); // 9

5. Cast sırasını yanlış yapmak:

int a = 1_000_000;
int b = 1_000_000;
// long c = a * b; // Overflow! Çarpma int'te yapılır, sonra long'a atanır
long d = (long) a * b; // Doğru — önce a'yı long yap, sonra çarp

Özet

  • Widening (küçük → büyük) otomatik ve güvenlidir; narrowing (büyük → küçük) explicit cast gerektirir ve veri kaybı riski taşır

  • byte ve short aritmetikte otomatik int'e yükseltilir — sonucu geri atarken cast gerekir

  • Wrapper sınıflar (Integer, Double vb.) koleksiyonlar ve null değer için zorunludur

  • Autoboxing/unboxing otomatiktir ama null unboxing NullPointerException verir — dikkat et

  • Integer cache (-128 ile 127) yüzünden == bazen doğru çalışır gibi görünür — her zaman `equals()` kullan

  • String → sayı dönüşümünde parseInt(), parseDouble() kullan ve NumberFormatException'ı yakala

  • Cast sırasına dikkat et: (long) a * b ile (long)(a * b) çok farklı sonuç verir — cast'ı işlemden önce yap


Mülakat Soruları

S: Widening ve narrowing farkı? C: Widening küçük tipten büyüğe dönüşüm (byte→int), otomatik ve güvenli. Narrowing büyük tipten küçüğe (int→byte), explicit cast gerektirir ve veri kaybı riski taşır.

S: Autoboxing nedir? C: Java'nın primitive değeri otomatik olarak wrapper nesnesine çevirmesi. Integer x = 42 yazdığında Java Integer.valueOf(42) çağırır.

S: Integer.valueOf(127) == Integer.valueOf(127) neden true? C: Java -128 ile 127 arasındaki Integer değerlerini cache'ler. Bu aralıkta valueOf() aynı nesneyi döner. 128 ve üzerinde yeni nesne oluşturulur, bu yüzden == false döner.

S: (int) 9.99 sonucu nedir? C: 9. Narrowing cast ondalık kısmı yuvarlamaz, keser (truncation). Yuvarlama istiyorsan Math.round() kullan.