Metodu override etmək – child classda parent classdakı metodla eyni signature (ad və parametr siyahısı) və geri dönüş tipində olan yeni metod yaratmaqdır. Metodu override etdikdən sonra artıq super
və this
açar sözlərindən istifadə edərək metodun cari yaxud parent versiyasından istifadə etmək istədiyimizi təyin edə bilərik:
class Dog { public int getAge() { return 8; } } class Husky extends Dog { public int getAge() { return super.getAge() + 3; // əgər super`i silsək, sonsuz dövrə düşəcək } public static void main(String[] args) { System.out.println(new Dog().getAge()); System.out.println(new Husky().getAge()); } }
Output:
8
11
Düzgün override olunmuş metod aşağıdakı qaydalara riayət etməlidir:
- Child classdakı metod parent classdakı metod ilə eyni quruluşa (the same signature) malik olmalıdır. Yəni, metod adları və parametr siyahısı mütləq eyni olmalıdır;
- Child classdakı metodun access modifier`i parent classdakı metodla ən azı eyni olmalıdır, ya da ki, daha yüksək;
- Parent classdakı metodun fırlatdığı (throws) exceptionun tipi həmişə child classdakı metoddan daha geniş olmalıdır. Child classdakı metod maksimum halda eyni exception tipini fırlada, yaxud da kiçik tip, və yaxud da heç exception fırlatmaya da bilər. Amma burada söhbət checked exceptionlardan gedir, runtime exceptionlara bu məhdudiyyət tətbiq olunmur;
- Hər iki metodun geriyə döndürdüyü dəyər eyni tipdə olmalıdır və ya override edilən/parent classdakı metodun subclassı (covariant return types).
* private metodlar override olunmur.
** Bu qaydalara nümunələr üzərində baxdıqda daha aydın olacaq.
Bu 4 qayda ilə bağlı nümunələrə keçməzdən öncə private
metodların override edilməməsi məsələsinə bir az aydınlıq gətirək. Bəli, parent classdakı private
metodlar override olunmur, hiding olur. Yəni əgər həmin metod parent classda çağırılırsa, parent classdakı metod işləyir, child classda çağırılırsa, child classdakı metod. Nümunə üzərindən baxaq:
public abstract class Bird { private void fly() { System.out.println("Bird is flying"); } public static void main(String[] args) { Bird bird = new Pelican(); bird.fly(); } } class Pelican extends Bird { protected void fly() { System.out.println("Pelican is flying"); } }
Nümunənin override ilə bağlı izahına keçməzdən əvvəl kodla bağlı bir maraqlı məqamı qeyd edim ki, əgər biz Pelican
classını run etsək həmin classın daxilində main
metod olmamasına baxmayaraq class run olacaq. Səbəb isə varisliklə bağlıdır. Əgər Pelican
`ın superclassının daxilində main
metod varsa, biz Pelican
classını run etdikdə o birbaşa gedib Bird
classının daxilindəki main
metodu çağırır.
İndi qayıdaq nümunəmizin override ilə bağlı hissəsinə. Hər iki classda fly()
metodu var və override olunmanın demək olar ki, bütün qaydalarına riayət edilir. Amma burada az öncə qeyd etdiyimiz istisna var, parent classdakı metod private
`dır, ona görə də bu override hesab olunmur, hiding hesab olunur. Kodu run etsək nəticə belə olacaqdır:
Bird is flying
Çünki fly()
metodu parent classda çağırılır. Biz əgər Pelican
classındakı fly()
metodunu commentə salsaq kod yenə compile olunacaq və run etdikdə eyni nəticəni alacağıq. Amma Bird
classındakı fly()
metodunu commentə salsaq kod compile olunmayacaqdır. Çünki bird
referansı Bird
classına aiddir və o compile vaxtı Bird
classında belə bir metodun olmadığını aşkarlayır və xəta verir. Əgər Bird bird = new Pelican();
əvəzinə Pelican bird = new Pelican();
yazsaq kod compile olunacaq və nəticə Pelican is flying
olacaqdır.
Əgər biz main
metodunu Bird
classından çıxarıb Pelican
classında yazsaq, kod yenə compile olunmayacaqdır. Çünki Bird
classındakı fly()
metodu private
`dır və subclass onu görə bilmir:
public abstract class Bird { private void fly() { System.out.println("Bird is flying"); } } class Pelican extends Bird { protected void fly() { System.out.println("Pelican is flying"); } public static void main(String[] args) { Bird bird = new Pelican(); bird.fly(); // DOES NOT COMPILE } }
4-cü bənddə “covariant return types” ifadəsi işlətdik, bunu biraz daha geniş izah edək. Deməli, bu xüsusiyyət Java`ya 1.5-ci versiyadan sonra gəlib. Anlamı isə odur ki, override edən (overriding) metodun return tipi override edilən (overridden) metodun return tipinin subclassı ola bilər. Yəni, əgər super classdakı metodun return tipi Number
olarsa, subclassdakı metodun return tipi Integer
(yaxud Number
classından törənmiş hər hansı bir başqa class) ola bilər.
Amma bu qayda primitiv tiplər üçün keçərli deyil. Misal üçün, əgər override edən (overriding) metodun return tipi int
olarsa, override edilən (overridden) metodun return tipi də mütləq int
olmalıdır. short
, long
və yaxud Integer
ola bilməz:
class Super { public Number getNumber() { return 5; } public int getInt() { return 6; } } class CovariantReturnTypes extends Super { public Integer getNumber(){ // OK return 7; } public short getInt(){ // DOES NOT COMPILE return 8; } }
Overriding ilə overloading bir-birinə çox bənzəyir, çünki hər ikisində də metod adları eynidir. Amma overriding`dən fərqli olaraq overloading həmişə fərqli metod signature istifadə edir:
class Bird { public void fly() { System.out.println("Bird is flying"); } public int eat(int food) { System.out.println("Bird is eating " + food + " units of food"); return food; } } public class Eagle extends Bird { public int fly(int height) { // Everything is ok, because of overloading System.out.println("Bird is flying at " + height + " meters"); return height; } public void eat(int food) { // DOES NOT COMPILE, because of overriding, return type is different System.out.println("Bird is eating " + food + " units of food"); } }
Əgər imtahanda child və parent classda eyni adlı metod görsəniz, ilk növbədə onun override yaxud overload metod olduğunu təyin etmək, sualı tez tapmaq baxımından daha faydalı olacaqdır.
İndi isə qeyd etdiyimiz 4 qayda ilə bağlı nümunələrə baxaq.
Nümunə 1:
public class Camel { protected String getNumberOfHumps() { return "Undefined"; } } class BactrianCamel extends Camel { private int getNumberOfHumps() { // DOES NOT COMPILE return 2; } }
2-ci və 4-cü qayda pozulur burada. Child classdakı metodun access modifier`i ancaq protected
və public
ola bilər. Geri dönüş tipi isə birində String
`dir, digərindi int
.
Nümunə 2:
class MyException extends Exception {} class Reptile { protected boolean hasLegs() throws MyException { throw new MyException(); } protected double getWeight() throws Exception { return 2; } } class Snake extends Reptile { protected boolean hasLegs() { return false; } protected double getWeight() throws MyException { return 2; } }
Reptile
classındakı hər iki metod Snake
classında override edilib və bütün qaydalara riayət edilib, xüsusilə də 3-cü qaydanın tələbləri pozulmayıb. Əgər nümunə aşağıdakı kimi olsa idi, o zaman qaydalar pozulmuş olardı:
class MyException extends Exception {} class Reptile { protected double getHeight() throws MyException { return 2; } protected int getLength() { return 5; } } class Snake extends Reptile { protected double getHeight() throws Exception { // DOES NOT COMPILE return 2; } protected int getLength() throws MyException { // DOES NOT COMPILE return 5; } }
İndi isə daha qarışıq sual nümunələrinə baxaq. Aşağıda qeyd edəcəyim nümunə Enthuware testləri içərisində ən maraqlı və ən yaxşı suallardan biridir. Buna bənzər bir nümunəni Order of Initialization mövzusunda qeyd etmişəm, bu suala isə bu məqalədə ayrıca baxaq:
class A { A() { print(); } void print(){ System.out.println("A"); } } class B extends A { int i = 4; public static void main(String[] args) { A a = new B(); a.print(); } void print() { System.out.println(i); } }
B
classı run edildikdə output`un nə olacağı soruşulur və cavab variantları belədir:
A) It will print A, 4
B) It will print A, A
C) It will print 0, 4
D) It will print 4, 4
E) None of the above.
Cavab variantları da bir-birinə çox yaxındır, ona görə də sual maksimum həssaslıq və diqqət istəyir. O vaxtkı qeydlərimə baxıram, Standart Test`ləri edərkən özüm də bu sualı səhv cavablandırmışam 🙂 Mənim seçdiyim cavab A variantı idi, mən hesab edirdim ki, A
classının constructoru çağırılarkən icra edilən print()
metodu A
classına məxsus print()
metodu olacaq (hətta indi də suala çox baxdıqda bunu qarışdırıram 😀 ), amma override özəlliyindən dolayı B
classının print()
metodu icra edilir, buna diqqət etmək lazımdır.
Artıq bildik ki, A
classının print()
metodu çağırılmır, bu səbəbdən də A və B variantlarını çıxara bilərik, geriyə qalır 3 variant. Kömək üçün qeyd edim ki, E variantını da çıxara bilərik, cavablar arasında düzgün variant var. Geriyə qalır C və D variantları. Amma sualın ən maraqlı hissəsi hələ qabaqdadır, işi bitmiş hesab etmək olmaz 🙂
Yəqin ki, çoxumuz C və D variantları arasında D-ni seçərdik. Amma D düzgün cavab deyil, düzgün cavab variantı C-dir. Maraqlı bir sual ortaya çıxır. Niyə? 🙂
Sualın bu hissəsi “order of initialization” ilə bağlıdır. Deməli sualda bütün hadisələr aşağıdakı ardıcıllıqla baş verir:
Main
metodda ilk sətirdəA
classının referansı yaradılır və bu zamanB
classının constructoru çağırılır (bu cür yazılış polimorfizm özəlliyindən dolayı düzgündür, növbəti məqalələrdə bu mövzuya toxunacağıq);B
classının super classı varsa, ilk növbədə super classın constructoru çağırılmalıdır və bu nümunədəA
classının constructoru çağırılır;A
classının constructorundaprint()
metodu çağırılır.print()
metoduB
classında override edildiyindən,A
classındakıprint()
metodu deyil,B
classındakıprint()
metodu icra edilir;- Bu addımda
i
dəyişəni çap edilir və işin ən maraqlı hissələrindən biri baş verir.i
dəyişəniB
classının instance dəyişənidir və instance dəyişənlərə dəyərlər super constructor icra edildikdən sonra mənimsədilir. Bu mərhələdə isəprint()
metodu super constructorun gövdəsində çağırılıb və bu səbəbdən super constructorun icrası hələ bitməyib. Məhz buna görəi
dəyişəninə hələ dəyər mənimsədilməyib, ona görə dəi
dəyişəninin default dəyəri (sıfır) çap edilir; - Super constructorun icrası bitir və
i
dəyişəninə4
dəyəri mənimsədilir; Main
metoddaa
referansı üzərindənprint()
metodu çağırılır.a
referansıA
classına aid olsa da “actual object”B
classına məxsusdur. Ona görə də runtime vaxtıB
classındaprint()
metodunun override edilib edilməməsi yoxlanılır.B
classındaprint()
metodu override edilib, bu səbəbdən də həmin metod çağırılır vəi
`nin dəyəri (4
) çap edilir.
Başqa bir nümunəyə baxaq. Tutaq ki, bizim 3 classımız var (A, B, C
) və bu classlar zəncirvari şəkildə bir-birilərindən törəyiblər; C
classı B
classından, B
classı da öz növbəsində A
classından:
class A { public void print() { System.out.println("A"); } } class B extends A { public void print() { System.out.println("B"); } } class C extends B { public void print() { System.out.println("C"); } }
Bu kod nümunəsində siz C
classının daxilində C
classının instansını istifadə etməklə A
classının print()
metoduna heç cürə müraciət edə bilmirsiniz:
class C extends B { public void print() { System.out.println("C"); } public void test(){ A a = new C(); a.print(); // C ((A)this).print(); // C this.print(); // C super.print(); // B super.super.print(); // DOES NOT COMPILE } }
[topics lang=az]