Java'da arayüz (interface), bir sınıfın hangi metotları sunması gerektiğini belirleyen ancak bu metotların nasıl çalışacağını söylemeyen bir sözleşmedir. Bir sınıf bu sözleşmeyi implements anahtar kelimesiyle uyguladığında, arayüzdeki tüm metotlara kendi mantığıyla gövde yazmak zorundadır. Bu sayede farklı sınıflar aynı arayüzü uygulayarak ortak bir davranışı kendi yöntemlerine göre gerçekleştirebilir. Aşağıda arayüzün ne olduğunu, bir sınıfın onu ne zaman uygulaması gerektiğini ve basit bir bildirim sistemi örneğini inceleyeceğiz.
Interface Nedir?
Interface, Java'da yalnızca metot imzalarından oluşan bir yapıdır. Yani bir metodun adını, parametrelerini ve dönüş tipini tanımlar ama içeriğini, yani gövdesini yazmaz. Bu haliyle interface, "bu işi yapan bir sınıf, şu metotları sunmalı" diyen bir kural listesi gibi düşünülebilir.
Basit bir örnek:
public interface Notification {
void send(String message);
}
Burada Notification arayüzü, send adında bir metot tanımlar ama bu metodun bir e-posta mı göndereceğini, SMS mi atacağını yoksa başka bir kanal mı kullanacağını belirtmez. Bu detay, arayüzü uygulayan sınıfların sorumluluğundadır. Interface içinde alan (field) tanımlanabilir ama bunlar otomatik olarak public static final olur, yani sabit değer niteliğindedir; sınıfların taşıdığı türden değişken durum (state) tutmaz. Java 8 ve sonrasında arayüzlere default ve static metotlarla gövdeli metot eklenebilse de, arayüzün temel amacı hâlâ davranışı değil sözleşmeyi tanımlamaktır.
Bir Sınıf Interface'i Ne Zaman Uygular?
Bir sınıf, bir arayüzdeki tüm metotları kendi mantığıyla gerçekleştirmek istediğinde o arayüzü implements anahtar kelimesiyle uygular. Bunun tipik senaryosu, aynı davranışın birden fazla şekilde gerçekleştirilmesi gerektiği durumlardır. Örneğin bir bildirim gönderme işlemi; e-posta, SMS ya da push bildirimi gibi farklı kanallardan yapılabilir. Bu kanalların her biri farklı çalışsa da, dışarıdan bakıldığında hepsi "bildirim gönderir" diye özetlenebilir.
İşte bu noktada bir arayüz tanımlanır, her kanal bu arayüzü uygulayan ayrı bir sınıf olarak yazılır. Böylece kodun diğer kısımları, hangi sınıfla çalıştığını bilmeden sadece arayüzdeki metodu çağırarak işlemi tetikleyebilir. Bir sınıfın arayüz uygulaması için şu üç koşulun birlikte anlamlı olması beklenir: davranış birden fazla varyasyona sahip olmalı, bu varyasyonlar dışarıya aynı arayüzle görünmeli ve ileride yeni varyasyonların eklenmesi olası olmalı.
Örnek: Bildirim Sistemi (EmailNotification, SmsNotification)
Yukarıdaki Notification arayüzünü iki farklı sınıfla uygulayalım:
public class EmailNotification implements Notification {
@Override
public void send(String message) {
System.out.println("E-posta gönderildi: " + message);
}
}
public class SmsNotification implements Notification {
@Override
public void send(String message) {
System.out.println("SMS gönderildi: " + message);
}
}
Her iki sınıf da send metodunu kendi mantığıyla dolduruyor. EmailNotification bir e-posta gönderme işlemini simüle ederken SmsNotification bir SMS gönderme işlemini simüle ediyor. Arayüz sayesinde her iki sınıf da aynı sözleşmeye uyduğundan, bu sınıfları kullanan kod, hangi bildirim türüyle çalıştığını bilmeden send metodunu çağırabilir. Bu, sonraki bölümde ele alınacak referans türü üzerinden çalışmanın da temelini oluşturur.
Referans Türü Üzerinden Nesne Kullanımı
Bir sınıf interface uyguladığında, o sınıfın nesnesini interface türünden bir referansla tutabilirsiniz. Bu, Java'daki polimorfizmin temelini oluşturur.
Notification bildirim = new EmailNotification();
bildirim.gonder("Siparişiniz onaylandı.");
bildirim = new SmsNotification();
bildirim.gonder("Kargonuz yola çıktı.");
Burada bildirim değişkeninin türü Notification interface'idir, ama içinde tuttuğu nesne çalışma zamanında EmailNotification ya da SmsNotification olabilir. gonder() metodunu çağırdığınızda hangi sınıfın kodunun çalışacağına Java, nesnenin gerçek türüne bakarak karar verir. Buna dinamik bağlama (dynamic binding) denir.
Bu yaklaşımın pratik faydası, kodu belirli bir sınıfa bağımlı yazmamanızdır. Örneğin bir liste üzerinde farklı bildirim türlerini tek tip referansla dolaşabilirsiniz:
List<Notification> bildirimler = new ArrayList<>();
bildirimler.add(new EmailNotification());
bildirimler.add(new SmsNotification());
for (Notification n : bildirimler) {
n.gonder("Genel duyuru");
}
Döngü, listedeki nesnelerin gerçek sınıfını bilmeden gonder() metodunu çağırır. Yeni bir bildirim türü eklendiğinde bu döngüde herhangi bir değişiklik yapmanız gerekmez, sadece listeye yeni nesneyi eklemeniz yeterlidir.
Birden Fazla Interface Uygulama
Java'da bir sınıf sadece bir sınıftan kalıtım alabilir, ama birden fazla interface'i aynı anda uygulayabilir. Bu, interface'lerin en güçlü özelliklerinden biridir. Uygulama, virgülle ayrılarak yapılır:
interface Loglanabilir {
void logYaz(String mesaj);
}
class EmailNotification implements Notification, Loglanabilir {
public void gonder(String mesaj) {
System.out.println("E-posta gönderildi: " + mesaj);
}
public void logYaz(String mesaj) {
System.out.println("Log: " + mesaj);
}
}
EmailNotification sınıfı burada iki farklı sorumluluğu üstlenir: bildirim gönderme ve loglama. Her iki interface'in metotlarını da uygulamak zorundadır, aksi halde kod derlenmez. Bu sayede bir sınıfın hangi davranışları desteklediğini interface listesine bakarak anlayabilirsiniz.
Interface ile Sınıf Arasındaki Sorumluluk Farkı
Interface ve sınıf arasındaki ayrımı özetlemek gerekirse: interface "ne yapılacağını", sınıf ise "nasıl yapılacağını" belirler.
Notification interface'i sadece bir bildirimin gönderilmesi gerektiğini söyler, ama bu gönderimin e-posta sunucusu üzerinden mi, SMS servisi üzerinden mi yapılacağını bilmez ve bilmesine de gerek yoktur. Bu detay, interface'i uygulayan sınıfın sorumluluğundadır.
Bu ayrım, kodun farklı katmanlarını birbirinden bağımsız hale getirir. Bildirim gönderen bir servis, Notification interface'ine bağımlı çalışırsa, e-posta gönderim mantığının SMTP mi yoksa başka bir kütüphane mi kullandığıyla ilgilenmez. Sadece gonder() metodunun çağrılabileceğini bilir. Bu sayede uygulama detayları değiştiğinde, interface'e bağımlı kodun etkilenmemesi sağlanır.
Ne Zaman Interface Kullanmalısınız?
Bir interface kullanma kararı genellikle şu soruya verilen cevaba bağlıdır: "Aynı davranışı farklı sınıflar farklı şekillerde mi gerçekleştirecek?" Cevap evet ise interface doğru araçtır. Örneğin bildirim sistemi örneğinde Notification interface'i, "bildirim gönder" davranışının ortak bir sözleşmesini tanımlar. EmailNotification ve SmsNotification sınıfları bu sözleşmeyi kendi mantıklarına göre doldurur.
Eğer sınıflar arasında yalnızca kod tekrarını önlemek amacıyla ortak bir davranış paylaşılıyorsa, bu genellikle kalıtım (miras) ile çözülür. Ancak sınıfların birbirine benzemesi gerekmiyor, sadece aynı sözleşmeyi yerine getirmeleri gerekiyorsa interface daha uygun bir seçimdir. Bu sayede sınıflar birbirinden bağımsız kalır ve yeni bir davranış türü eklemek mevcut kodu bozmaz.
Interface kullanımı özellikle bağımlılıkları soyutlamak istediğinizde değer kazanır. Bir metot parametre olarak somut bir sınıf değil, interface bekliyorsa, o metodu değiştirmeden yeni davranış türleri eklemek mümkün olur. Bu da kodun test edilebilirliğini ve genişletilebilirliğini artırır.
Kısacası, davranışın "ne yapılacağı" sabitken "nasıl yapılacağı" değişkense interface tercih edilmelidir.
Sık Sorulan Sorular
Interface içinde alan (field) tanımlanabilir mi?
Interface içinde yalnızca public static final nitelikte sabitler tanımlanabilir. Nesneye özgü, değişebilen alanlar interface içinde yer alamaz; bu tür veriler interface'i uygulayan sınıflarda tutulur.
Bir sınıf hem bir sınıftan miras alıp hem interface uygulayabilir mi?
Evet. Java'da bir sınıf extends ile tek bir sınıftan miras alabilir ve aynı zamanda implements ile birden fazla interface uygulayabilir. Bu iki mekanizma birbirini engellemez.
Interface ile abstract class arasındaki temel fark nedir?
Abstract class, ortak durum ve kısmi davranış paylaşan sınıflar için uygundur ve tekli kalıtım sınırına tabidir. Interface ise sadece sözleşme tanımlar, durum tutmaz ve bir sınıf birden fazla interface uygulayabilir.
Interface uygulamayı unutan bir sınıf ne olur?
Bir sınıf implements ile bir interface'i belirtip onun soyut metotlarından birini uygulamazsa derleyici hata verir. Sınıfın interface'in tüm metotlarını uygulaması veya kendisinin de abstract olarak tanımlanması gerekir.
Interface kullanımı, kodunuzu esnek ve bakımı kolay tutmanın pratik bir yoludur. Doğru yerde kullanıldığında, sistemin büyümesi ve değişmesi çok daha az sürtünmeyle gerçekleşir.