1. Giriş
Java-da eyni anda bir neçə sinfi miras almaq olmaz. Bu, “ölüm almazı” — iki valideyn sinifin eyni metodu təyin etdiyi və hansı implementasiyanı götürəcəyin aydın olmadığı vəziyyətdən qaçmaq üçün qəsdən edilib. Amma interfeysləri istədiyin qədər implementasiya etmək olar! Niyə? Çünki interfeys — sadəcə müqavilədir, o, implementasiya daşımır (Java 8-ə qədər — implementasiya yox idi; Java 8-dən etibarən default və static metodlar mümkündür). Deməli, kod mirasalma qarışıqlığı yaranmır.
Bu, real həyatda olan vəziyyətə bənzəyir: siz eyni anda “Sürücü”, “Kompüter istifadəçisi” və “Üzgüçü” ola bilərsiniz. Bu “interfeyslərin” hər biri müəyyən bacarıqları təsvir edir, amma sizi başqa bir insanın surəti olmağa məcbur etmir.
İnterfeyslərin çoxsaylı implementasiyasının sintaksisi
Java-da sinif bir neçə interfeysi implementasiya edə bilər, onları implements açar sözündən sonra vergüllə sadalamaqla. Budur əsas nümunə:
public interface Movable {
void move(int x, int y);
}
public interface Chargeable {
void charge();
}
public class Robot implements Movable, Chargeable {
@Override
public void move(int x, int y) {
System.out.println("Robot (" + x + ", " + y + ") nöqtəsinə hərəkət edir");
}
@Override
public void charge() {
System.out.println("Robot şarj olunur.");
}
}
Bu nümunədə Robot — universal biridir: həm hərəkət edir, həm də şarj olunur. Həyatda olduğu kimi: nə qədər çox bacarırsansa — səni bir o qədər tez-tez müsahibəyə çağırırlar!
2. Nəyə görə lazımdır? Praktik nümunələr
Nümunə 1. Obyektin müxtəlif “rolları”
Təsəvvür edin ki, siz oyun personajını layihələndirirsiniz:
- O hərəkət edə bilər (Movable)
- Hücum edə bilər (Attackable)
- Fayla yazıla bilər (Serializable — belə bir interfeys Java-nın standart kitabxanasında var)
public interface Attackable {
void attack();
}
public class Hero implements Movable, Attackable, java.io.Serializable {
@Override
public void move(int x, int y) {
System.out.println("Qəhrəman yeni mövqeyə keçir.");
}
@Override
public void attack() {
System.out.println("Qəhrəman zərbə endirir!");
}
}
İndi sinfiniz müxtəlif kontekstlərdə istifadə oluna bilər: onu bu interfeyslərdən hər hansı birini tələb edən metodlara ötürmək olar.
Nümunə 2. Standart interfeyslərin birləşdirilməsi
Java-nın standart kitabxanasında tez-tez Comparable (obyektlərin müqayisəsi üçün) və Serializable (obyektləri fayla saxlamaq və ya şəbəkə ilə ötürmək üçün) interfeyslərinə rast gəlinir. Bəzən obyektin hər ikisi olması lazım olur:
public class Person implements Comparable<Person>, java.io.Serializable {
private String name;
private int age;
public Person(String name, int age) { this.name = name; this.age = age; }
@Override
public int compareTo(Person other) {
return Integer.compare(this.age, other.age);
}
}
Artıq Person obyektlərini çeşidləmək (məsələn, siyahıda) və fayla yazmaq olar.
3. Xüsusiyyətlər və məhdudiyyətlər
Hər metod üçün bir implementasiya
Əgər iki interfeys eyni imzalı metodu müəyyən edirsə, onu yalnız bir dəfə implementasiya etmək lazımdır. Nümunə:
public interface A {
void doSomething();
}
public interface B {
void doSomething();
}
public class MyClass implements A, B {
@Override
public void doSomething() {
System.out.println("Hər iki interfeys üçün doSomething implementasiyası.");
}
}
Java etiraz etməyəcək — əsas odur ki, imzalar üst-üstə düşsün. Əgər metodlar imza baxımından fərqlidirsə, onlar müxtəlif metodlar sayılır — hər birini ayrıca implementasiya etməlisiniz.
“Ölüm almazı” yoxdur
Siniflərin çoxsaylı miras almasından fərqli olaraq, bir neçə interfeysi implementasiya edərkən eyni metodun iki fərqli implementasiyasını miras almaq vəziyyəti yaranmır. Java 8-ə qədər interfeyslərdə ümumiyyətlə implementasiya yox idi, default metodlar yarandıqdan sonra isə konflikt çıxsa, onu açıq şəkildə həll etməlisiniz (bu barədə daha ətraflı növbəti mühazirədə).
Vəziyyət (state) yoxdur
İnterfeyslər adi sahələr (yalnız sabitlər — public static final) saxlaya bilməz. Buna görə “eyni adlı iki valideyn sahəsi” ilə bağlı qarışıqlıq yaranmır.
4. Nümunə: bir sinifdə bir neçə interfeysi implementasiya edirik
Gəlin tədris tətbiqimizə (məsələn, zooparka) yeni imkanlar əlavə edək. Tutaq ki, bizdə həm hərəkət edə, həm də səs çıxara bilən heyvanlar var:
public interface Movable {
void move(int x, int y);
}
public interface Soundable {
void makeSound();
}
public class Dog implements Movable, Soundable {
private String name;
public Dog(String name) {
this.name = name;
}
@Override
public void move(int x, int y) {
System.out.println(name + " (" + x + ", " + y + ") tərəfə qaçır");
}
@Override
public void makeSound() {
System.out.println(name + " deyir: Hav-hav!");
}
}
public class Cat implements Movable, Soundable {
private String name;
public Cat(String name) {
this.name = name;
}
@Override
public void move(int x, int y) {
System.out.println(name + " (" + x + ", " + y + ") tərəfə gizlicə yaxınlaşır");
}
@Override
public void makeSound() {
System.out.println(name + " deyir: Miyav!");
}
}
İndi istənilən “hərəkət edən” və ya “səs çıxaran” obyektlə işləmək üçün universal metod yaza bilərik:
public static void testMovable(Movable m) {
m.move(10, 20);
}
public static void testSoundable(Soundable s) {
s.makeSound();
}
public static void main(String[] args) {
Dog rex = new Dog("Reks");
Cat murka = new Cat("Murka");
testMovable(rex); // Reks (10, 20) tərəfə qaçır
testSoundable(murka); // Murka deyir: Miyav!
}
Və əlbəttə, obyekt hər iki interfeysi implementasiya edirsə, onu hər iki metoda da ötürmək olar!
6. Faydalı incəliklər
İnterfeyslər toqquşanda?
Bəzən elə olur ki, iki interfeys eyni imzalı, amma fərqli məna daşıyan metodlar müəyyən edir. Məsələn, bir interfeys reset() metodunun koordinatları sıfırlamasını, digəri isə həmin metodun cihazı söndürməsini gözləyir. Bu halda diqqətli olmaq gərəkdir: metodu yenə də bir dəfə implementasiya etməlisiniz və o, hər iki davranışı “bacarmalıdır” (ya da ən azı hansını edəcəyini seçməlidir). Real həyatda belə hallar nadirdir, amma rast gəlsəniz — dizaynın düzgünlüyü barədə düşünməyə dəyər.
Müxtəlif interfeyslərə aid obyektlərdən ibarət kolleksiya nümunəsi
Tutaq ki, müxtəlif interfeysləri implementasiya edən obyektlərdən ibarət siyahımız var. Onları dövrə salıb lazım olan metodları çağırmaq olar:
Movable[] movables = {
new Dog("Şarik"),
new Cat("Barsik"),
new Robot()
};
for (Movable m : movables) {
m.move(0, 0);
}
Eyni şeyi istənilən interfeys üçün etmək olar.
7. İnterfeyslərin çoxsaylı implementasiyasında tipik səhvlər
Xəta №1: interfeyslərin bütün metodları implementasiya edilməyib.
Sinif interfeysi implementasiya etdiyini elan edibsə, amma ən azı bir metodunu belə reallaşdırmayıbsa — kompilyator dərhal xəta verəcək. Bütün metodları unutmayın, hətta onlar “artıq” görünsə belə.
Xəta №2: eyni imzalı, amma ziddiyyətli metodlar.
Əgər iki interfeys eyni metodları müəyyən edirsə, onları yalnız bir dəfə implementasiya etmək lazımdır. Amma bu metodların mənası fərqlidirsə, bu, qarışıqlığa və xətalara səbəb ola bilər. Bu halda memarlığı yenidən düşünmək daha yaxşıdır.
Xəta №3: sinifdə interfeysi extends ilə “miras almağa” cəhd.
Sinifdə interfeysi implementasiya etmək üçün həmişə implements istifadə olunur, extends yox. Məsələn:
public class MyClass implements A, B { ... } // doğru
public class MyClass extends A, B { ... } // səhv!
Xəta №4: interfeys obyektini yaratmağa cəhd.
İnterfeys — müqavilədir, onu birbaşa yaratmaq olmaz:
Movable m = new Movable(); // kompilyasiya xətası
Yalnız interfeysi implementasiya edən siniflərin obyektlərini yaratmaq olar.
GO TO FULL VERSION