throw ve throws
Şimdiye kadar exception yakalamayı öğrendin. Ama ya senin kodun bir sorun tespit ederse? O zaman kendin exception fırlatırsın. Ve eğer bir method exception fırlatabiliyorsa, bunu bildirmesi gerekir. İşte throw ve throws burada devreye girer.
İkisini karıştırmak çok kolay ama rolleri çok farklı:
throw → Exception nesnesini fırlatır (eylem)
throws → Method'un exception fırlatabileceğini bildirir (deklarasyon)
Posta analojisiyle düşün: throw postaya mektup atmak, throws ise kapıdaki "dikkat, köpek var" tabelası.
throw: Exception Fırlatma
throw keyword'ü ile bir exception nesnesi fırlatırsın. O andan itibaren normal akış durur ve JVM en yakın uygun catch'i arar.
public static int divide(int a, int b) {
if (b == 0) {
throw new ArithmeticException("Sıfıra bölme yapılamaz");
}
return a / b;
}
public static void main(String[] args) {
try {
int result = divide(10, 0);
} catch (ArithmeticException e) {
System.out.println(e.getMessage()); // "Sıfıra bölme yapılamaz"
}
}throw ile fırlattığın şey bir nesne olmalı. throw new ...() kalıbını çok göreceksin. Şunu yazamazsın: throw "hata oldu" — String throwable değil.
// throw kullanım kuralları
throw new NullPointerException("değer null olamaz"); // OK
throw new RuntimeException("genel hata"); // OK
Exception e = new IOException("dosya hatası");
throw e; // Önceden oluşturulmuş nesne de olabilir
// throw "hata"; // COMPILE ERROR — String throwable değil
// throw 404; // COMPILE ERROR — int throwable değilValidasyon İçin throw
En yaygın kullanım: method parametrelerini kontrol etmek. Geçersiz değer geldiğinde hemen hata fırlatırsın — fail-fast prensibi.
public class User {
private String name;
private int age;
public User(String name, int age) {
if (name == null || name.isBlank()) {
throw new IllegalArgumentException("İsim boş olamaz");
}
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Yaş 0-150 arasında olmalı: " + age);
}
this.name = name;
this.age = age;
}
public void setAge(int age) {
if (age < 0) {
throw new IllegalArgumentException("Negatif yaş: " + age);
}
this.age = age;
}
}💡 İpucu:
IllegalArgumentException— method'a geçersiz argüman geldi.IllegalStateException— nesne doğru durumda değil. Bu ikisi validasyon için en çok kullandığın unchecked exception'lardır.
public class BankAccount {
private double balance;
public void withdraw(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Çekim miktarı pozitif olmalı");
}
if (amount > balance) {
throw new IllegalStateException("Yetersiz bakiye. Mevcut: " + balance);
}
balance -= amount;
}
}throws: Method Deklarasyonunda Bildirim
throws keyword'ü method imzasında kullanılır. "Bu method şu exception'ları fırlatabilir" diye bildirir.
Checked exception fırlatan method'lar bunu bildirmek zorunda. Unchecked (RuntimeException) için zorunlu değil ama belgelemek amacıyla yazılabilir.
// Checked exception — throws ZORUNLU
public void readFile(String path) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(path));
String line = reader.readLine();
reader.close();
}
// Birden fazla exception
public void processData(String path) throws IOException, ParseException {
// Dosya oku + parse et
}
// Unchecked exception — throws yazmak opsiyonel
public int divide(int a, int b) { // throws ArithmeticException yazmana gerek yok
return a / b;
}throws ile bildirilen method'u çağıran kod ya try-catch ile yakalamalı ya da kendisi de throws ile yukarı bildirmelidir:
// Seçenek 1: Yakala
public void processFile() {
try {
readFile("data.txt");
} catch (IOException e) {
System.out.println("Dosya hatası: " + e.getMessage());
}
}
// Seçenek 2: Yukarı bildir
public void processFile() throws IOException {
readFile("data.txt");
// IOException'ı biz yakalamıyoruz, çağırana bırakıyoruz
}⚠️ throws zinciri: Eğer herkes
throwsile yukarı bildirirse, sonundamainmethod'a kadar çıkar.mainde bildirirse, JVM default exception handler'a düşer (program çöker, stack trace basılır).
public static void main(String[] args) throws IOException {
readFile("data.txt");
// IOException olursa program çöker — kötü kullanıcı deneyimi
}throw ve throws Birlikte
Tipik senaryo: method hem kendi exception'ını fırlatır, hem de bunu bildirir.
public class FileValidator {
public static void validateFile(String path) throws IOException {
if (path == null || path.isEmpty()) {
throw new IllegalArgumentException("Dosya yolu boş olamaz");
// Bu unchecked — throws'a eklemeye gerek yok
}
File file = new File(path);
if (!file.exists()) {
throw new FileNotFoundException("Dosya bulunamadı: " + path);
// Bu checked — throws IOException ile kapsanıyor
// (FileNotFoundException extends IOException)
}
if (!file.canRead()) {
throw new IOException("Dosya okunamıyor: " + path);
}
System.out.println("Dosya geçerli: " + path);
}
}Custom Exception Yazma
JDK'daki exception'lar bazen yeterli gelmez. Kendi iş mantığına özel exception'lar yazman gerekir. Çok basit:
// Checked custom exception
public class InsufficientFundsException extends Exception {
private final double amount;
private final double balance;
public InsufficientFundsException(double amount, double balance) {
super(String.format("%.2f çekilemez, bakiye: %.2f", amount, balance));
this.amount = amount;
this.balance = balance;
}
public double getAmount() { return amount; }
public double getBalance() { return balance; }
}// Unchecked custom exception
public class UserNotFoundException extends RuntimeException {
private final long userId;
public UserNotFoundException(long userId) {
super("Kullanıcı bulunamadı: ID=" + userId);
this.userId = userId;
}
public long getUserId() { return userId; }
}Kullanımı:
public class BankService {
public void withdraw(Account account, double amount)
throws InsufficientFundsException {
if (amount > account.getBalance()) {
throw new InsufficientFundsException(amount, account.getBalance());
}
account.setBalance(account.getBalance() - amount);
}
}
public class UserService {
public User findById(long id) {
User user = database.find(id);
if (user == null) {
throw new UserNotFoundException(id); // Unchecked — throws gerekmez
}
return user;
}
}Checked mı unchecked mı yapayım? Genel kural:
Çağıranın mantıklı bir kurtarma yolu varsa → checked
Programcı hatası veya kurtarılamaz durum → unchecked
Şüpheye düşersen → modern Java dünyası unchecked'ı tercih eder
Custom Exception İçin 4 Constructor Pattern
Standart exception sınıflarının 4 constructor'ı var. Kendi exception'larında da bunları sağlamak iyi pratik:
public class AppException extends RuntimeException {
// 1. Parametresiz
public AppException() {
super();
}
// 2. Sadece mesaj
public AppException(String message) {
super(message);
}
// 3. Mesaj + cause
public AppException(String message, Throwable cause) {
super(message, cause);
}
// 4. Sadece cause
public AppException(Throwable cause) {
super(cause);
}
}En azından 2. ve 3. constructor'ları mutlaka yaz. cause parametresi "exception chaining" için kritik — birazdan göreceğiz.
Exception Chaining (Zincirleme)
Bazen bir exception'ı yakalayıp, başka bir exception olarak yeniden fırlatırsın. Ama orijinal hatayı kaybetmemek istersin. İşte cause burada devreye girer.
public class DataService {
public List<User> getUsers() {
try {
return database.query("SELECT * FROM users");
} catch (SQLException e) {
// Orijinal exception'ı cause olarak ekliyoruz
throw new DataAccessException("Kullanıcılar alınamadı", e);
}
}
}// Chaining olmadan — orijinal hata kaybolur!
catch (SQLException e) {
throw new DataAccessException("Hata oluştu"); // e nereye gitti?
}
// Chaining ile — orijinal hata korunur
catch (SQLException e) {
throw new DataAccessException("Hata oluştu", e); // e cause olarak saklanır
}Yakalayan tarafta orijinal hataya erişebilirsin:
try {
dataService.getUsers();
} catch (DataAccessException e) {
System.out.println("Üst hata: " + e.getMessage());
System.out.println("Asıl sebep: " + e.getCause().getMessage());
e.printStackTrace(); // Tüm zinciri gösterir
}Stack trace çıktısı şöyle görünür:
DataAccessException: Kullanıcılar alınamadı
at DataService.getUsers(DataService.java:12)
at Main.main(Main.java:5)
Caused by: java.sql.SQLException: Connection refused
at Database.query(Database.java:45)
at DataService.getUsers(DataService.java:10)
... 1 more💡 İpucu: "Caused by" kısmı debug yaparken altın değerinde. Exception chaining yapmazsan bu bilgi kaybolur ve hata bulmak çok zorlaşır.
initCause() ile Sonradan Ekleme
Bazı eski exception sınıflarında cause parametreli constructor yoktur. Bu durumda initCause() kullanabilirsin:
try {
// ...
} catch (IOException e) {
NoSuchElementException nse = new NoSuchElementException("Eleman bulunamadı");
nse.initCause(e); // Sonradan cause ekleme
throw nse;
}Ama modern kodda genellikle constructor ile geçirmeyi tercih et.
Re-throw: Yakalayıp Tekrar Fırlatma
Bazen exception'ı yakalar, bir şey yaparsın (loglama gibi), sonra tekrar fırlatırsın:
public void process() throws IOException {
try {
readData();
} catch (IOException e) {
logger.error("Veri okunamadı", e);
throw e; // Aynı exception'ı tekrar fırlat
}
}Java 7+ ile compiler daha akıllıdır — re-throw edilen exception'ın gerçek türünü tanır:
public void handle() throws FileNotFoundException, ParseException {
try {
// FileNotFoundException veya ParseException fırlatabilir
openAndParse();
} catch (Exception e) {
logger.error("Hata", e);
throw e; // Compiler bilir ki e sadece FileNotFoundException veya ParseException olabilir
// throws'a Exception yazmana gerek yok
}
}throws ve Kalıtım
Override edilen method'larda throws kuralları var:
class Parent {
public void doSomething() throws IOException {
// ...
}
}
class Child extends Parent {
// OK — aynı exception
@Override
public void doSomething() throws IOException { }
// OK — daha spesifik exception
// public void doSomething() throws FileNotFoundException { }
// OK — hiç exception bildirmemek
// public void doSomething() { }
// HATA — daha geniş exception bildiremezsin
// public void doSomething() throws Exception { } // Compile error!
}⚠️ Kural: Override eden method, üst sınıf method'undan daha geniş checked exception bildiremez. Aynı veya daha dar olmalı. Unchecked exception'lar bu kuraldan muaf.
Bu kural Liskov Substitution Principle ile ilgili. Eğer alt sınıf daha fazla exception fırlatabilseydi, üst sınıf referansıyla çalışan kod beklenmedik exception'larla karşılaşırdı.
Gerçek Dünya: Katmanlı Mimari
Büyük projelerde exception'lar katmanlar arasında dönüştürülür:
// Repository katmanı — veritabanı exception'ları
public class UserRepository {
public User findById(long id) throws SQLException {
// JDBC kodu
ResultSet rs = stmt.executeQuery("SELECT * FROM users WHERE id = " + id);
if (!rs.next()) {
return null;
}
return mapToUser(rs);
}
}
// Service katmanı — iş mantığı exception'ları
public class UserService {
private final UserRepository repo;
public User getUser(long id) {
try {
User user = repo.findById(id);
if (user == null) {
throw new UserNotFoundException(id);
}
return user;
} catch (SQLException e) {
throw new DataAccessException("Kullanıcı sorgulanamadı", e);
}
}
}
// Controller katmanı — HTTP response'a dönüştür
public class UserController {
private final UserService service;
public Response getUser(long id) {
try {
User user = service.getUser(id);
return Response.ok(user);
} catch (UserNotFoundException e) {
return Response.notFound(e.getMessage());
} catch (DataAccessException e) {
return Response.serverError("Sunucu hatası");
}
}
}Her katman kendi seviyesine uygun exception kullanır. Alt katmanın teknik detayları üst katmana sızmaz. Bu abstraction prensibidir.
Exception Factory Pattern
Çok sayıda benzer exception fırlatıyorsan, factory method'lar temiz bir çözüm:
public class Exceptions {
public static UserNotFoundException userNotFound(long id) {
return new UserNotFoundException("Kullanıcı bulunamadı: " + id);
}
public static ValidationException invalidField(String field, Object value) {
return new ValidationException(
String.format("Geçersiz %s değeri: %s", field, value)
);
}
public static PermissionException accessDenied(String resource) {
return new PermissionException("Erişim reddedildi: " + resource);
}
}
// Kullanım — temiz ve okunabilir
throw Exceptions.userNotFound(42);
throw Exceptions.invalidField("email", "not-an-email");
throw Exceptions.accessDenied("/admin/users");Özet
throw exception nesnesi fırlatır —
throw new XException("mesaj")— o andan itibaren normal akış dururthrows method imzasında bildirim yapar — checked exception fırlatan method'lar bunu yazmak zorunda
Custom exception yazmak basit:
ExceptionveyaRuntimeException'dan extend et, en az mesajlı ve cause'lu constructor'ları ekleException chaining orijinal hatayı kaybetmemek için kritik — her zaman
causeparametresini geçirOverride kuralı: Alt sınıf, üst sınıftan daha geniş checked exception bildiremez
Katmanlı mimaride her katman kendi seviyesine uygun exception kullanır — alt katman detayları yukarı sızmamalı
AI Asistan
Sorularını yanıtlamaya hazır