JAVA PROGRAMLAMA

Java programlama yapısını temsil eden turuncu sınıf bloğu ve metot zincirleri kolajı

"Yeni başlayanlar Java'yı zor sanır çünkü ekrana 'Merhaba Dünya' yazdırmak için bile bir sınıf yazmak gerekir." Bu yaygın yakınma aslında dilin felsefesini yanlış anlamaktan doğar. Java her satırı bir sınıfa, her sınıfı bir pakete, her paketi bir modüle bağlar; çünkü dil küçük scriptler için değil, on yıl ayakta kalacak kurumsal yazılım için tasarlandı. Kodun nasıl organize edileceği, dilin syntax'ı kadar önemlidir.

Bu yazı Java ile programlama yaparken kodun nasıl kurulduğunu ele alıyor: sınıfı nasıl yazarsın, metodu nasıl tanımlarsın, OOP ilkelerini gerçek bir senaryoda nasıl uygularsın, paket düzeni nasıl kurulur, exception nasıl yakalanır. Hangi sektörde kullanıldığı veya öğrenme yolu değil — somut kod yapısı.

Java Kodunun İskeleti Nedir?

Java'da en küçük çalışan program bile en az bir sınıf ve bir main metodu içerir. Bu sıkı kurallar diğer dillerden gelenlere ilk başta gereksiz görünür; ancak büyük kod tabanlarında her şeyin tutarlı bir yapıda olması yıllar geçtikçe avantaja dönüşür.

package com.veriakademi.bordro;

public class BordroHesapla {
    public static void main(String[] args) {
        double brut = 25000.0;
        double net = hesaplaNet(brut);
        System.out.println("Net maaş: " + net);
    }

    public static double hesaplaNet(double brut) {
        return brut * 0.78;
    }
}

Yukarıdaki dosyada beş farklı dil özelliği iç içe duruyor: paket bildirimi, public erişim belirleyici, sınıf tanımı, statik metot ve main giriş noktası. Java derleyicisi bu yapıyı bekler; aksi halde derleme aşamasında hata verir. Oracle Java tutorial bu iskeleti resmi terimleriyle birlikte detaylı işliyor.

İskeletin parçalarını teker teker tanıyalım:

  • package: Sınıfı bir isim alanına yerleştirir, dosya sisteminde klasör yolu ile eşleşir
  • public class: Sınıf adı her zaman dosya adı ile aynı olmak zorunda
  • main metodu: JVM programı buradan başlatır, imzası sabittir
  • String[] args: Komut satırından gelen argümanlar bu diziye düşer

Sınıf Nasıl Tanımlanır?

Sınıf, Java'da nesnenin şablonudur. Bir Musteri sınıfı tanımladığında o sınıftan üretilen her nesne aynı alanlara ve aynı davranışlara sahip olur. Sınıf tanımı dört temel parçadan oluşur: alan (field), constructor, metot ve isteğe bağlı static üyeler.

public class Musteri {
    private String ad;
    private String email;
    private int yas;

    public Musteri(String ad, String email, int yas) {
        this.ad = ad;
        this.email = email;
        this.yas = yas;
    }

    public String getAd() {
        return ad;
    }

    public boolean yetiskinMi() {
        return yas >= 18;
    }
}

Bu sınıfta private ile işaretlenmiş alanlar yalnızca sınıfın kendi içinden erişilebilir. Constructor (yapıcı metot) sınıf adıyla aynı isimde olur ve new anahtar kelimesiyle nesne üretilirken çağrılır. this referansı, parametre adıyla alan adının çakıştığı durumlarda hangisinin kastedildiğini belirtmek için kullanılır.

Bir nesne üretmek artık tek satırlık iştir:

Musteri m = new Musteri("Ayşe Yılmaz", "ayse@example.com", 28);
System.out.println(m.getAd() + " yetişkin mi? " + m.yetiskinMi());

Burada dikkat edilecek nokta şu: m.ad doğrudan yazılamaz çünkü alan private. Veriye dışarıdan erişim için getAd() gibi public metotlar tanımlanır. Bu kapsüllemenin (encapsulation) sözdizimsel karşılığı.

Java sınıf yapısının alanları metotları constructor ve erişim belirleyicilerle birlikte gösterildiği şema

Metot Yazma ve Parametre Aktarımı

Metot, bir sınıfın davranışını tanımlayan kod bloğudur. Java'da her metodun bir dönüş tipi, bir adı ve isteğe bağlı parametre listesi vardır. Dönüş tipi void olabilir (hiçbir şey döndürmüyorsa) veya somut bir tip — int, String, başka bir sınıf adı.

public int topla(int a, int b) {
    return a + b;
}

public void uyariYaz(String mesaj) {
    System.out.println("[UYARI] " + mesaj);
}

public double[] istatistik(int[] sayilar) {
    double toplam = 0;
    for (int s : sayilar) toplam += s;
    double ortalama = toplam / sayilar.length;
    return new double[]{toplam, ortalama};
}

Java'da parametreler her zaman değer kopyası ile aktarılır. İlkel tipler için kopya tam anlamıyla değerin kendisidir; nesne referansları için kopya, referansın kendisi olur — ama o referansın gösterdiği nesne paylaşılır. Bu ince ayrım yeni başlayanların en sık yanıldığı konulardandır.

Aynı isimde birden çok metot tanımlanabilir; buna method overloading denir. Derleyici çağrıyı parametre tiplerine göre ayırır:

public double topla(double a, double b) { return a + b; }
public int topla(int a, int b, int c) { return a + b + c; }
public String topla(String a, String b) { return a + " " + b; }

Aynı sınıfta üç farklı topla bir arada yaşar. Hangisinin çağrılacağına derleyici karar verir — JVM çalışma zamanında bunu zaten kararlaştırmış olur.

OOP İlkeleri Kod Üzerinde Nasıl Görünür?

Kapsülleme, kalıtım, çok biçimlilik ve soyutlama — bu dört kavramın her biri Java syntax'ında somut karşılığı olan yapılarla desteklenir. Soyut anlatımı bir kenara bırakıp koda dökelim.

Kalıtım (Inheritance) için extends anahtar kelimesi kullanılır:

public class Calisan {
    protected String ad;
    protected double maas;

    public double yillikMaas() {
        return maas * 12;
    }
}

public class Yonetici extends Calisan {
    private double prim;

    public Yonetici(String ad, double maas, double prim) {
        this.ad = ad;
        this.maas = maas;
        this.prim = prim;
    }

    @Override
    public double yillikMaas() {
        return super.yillikMaas() + prim;
    }
}

Yonetici sınıfı Calisan'dan tüm alanları ve metotları miras alır. yillikMaas metodunu yeniden tanımlayarak (override) prim eklenmiş hesabı sunar. super ile üst sınıfın orijinal davranışı çağrılır. @Override annotation'ı derleyiciye "bu metot üst sınıftaki bir metodu eziyor" sinyalini verir; imza uyuşmazsa derleyici hata verir.

Çok biçimlilik (Polymorphism) aynı tipte değişkenin farklı somut nesnelere bağlanabilmesidir:

Calisan[] ekip = {
    new Calisan(),
    new Yonetici("Mehmet", 30000, 5000)
};

for (Calisan c : ekip) {
    System.out.println(c.yillikMaas());
}

Döngüde her eleman Calisan referansı olarak görülür, ama yillikMaas() çağrısı çalışma zamanında gerçek sınıfa göre çözülür. Bu Java'nın dynamic dispatch dediğimiz davranışıdır.

Soyutlama (Abstraction) için iki araç vardır: abstract sınıflar ve interface'ler. Interface bir sözleşmedir; sınıf bu sözleşmeyi implements ile imzalar:

public interface Odenebilir {
    double odemeMiktari();
    String odemeReferansi();
}

public class Fatura implements Odenebilir {
    private double tutar;
    private String no;

    public double odemeMiktari() { return tutar; }
    public String odemeReferansi() { return "FAT-" + no; }
}

Aynı interface'i MaasOdemesi, VergiOdemesi gibi farklı sınıflar da uygulayabilir. Bu sayede ödeme yapan bir metot, somut sınıfı bilmeden sadece Odenebilir tipi ile çalışabilir. Java'nın bu yapıları kurumsal kodda nasıl bir araya geldiğini daha derinlemesine görmek isteyenler uygulamalı Java eğitimi içeriğinden yararlanabilir; OOP ilkeleri proje ölçeğinde nasıl uygulanır sorusu modüller arasında somutlaşıyor.

Paket Düzeni ve Erişim Belirleyiciler

Tek dosyalık örnekler kolay; ama 200 sınıfı olan bir proje paket düzeni olmadan kaosa döner. Java'da paket (package) hem isim alanı sağlar hem de erişim kontrolünün bir katmanıdır. Tipik bir kurumsal projede paket ağacı şöyle görünür:

com.veriakademi.bordro
├── model
│   ├── Calisan.java
│   ├── MaasBordrosu.java
│   └── KesintiKalemi.java
├── service
│   ├── BordroHesaplaService.java
│   └── VergiService.java
├── repository
│   └── BordroRepository.java
└── controller
    └── BordroController.java

Her sınıf en üstte hangi pakete ait olduğunu bildirir. Başka paketten sınıf kullanmak için import ifadesi gerekir:

package com.veriakademi.bordro.service;

import com.veriakademi.bordro.model.Calisan;
import com.veriakademi.bordro.repository.BordroRepository;

public class BordroHesaplaService {
    private BordroRepository repo;
    // ...
}

Erişim belirleyiciler bir üyenin nereden görüleceğini kontrol eder. Dört seviye var:

  • public: Her yerden erişilebilir — herkese açık API
  • protected: Aynı paket ve alt sınıflardan erişilebilir
  • (belirtilmemiş, package-private): Sadece aynı paketten
  • private: Sadece tanımlandığı sınıftan

İyi bir Java kodunun ölçütlerinden biri, alanların ve yardımcı metotların mümkün olduğunca dar erişimde tutulmasıdır. private by default, public when necessary pratiği, ileride sınıfın iç yapısını değiştirmek gerektiğinde geri kalan kodun bozulmamasını sağlar.

Exception Handling Nasıl Yapılır?

Java'da bir şeyler ters gittiğinde program Exception tipinden bir nesne fırlatır. Eğer yakalanmazsa, JVM stack trace basıp programı kapatır. Düzgün yazılmış bir kod hataları öngörür ve uygun yerde yakalar.

public double bolme(int a, int b) {
    try {
        return (double) a / b;
    } catch (ArithmeticException e) {
        System.err.println("Sıfıra bölme: " + e.getMessage());
        return 0.0;
    } finally {
        System.out.println("İşlem tamamlandı");
    }
}

try bloğu riskli kodu kapsar, catch belirli bir exception tipini yakalar, finally hata olsun veya olmasın her zaman çalışır — dosya kapatma, bağlantı serbest bırakma gibi işler için ideal.

Java exception'ları iki kategoriye ayırır:

  • Checked exception: Derleyici yakalamayı veya method imzasına eklemeyi zorunlu kılar (ör. IOException, SQLException)
  • Unchecked (runtime) exception: RuntimeException alt türleri — derleyici zorlamaz (ör. NullPointerException, IllegalArgumentException)

Çoğu zaman kendi domain'inize özgü exception tanımlamak işin temizliğine yarar:

public class YetersizBakiyeException extends RuntimeException {
    public YetersizBakiyeException(double mevcut, double talep) {
        super("Bakiye " + mevcut + " ama talep " + talep);
    }
}

public void cek(double tutar) {
    if (tutar > bakiye) {
        throw new YetersizBakiyeException(bakiye, tutar);
    }
    bakiye -= tutar;
}

Bu yapı hatanın anlamını "hata oluştu" gibi belirsiz mesajdan çıkarıp domain seviyesine taşır. Servis katmanı sadece kendi exception'larını fırlatır; controller katmanı bunları HTTP cevabına dönüştürür.

Java try catch finally akışını ve checked unchecked exception ayrımını gösteren diyagram

Temiz Java Kodu İçin Pratikler

Syntax öğrenildikten sonra ayırt eden şey kodun düzenidir. Aşağıdaki pratikler tecrübeli Java geliştiricilerinin neredeyse refleks olarak uyguladıkları kurallar:

  1. Alanları daima private başlat; gerekli olunca getter/setter aç
  2. Sınıf ve metot adlarını anlamlı yaz — data1, do() gibi isimlerden kaçın
  3. Bir metot bir iş yapsın; 50 satırı geçen metot büyük ihtimalle bölünebilir
  4. Boş catch bloğu yazma — en azından log'a bas
  5. Constructor'da iş mantığı çalıştırma; sadece alanları ata
  6. null döndürmek yerine boş koleksiyon veya Optional kullan
  7. Sabit değerleri static final alana taşı, magic number bırakma
  8. Equals ve hashCode'u beraber override et (HashMap kullandığında hatırlayacaksın)

Bu pratikler iki gün kodunu açıp bakan başka bir geliştiricinin (veya altı ay sonraki sen'in) işini kolaylaştırır. Oracle Java sayfası dilin resmi yol haritasını ve sürüm notlarını takip etmek için referans nokta olarak iş görür.

Son bir hatırlatma: Java'da kod yazmak sadece syntax bilmek değil, sınıfı küçük ve odaklı tutmak, metotları okunabilir bırakmak, exception'ları anlamlı yakalamak, paket düzenini erkenden kurmaktır. Bu alışkanlıklar oturduğunda dilin "her şeyi sınıfa sokmak" görünen katı tarafı, aslında uzun ömürlü kodun temelini attığını gösterir.

Sık Sorulan Sorular

Java'da main metodu neden static olmak zorunda?

JVM programı başlatırken henüz hiçbir nesne yoktur. main static olduğu için JVM sınıfı yüklemekle yetinir, nesne üretmek zorunda kalmaz ve doğrudan çağırır. İmzası da bu yüzden sabittir: public static void main(String[] args). İmza bozulursa JVM giriş noktasını bulamaz ve program başlamaz.

this anahtar kelimesi ne işe yarar?

this bulunduğun nesnenin kendisine referanstır. En sık parametre adıyla alan adı çakıştığında kullanılır: this.ad = ad ifadesinde sol taraf nesnenin alanı, sağ taraf parametre. Ayrıca aynı sınıftaki başka bir constructor'ı çağırmak için this(...) şeklinde de kullanılabilir.

Abstract sınıf ile interface arasındaki fark nedir?

Abstract sınıf hem metot hem alan içerebilir, ortak davranışları paylaşmak için uygundur ve extends ile tek üst sınıf olarak alınır. Interface ise sözleşmedir; bir sınıf birden çok interface uygulayabilir. Java 8 sonrası interface'ler default ve static metot da içerebilir ama durum bilgisi (instance alan) tutamaz.

Checked exception ile unchecked exception arasındaki fark nedir?

Checked exception Exception sınıfından türer (RuntimeException dışında) ve derleyici tarafından zorlanır — ya try/catch ile yakalanmalı ya da metot imzasına throws ile eklenmelidir. Unchecked exception ise RuntimeException alt türüdür, derleyici zorlamaz. NullPointerException, IllegalArgumentException gibi mantık hataları unchecked tarafında yer alır.

Java'da neden her şey bir sınıfın içinde olmak zorunda?

Çünkü Java tasarımı saf nesne yönelimli paradigmaya yakın durur. Global fonksiyon veya global değişken kavramı dilde yoktur. Bu yaklaşım küçük scriptler için ağır görünür, fakat büyük projelerde her parçanın bir sahibinin olması, bağımlılıkları izlemeyi ve test yazmayı kolaylaştırır.

Method overloading ile overriding arasındaki fark nedir?

Overloading aynı sınıfta aynı isimli ama farklı parametre listesine sahip birden çok metot tanımlamaktır; derleyici karar verir. Overriding ise alt sınıfın üst sınıftaki metodu yeniden tanımlamasıdır; çağrı çalışma zamanında gerçek nesneye göre çözülür. @Override annotation'ı overriding sırasında derleyici kontrolü için yazılır.

Constructor neden geri dönüş tipi belirtmez?

Constructor bir metot değil, nesne üretim mekanizmasıdır. new ifadesi zaten yeni üretilen nesneyi döndürür; constructor'ın görevi bu nesnenin alanlarını başlatmaktır. Sınıf adıyla aynı isim taşır ve dönüş tipi yazılmaz. Yanlışlıkla dönüş tipi koyarsan o tanım constructor değil, sıradan bir metot olur ve new çağrısı doğru çalışmaz.

Java'da paket adlandırma kuralı nedir?

Yerleşik konvansiyon ters domain adıdır: com.sirketadi.proje.modul şeklinde. Bu çakışmaları önlemek için tasarlandı. Açık kaynak kütüphaneler de aynı kalıbı izler. Paket adları daima küçük harfle yazılır; sınıf adlarından farkı budur. Maven veya Gradle ile projeyi yönetiyorsan groupId zaten bu kalıpta tanımlanır.