1. Metodların yenidən təyin edilməsi
Yenidən təyin etmə (overriding) — alt sinifdə artıq valideyn sinifdə olan metodun öz versiyasını yazmaq imkanıdır. Bunun sayəsində proqramın icrası zamanı (run-time) həqiqi polimorfizm işləyir.
Sadə dillə:
Əgər sizdə metodlu baza sinfi varsa və alt sinifin həmin metodu özünəməxsus şəkildə icra etməsini istəyirsinizsə, alt sinifdə eyni siqnaturlu metodu elan edirsiniz. Metodu baza tipli istinad vasitəsilə çağırdıqda, obyektin faktiki tipinə məxsus metod versiyası işə düşəcək.
Həyatdan nümunə.
Təsəvvür edin, heyvanlardan ibarət bir komandanız var və siz hər birindən “səs çıxarmağı” xahiş edirsiniz. Hamısı üçün komanda makeSound()-dır, amma it havlayır, pişik miyau edir, inək isə möö deyir. Koddakı çağırış eyni metod kimidir, amma nəticə fərqlidir — bax budur, yenidən təyin etmənin sehri!
Yenidən təyin etmənin sintaksisi
Baza nümunə
class Animal {
void makeSound() {
System.out.println("Heyvan hansısa səs çıxarır");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Hav!");
}
}
class Cat extends Animal {
@Override
void makeSound() {
System.out.println("Miyau!");
}
}
Burada Animal sinfində makeSound() metodu müəyyən edilib. Dog və Cat alt sinifləri bu metodu yenidən təyin edir, öz reallaşdırmalarını təqdim edirlər.
@Override annotasiyası
Java-da yenidən təyin edilən metodları @Override annotasiyası ilə işarələmək tövsiyə olunur (və bu yaxşı vərdişdir):
@Override
void makeSound() { ... }
Bu, kodun işləməsi üçün məcburi deyil, amma:
- Kompilyator həqiqətən də metodu yenidən təyin etdiyinizi (adı və ya parametrləri səhv yazmadığınızı) yoxlayacaq.
- Kodun oxunaqlılığını artırır — digər proqramçılar dərhal görür ki, bu metod valideyn metodu yenidən təyin edir.
Yenidən təyin edilmiş metodun çağırılması
Animal myDog = new Dog();
myDog.makeSound(); // Çap edəcək: Hav!
Baxmayaraq ki, dəyişənin tipi Animal-dır, o, əslində Dog obyektinə işarə edir və Dog sinfinin metodu çağırılır. Bu, dinamik (gec) bağlama deməkdir.
2. Yenidən təyin etmənin (overriding) overloading-dən fərqi
Overloading (metod yüklənməsi)
- Eyni sinifdə (və ya ierarxiyada, amma həmişə “yanaşı”).
- Metodların adı eynidir, amma parametrləri (tipi, sayı, sırası) fərqlidir.
- Metod seçimi kompilyasiya zamanı baş verir.
void print(int x) { ... }
void print(String s) { ... }
Overriding (yenidən təyin etmə)
- Fərqli siniflərdə: metod supersinifdə elan olunur və altsinifdə yenidən təyin edilir.
- Metodların adı və siqnaturları eynidir (parametrlər və qaytarılan tip).
- Metod seçimi icra zamanı (run-time) obyektin faktiki tipinə əsasən baş verir.
Müqayisə cədvəli
| Overloading | Overriding | |
|---|---|---|
| Harada | Eyni sinifdə | Üst sinifdə və alt sinifdə |
| Metodun adı | Eyni | Eyni |
| Parametrlər | Fərqli | Eyni |
| Qaytarılan tip | Fərqli ola bilər | Eyni olmalı və ya alt tip olmalıdır |
| Seçim nə vaxt olur | Kompilyasiya | İcra (run-time) |
| Annotasiya | Tələb olunmur | @Override (tövsiyə olunur) |
3. Metodların yenidən təyin edilməsinin qaydaları
Yenidən təyin etmə güclü bir alətdir, amma onunla bağlı sərt qaydalar var. Gəlin onları ardıcıllıqla nəzərdən keçirək.
Metodun siqnaturu üst-üstə düşməlidir
- Metodun adı, parametrlərin tipləri və sırası supersinifdəki metodla tam eyni olmalıdır.
- Qaytarılan tip eyni olmalı və ya kovariant olmalıdır (yəni valideyn metodunun qaytarılan tipinin alt tipi).
Kovariant qaytarılan tip nümunəsi:
class Animal {
Animal reproduce() { return new Animal(); }
}
class Cat extends Animal {
@Override
Cat reproduce() { return new Cat(); } // OK! Cat, Animal-ın alt tipidir
}
Giriş modifikatoru
Yenidən təyin edilmiş metodun giriş modifikatoru supersinifdəki metoddan daha sərt ola bilməz. Əgər valideyndə metod public-dirsə, altsinifdə də mütləq public qalmalıdır. Onu daha az əlçatan (protected və ya private) etmək olmaz.
Nümunə:
class Parent {
public void greet() { }
}
class Child extends Parent {
// void greet() { } // Səhv! Susmaya görə modifikator — package-private, public-dən daha məhduddur
@Override
public void greet() { } // OK
}
İstisnalar
- Yenidən təyin edilmiş metod baza metodunda elan olunmamış yeni checked istisnalar ata bilməz.
- Daha az və ya eyni istisnaları ata bilər.
Nümunə:
class Parent {
void doWork() throws IOException { }
}
class Child extends Parent {
@Override
void doWork() throws FileNotFoundException { } // OK, FileNotFoundException, IOException-un alt tipidir
// void doWork() throws SQLException { } // Səhv! SQLException valideyndə elan olunmayıb
}
Statik metodlar yenidən təyin edilmir
Statik metodlar gizlədilə bilər (hidden), lakin yenidən təyin edilmir. Alt sinifdə eyni siqnaturlu statik metod elan etsəniz, bu yenidən təyin etmə deyil! Bu, sadəcə metodun gizlədilməsidir, polimorfizm deyil.
class Animal {
static void info() { System.out.println("Animal"); }
}
class Dog extends Animal {
static void info() { System.out.println("Dog"); }
}
Dog.info() çağırışı "Dog" çap edəcək, amma baza tipli Animal dəyişəni vasitəsilə çağırılarsa, Animal.info() metodu çağırılacaq. Bu, polimorfizm deyil!
final metodlar yenidən təyin edilə bilməz
Əgər supersinifdə metod final kimi elan edilibsə, onu yenidən təyin etməyə cəhd kompilyasiya xətası verəcək.
class Animal {
final void sleep() { }
}
class Dog extends Animal {
// @Override
// void sleep() { } // Səhv! final metodu yenidən təyin etmək olmaz
}
4. Praktik nümunələr
Gəlin praktikada yenidən təyin etmənin necə işlədiyini və onun overloading-dən nə ilə fərqləndiyini baxaq.
Nümunə 1: Shape sinfi və onun törəmələri
class Shape {
void draw() {
System.out.println("Fiqur çəkirik");
}
}
class Circle extends Shape {
@Override
void draw() {
System.out.println("Dairə çəkirik");
}
}
class Rectangle extends Shape {
@Override
void draw() {
System.out.println("Düzbucaqlı çəkirik");
}
}
Polimorfizmdən istifadə:
public class Main {
public static void main(String[] args) {
Shape s1 = new Circle();
Shape s2 = new Rectangle();
s1.draw(); // Dairə çəkirik
s2.draw(); // Düzbucaqlı çəkirik
}
}
Dəyişənlər Shape kimi elan olunsa da, məhz obyektin aid olduğu sinfin metodu çağırılır.
Nümunə 2: Overloading-dən fərq
class Printer {
void print(String s) {
System.out.println("Sətir: " + s);
}
void print(int n) {
System.out.println("Rəqəm: " + n);
}
}
Burada hər iki metodun adı print-dir, lakin parametrləri fərqlidir — bu, overloading-dir, overriding deyil.
5. Yenidən təyin etmə və valideyn metodunun çağırılması (super)
Bəzən yenidən təyin edilmiş metodda əvvəlcə valideynin məntiqini icra edib, sonra özünüzünkünü əlavə etmək istəyirsiniz. Bunun üçün super açar sözündən istifadə olunur.
class Animal {
void makeSound() {
System.out.println("Heyvan səs çıxarır");
}
}
class Dog extends Animal {
@Override
void makeSound() {
super.makeSound(); // Valideyn metodunun çağırılması
System.out.println("Hav!");
}
}
new Dog().makeSound() çağırışı belə çıxış verəcək:
Heyvan səs çıxarır
Hav!
Dinamik bağlama (late binding) necə işləyir
Metodu baza tipli istinad vasitəsilə çağırdıqda, Java icra zamanı bu istinadın hansı faktiki obyektə aid olduğunu yoxlayır və məhz həmin obyektin sinfində müəyyən olunmuş metod versiyasını çağırır.
Animal a = new Cat();
a.makeSound(); // Cat.makeSound()-u çağıracaq, Animal.makeSound() yox
Bu, Java-da polimorfizmin əsasını təşkil edir.
6. Bu, sizin tətbiqinizlə necə bağlıdır
Tədris tətbiqimizdə (məsələn, əməkdaşların uçotu sistemi) Employee adlı baza sinfi və work() metodu yarada bilərsiniz, Manager və Developer altsinifləri isə bu metodu özlərinə görə reallaşdıra bilər:
class Employee {
void work() {
System.out.println("İşçi işləyir");
}
}
class Manager extends Employee {
@Override
void work() {
System.out.println("Menecer rəhbərlik edir");
}
}
class Developer extends Employee {
@Override
void work() {
System.out.println("Proqramçı kod yazır");
}
}
İndi bütün əməkdaşları eyni massivdə və ya siyahıda saxlaya bilərsiniz:
Employee[] employees = {new Manager(), new Developer(), new Developer()};
for (Employee e : employees) {
e.work(); // Hər biri üçün — öz çıxışı!
}
7. Metodların yenidən təyin edilməsində tipik səhvlər
Səhv №1: metodun adında və ya parametrlərdə yazı səhvi. Əgər səhvən metodun adını və ya parametrlərini səhv yazsanız, metodu yenidən təyin etmir, yenisini yaradırsınız. Nəticədə polimorfizm işləməyəcək. Məhz buna görə həmişə @Override annotasiyasından istifadə edin — kompilyator sizi dərhal xəbərdar edəcək.
Səhv №2: daha sərt giriş modifikatoru. Əgər valideyndə metod public-dirsə, uşaq sinifində onu protected və ya modifikatorsuz elan etsəniz, kompilyasiya xətası alacaqsınız.
Səhv №3: static və ya final metodu yenidən təyin etməyə cəhd. Statik metodlar yenidən təyin edilmir, final metodlar isə ümumiyyətlə yenidən təyin olunmur. Bunu etməyə çalışsanız — kompilyator sizi dayandıracaq.
Səhv №4: qaytarılan tipin uyğun olmayan tipə dəyişdirilməsi. Əgər altsinifdə metodun qaytarılan tipi supersinifdəki tiplə üst-üstə düşmürsə (və onun alt tipi deyilsə), kompilyator metodun yenidən təyin edilməsinə icazə verməyəcək.
Səhv №5: yeni checked istisnaların əlavə edilməsi. Yenidən təyin edilmiş metod baza metodunda olmayan yeni checked istisnalar ata bilməz. Bunu etsəniz — kompilyator xəta verəcək.
Səhv №6: super-i unutmaq. Əgər yenidən təyin edilmiş metodda valideyn davranışının bir hissəsini saxlamaq istəyirsinizsə, super.methodName() çağırışını mütləq edin. Java bunu özü avtomatik etməyəcək.
Artıq bilirsiniz ki, metodların yenidən təyin edilməsi necə işləyir, overloading-dən nə ilə fərqlənir və Java-da polimorfizm bunun sayəsində necə reallaşır. Növbəti mühazirədə polimorfizmdən praktikada necə istifadə etməyi — kolleksiyalar, massivlər və real tapşırıqlarla — nəzərdən keçirəcəyik!
GO TO FULL VERSION