OCA Java imtahan mövzuları

Java Polymorphism

Understanding Polymorphism

Polimorfizm Java’da OOP-nin əsas prinsiplərindən biri hesab edilir. Polimorfizm nədir deyə sual versək, ona şəkildəyişdirmə, çoxşəkillilik kimi təriflər verə bilərik. Fərqli mənbələrdə polimorfizmə fərqli yanaşmalar, fərqli təriflər verilir. Onları ümumiləşdirsək belə deyə bilərik ki, polimorfizm eyni bir hərəkətin bir neçə fərqli şəkildə icra edilməsidir.

Öz yazdığım real bir kod parçasından bir nümunə göstərməyə çalışacam. Deməli bizim Vacancy adlı bir interface’imiz var və onun getVacancies() adlı bir metodu var. Və bu interface’i implements edən 3 classımız var. Daha yaxşı olar ki, əvvəlcə kod nümunəsini tam şəkildə yazaq və sonra izaha həmin kod nümunəsi üzərindən davam edək:

interface Vacancy {
    public void getVacancies();
}

class JobSearchAz implements Vacancy {
    public void getVacancies() {
        System.out.printf("%-23s %s%n", "jobsearch.az selector:", "table.hotvac tr td.hotv_text");
    }
}

class BossAz implements Vacancy {
    public void getVacancies() {
        System.out.printf("%-23s %s%n", "boss.az selector:", ".results .results-i");
    }
}

class RabotaAz implements Vacancy {
    public void getVacancies() {
        System.out.printf("%-23s %s%n", "rabota.az selector:", "div#vacancy-list ul.visitor-vacancy-list li");
    }
}

public class VacancyFinder {

    public static void main(String[] args) {

     /* 1st - long way
        List<Vacancy> listWebSite = new ArrayList<>();
        listWebSite.add(new JobSearchAz());
        listWebSite.add(new BossAz());
        listWebSite.add(new RabotaAz());
        for (Vacancy v : listWebSite)
            displayVacancySelector(v);
     */

        // 2nd - short way       
        List<Vacancy> listWebSite = Arrays.asList(new JobSearchAz(), new BossAz(), new RabotaAz());
        listWebSite.forEach(v -> displayVacancySelector(v));
    }

    private static void displayVacancySelector(Vacancy v){
        v.getVacancies();
    }
}

Output:

jobsearch.az selector: table.hotvac tr td.hotv_text
boss.az selector:      .results .results-i
rabota.az selector:    div#vacancy-list ul.visitor-vacancy-list li

Koddan da göründüyü kimi bizim 3 vakansiya saytımız (classımız) var:

   JobSearchAz, BossAz, RabotaAz

Bizdə icra edilən ancaq bir hərəkət var – vakansiyanın tapılması. Amma bütün saytlar üçün vakansiyanın tapılması şəkli eyni deyildir. Hər saytın öz fərqli strukturu var və biz hər sayt üçün özünəməxsus selector’lar istifadə etməliyik. Başqa sözlə biz eyni bir davranışı (vakansiyanın tapılması) bir neçə fərqli şəkildə icra edəcəyik (saytların sayına uyğun). Bu məqsədlə də biz hər üç vakansiya classımız (saytımız) üçün Vacancy interface’ni implements edirik və onun getVacancies() metodunu override edirik. Kod nümunəsində diqqət yetirdinizsə displayVacancySelector() metodu Vacancy interface’i tipində parametr qəbul edir, amma biz həmin metoda həmin interface’i implements edən classların instance’lərini göndəririk. Bu polimorfizm özəlliyinin sayəsində mümkündür. Buna “polymorphic parameters” də deyilir, daha aşağıda ayrıca baxacağıq.

Ümumiyyətlə, java obyektinə bir neçə cür müraciət edilə bilər:

  • obyektin öz tipində olan referans ilə;
  • obyektin superclassının referansı ilə;
  • obyektin implements etdiyi interface tipində olan referans ilə və s.

Əgər obyekt implements/extends etdiyi interface/superclassa mənimsədilibsə, cast etmək tələb olunmur.

interface MyInterface {
    public boolean interfaceMethod();
}
 
class Super {
    public String superMethod() {
        return "super";
    }
}
 
public class Sub extends Super implements MyInterface {
 
    public int var = 10;
 
    public boolean interfaceMethod() {
        return false;
    }
 
    public static void main(String args[]) {
        Sub sub = new Sub();
        System.out.println(sub.var);
        MyInterface inf = sub;
        System.out.println(inf.interfaceMethod());
        Super sup = sub;
        System.out.println(sup.superMethod());
    }
}

Output:

10
false
super

Bu nümunədə ancaq bir obyekt yaradılır (Sub) və həmin obyekt öz superclassı – Super və implements etdiyi interface – MyInterface`in referanslarına heç bir problem olmadan mənimsədilir. Bu polimorfizmin qeyd etdiyimiz özəllikləri sayəsində mümkündür.

Artıq həmin superclass və interface`in referansları vasitəsilə bəzi metod və dəyişənləri çağırmaq mümkündür. Bəs hansı metod və dəyişənləri? Ancaq və ancaq həmin referansın aid olduğu obyektin daxilində mövcud olan metod və dəyişənlər çağırıla bilər. Əks halda compile xətası baş verir:

MyInterface inf = sub;
System.out.println(inf.var);   // DOES NOT COMPILE

Super sup = sub;
System.out.println(sup.interfaceMethod());  // DOES NOT COMPILE

Biz bu nümunədə inf referansı vasitəsilə birbaşa olaraq ancaq MyInterface interface`nin daxilində mövcud olan metodları (həmçinin dəyişənləri, amma interface dəyişənləri static olduğundan onları birbaşa interface adı ilə də çağırmaq mümkündür, referansa ehtiyac yoxdur) çağıra bilərik. Ona görə də o var dəyişənini tanımır. Eləcə də sup referansının ancaq Super classı daxilində tanımlanan metod və dəyişənlərə birbaşa icazəsi var, interfaceMethod() metoduna birbaşa müraciət edə bilməz.

Polimorfizmin avantajları:

  • təkrar istifadə edilə bilən (reusable) kod yaratmağa imkan verir;
  • kodu daha dinamik şəklə gətirməyə kömək edir.

 

Casting Objects

Əvvəlki mövzuda biz təkcə Sub classının instansını yaratdıq və həmin obyektə superclass və interface referansı ilə müraciət etdik. İndi biz həmin referans tiplərini yenidən subclassın referansına mənimsədə bilərik. Birinci dəfə biz belə desək daha kiçik tipi daha böyük tipə mənimsədirdik. Bu upcasting adlanır. Upcasting`də aşkar şəkildə cast edilmək tələb olunmur və daha etibarlıdır. Yox əgər biz tərsinə, yəni böyük tipi kiçik tipə mənimsətmək istəyiriksə, bu artıq downcasting adlanır və aşkar şəkildə cast edilməyi tələb edir, əks halda compile xətası verir:

Super sup = sub;
MyInterface inf = sub;
Sub sub2 = sup;    // DOES NOT COMPILE
Sub sub3 = inf;    // DOES NOT COMPILE 

Cast etdikdən sonra isə compile olunur:

Sub sub4 = (Sub) sup;
// Sub sub4_ = (Sub)new Super();  // does compile but throws exception
System.out.println(sub4.var);                // 10
System.out.println(sub4.interfaceMethod());  // false
System.out.println(sub4.superMethod());      // super

Sub sub5 = (Sub) inf;
System.out.println(sub5.var);                // 10
System.out.println(sub5.interfaceMethod());  // false
System.out.println(sub5.superMethod());      // super 

Casting ilə bağlı yadda saxlamalı bəzi əsas qaydalar:

  1. Əgər obyekt subclassdan superclassa cast olunursa, onda aşkar cast tələb olunmur;
  2. Əgər obyekt superclassdan subclassa cast olunursa, onda aşkar cast tələb olunur;
  3. Compiler bir-biri ilə əlaqəsi olmayan tipləri (unrelated types) cast etməyə icazə vermir;
  4. Hətta kod problemsiz compile olunsa belə, runtime vaxtı exception verə bilər (buna nümunə ilə baxdıqda daha aydın olacaq).

1-ci və 2-ci qayda ilə bağlı nümunəyə baxdıq, indi 3-cü qayda ilə bağlı nümunəyə baxaq:

interface Fly {}

class Bird {}

public class Fish {
   public static void main(String[] args) {
      Fish fish = new Fish();
      Bird bird = (Fish)fish;  // line1, DOES NOT COMPILE
      Fly fly = (Fly)fish;     // line2, does compile but throws exception
   }
}

Burada BirdFish classları arasında heç bir əlaqə yoxdur, ona görə də compile xətası verir. Əgər Fish classı Bird classını extends etsə idi, o zaman line1 compile olunardı.

Interface`lər ilə isə vəziyyət nisbətən fərqlidir, Fly interface ilə Fish classı arasında heç bir bağlılıq olmasa da cast etdikdə line2 compile xətası baş vermədən dərlənir, amma runtime vaxtı exception baş verir. Əgər Fish classı Fly interface`ni implements etsə, o zaman exception baş verməyəcək və normal run olacaq (bunu 4-cü qaydaya da aid etmək olar, bu qaydaya bir az sonra aydınlıq gətirəcəyik).

İndi isə 4-cü qayda ilə bağlı nümunəyə baxaq:

class Mercedes {}

public class ML350 extends Mercedes {
    
    public static void main(String[] args) {
        Mercedes mercedes = new Mercedes();
        // mercedes = new ML350();    // exception will not throw if we uncomment this line 
        ML350 ml = (ML350) mercedes;  // does compile but throw ClassCastException at runtime
    }
}

Runtime vaxtı müəyyən edilir ki, ml referansının işarə etdiyi obyekt ML350 classının instansı deyil, ona görə də ClassCastException fırladılır. Bu nümunədə əsas onu yadda saxlamaq lazımdır ki, yaradılan obyektin ML350 classı ilə istənilən halda heç bir əlaqəsi yoxdur.

ClassCastException`ın qarşısını almaq üçün adətən instanceof operatorundan istifadə edilir:

if(mercedes instanceof ML350){
    ML350 ml = (ML350)mercedes;
}

instanceof operatorundan sağ tərəfdə class adı gəlməlidir, sol tərəfində isə yoxlanılacaq referans qeyd edilməlidir.

Coderanch forumunda bu mövzu ilə bağlı bir suala rast gəlmişdim və bu sualın sayəsində sertifikat imtahanına hazırlıq ərəfəsində ən maraqlı faktlardan birini öyrənmiş olmuşdum. Sual belə idi:

class Ink{}

interface Printable {}

class ColorInk extends Ink implements Printable {}

class BlackInk extends Ink{}

class TwistInTaleCasting {

   public static void main(String args[]) {
      Printable printable = null;
      BlackInk blackInk = new BlackInk();
      printable = (Printable)blackInk;  // does compile, but throws ClassCastException
   }
}

Deməli, istifadəçi soruşur ki, BlackInk classı ilə Printable interface’i arasında heç bir iyerarxik əlaqə yoxdur, bəs niyə 15-ci sətir compile xətası vermir?

Casting ilə bağlı qaydalarda biz də qeyd etdik ki, compiler bir-biri ilə əlaqəsi olmayan tipləri (unrelated types) cast etməyə icazə vermir. Bəs bu nümunədə niyə buna icazə verir?

Əvvəla qeyd edək ki, Printable sırf interface olduğu üçün bu baş verir. Əgər biz Printable interface’ni dəyişib class etsək, compiler artıq xəta verəcək. İndi isə keçək izahına.

15-ci sətrin compile xətası verməməsinin yeganə səbəbi odur ki, BlackInk classı final deyil. Əgər BlackInk classını final etsək compile xətası alacağıq. Bunun mətləbə nə dəxli var deyirsinizsə, onda ardına baxaq.

Compiler fikirləşir ki, kimsə BlackInk classından törəmiş hər hansı bir subclass yarada bilər və bu subclass da öz növbəsində Printable interface’ni implements edə bilər:

class DarkBlackInk extends BlackInk implements Printable {}

Və daha sonra biz 14-cü sətri aşağıdakı formada dəyişə bilərik:

BlackInk blackInk = new DarkBlackInk();

Artıq bu dəyişikliklərdən sonra TwistInTaleCasting classı heç bir compile xətası olmadan dərlənəcək və runtime vaxtı heç bir exception baş verməyəcək. Sırf bu ehtimalın mümkün ola biləcəyindən dolayı compiler BlackInk classı ilə Printable interface’i arasında birbaşa iyerarxik əlaqə olmasa da BlackInk classının instance’sini Printable interface’nin referansına mənimsətdikdə cast ilə bağlı compile zamanı xəta mesajı vermir.

Amma biz BlackInk classını final etməklə deyirik ki, bu classdan varis alına bilməz, başqa sözlə subclassı ola bilməz. Artıq bu zaman compiler bizə bildirir ki, “incompatible types: BlackInk cannot be converted to Printable”.

Enthuware test bankından başqa bir maraqlı sual nümunəsinə baxaq:

interface I {}

class A implements I {}

class B extends A {}

class C extends B {}

class D {

    public static void main(String[] args) {
        A a = new A();
        B b = new B();
    }
}

Sualda soruşulur ki, verilmiş kod nümunəsinə əsasən aşağıdakı bəndlərdən hansı compile və runtime vaxtı xəta vermədən çalışacaq?

A)  a = (B)(I) b;
B)  b = (B)(I) a;
C)  a = (I) b;
D)  I i = (C) a;

Əvvəlcə yanlış cavablardan başlayaq.

C – bəndi compile xətası verəcək, çünki “A is-a I” əlaqəsi doğru olsa da, “I is-a A” əlaqəsi doğru deyildir;

B – bəndi compile xətası vermir, çünki sonda biz a referansını B classına cast edirik. Amma runtime vaxtı ClassCastException verəcək, çünki a referansı B classına aid obyektə işarə etmir. Əgər A a = new B(); olarsa, bu bənd doğru olar;

D – bəndi compile xətası vermir, çünki C classı B classından törəyib, B classı da öz növbəsində A classından. Bu səbəbdən də C classı eyni zamanda A classının da subclassı hesab edilir. A classı da I interface’ni implements etdiyindən “C is-a I” əlaqəsi doğrudur. Amma runtime vaxtı bu da ClassCastException verəcək, çünki a referansı C classına aid obyektə işarə etmir. Əgər A a = new C(); olarsa, bu bənd doğru olar;

A – bəndi doğru cavabdır, çünki b referansı I interface’inə və sonra yenidən B classına cast edilə bilər. b referansı B classına işarə etdiyindən və həmçinin “B is-a A” əlaqəsi doğru olduğundan compile və runtime vaxtı heç bir xəta baş vermədən bu kod nümunəsi icra edilir.

  

Virtual Methods

Virtual metodlar o metodlar hesab edilir ki, icra edilən vaxta qədər onun dəqiq implementasiyasını təyin etmək mümkün deyil (A virtual method is a method in which the specific implementation is not determined until runtime).

Faktiki olaraq final, staticprivate olmayan bütün java metodları virtual metodlar hesab olunur, çünki onlardan istəniləni runtime vaxtı override oluna bilər.

class Bird {

    String getName() {
        return "Unknown";
    }

    void displayInformation() {
        System.out.println("The bird name is: " + getName());
    }
}

public class Pigeon extends Bird {

    String getName() {
        return "Pigeon";
    }

    public static void main(String[] args) {
        Bird bird1 = new Bird();
        bird1.displayInformation();    // The bird name is: Unknown

        Bird bird2 = new Pigeon();
        bird2.displayInformation();    // The bird name is: Pigeon

        Pigeon pigeon1 = (Pigeon) bird2;  // bird2-ni bird1 ilə əvəz etdikdə exception verir
        pigeon1.displayInformation();     // The bird name is: Pigeon

        Pigeon pigeon2 = (Pigeon) new Bird();  // throws ClassCastException at runtime
        pigeon2.displayInformation();
    }
}

Burada əsas diqqət ediləsi məqam odur ki, bird2.displayInformation() metodunu çağırdıqda, bu metodun daxilində çağırılan getName() metodu Pigeon classında olan eyniadlı metodla yerini dəyişir. Başqa sözlə desək, parent  class olan Bird classının da getName() metodu var və compile time vaxtı Bird classı Pigeon classındakı getName() metodu ilə bağlı məlumatlı olmur. Ancaq runtime time vaxtı artıq həmin metodun override olunmuş versiyası çağırılır. Və bu da polimorfizmin təbiəti ilə əlaqəli bir nüansdır.

Aşağıdakı nümunədə bu qeyd etdiklərimiz əyani olaraq bir daha göstərilir*: java-polymorphism

 

 

Polymorphic Parameters

Polimorfizmin ən faydalı xüsusiyyətlərindən biri subclassın və ya interface`in instansının metoda parametr olaraq göndərilə bilinməsidir. Tutaq ki, bir metodumuz var və parametr olaraq interface instansı qəbul edir. Bu qaydaya görə həmin interface`i implements edən istənilən classın obyektini bu metoda parametr olaraq göndərə bilərik. Çünki subtipdən supertipə cast edəndə aşkar cast tələb olunmur. Bu özəlliyi metodun polimorfik parametrləri kimi də adlandırmaq olar.

interface ProcessingService {
    public void service();
}

class AzeriCard implements ProcessingService {
    public void service() {
        System.out.println("AzeriCard is servicing..");
    }
}

class MilliKart implements ProcessingService {
    public void service() {
        System.out.println("MilliKart is servicing..");
    }
}

class KapitalBank implements ProcessingService {
    public void service() {
        System.out.println("KapitalBank is servicing..");
    }
}

class CardProcessingCentre {
    
    public static void service(ProcessingService processing) {
        processing.service();
    }
    
    public static void main(String[] args) {
        service(new AzeriCard());
        service(new MilliKart());
        service(new KapitalBank());
    }
}

Output:

AzeriCard is servicing..
MilliKart is servicing..
KapitalBank is servicing..

 

[topics lang=az]

 

* “OCA Java SE 7 Programmer I Certification Guide”, by Mala Gupta

 

About the author

Mushfiq Mammadov

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.

 

This site uses Akismet to reduce spam. Learn how your comment data is processed.