Wednesday 3 October 2018

Polimorfizm (Overloading və Overriding)

OOP-nin əsas prinsiplərindən biri olan polimorfizm metodların və konstruktorların bir sıra fərqli formada yazılmasında və varislikdə (inheritance) özünü göstərir. Polimorfizm 2 cür olur:

-compile time (statik)
-runtime (dinamik)

Compile time, və ya statik polimorfizm Java kodları baytkoda çevrilərkən, kompilyasiya prosesi zamanı baş verir. Ən çox bilinən forması: Overloading (çox yüklənmə)

Overloading həm konstruktorlar, həm də metodlar üçün keçərlidir. Bu zaman konstruktor və ya metodların adları eyni olur, lakin aldığı parametrlərin sayı, tipi və sıralaması fərqli olur. Məsələn,

public class Test
{
   public Test()
   {
      ...
   }
   public Test(String s)
   {
      ...
   }
   public Test(int i)
   {
      ...
   }
   public Test(String s, int i)
   {
      ...
   }
   public Test(int i, String s)
   {
      ...
   }


Kod daxilində situasiyaya uyğun olaraq bu konstruktorlardan istənilən biri çağırıla bilər. Hansı parametr dəsti uyğun gəlsə, o konstruktor işə düşəcək. Mənbə kodun kompilyasiyası zamanı bu, artıq təyin olunmuşdur. Bu cür konstruktor və metodlar Overloaded adlanır.



Runtime, və ya dinamik polimorfizm kompilyasiya olmuş kodun icrası zamanı baş verir. Ən çox bilinən forması: Overriding (üst-üstə minmə).

Overriding varislik əlaqələrində özünü göstərir. Üst sinifdə yazılmış konstruktor və ya metod eyni imza (signature) və dönüş tipi ilə (return type)  alt sinifdə yenidən yazılarsa, bu konstruktor və ya metod Overrided olmuş olur. Bunun mənası odur ki, hər sinifdə konstruktor və ya metodun işləmə prosesi bir-birindən fərqlənə bilər və həmin sinifə uyğun şəkildə yenidən yazılmalıdır. Məsələn,

Üst sinif:

public class Test
{
   public String test(String s)
   {
      ...
   }


Alt sinif:
public class Test1 extends Test
{
   public String test(String s)
   {
      ...
   }

   public static void main(String[] arg)
   {
      Test1 t=new Test1();
      t.metod("Java");
   }
}

Burada Test1 sinfindən olan t obyekti Test1 sinfinin test metodunu çağırır. Əgər bu metod alt sinifdə yenidən yazılmış olmasaydı, bu zaman Test sinfindəki metod icra olunacaqdı. Bu, varislik mövzusunda daha geniş izah olunacaq.

Thursday 9 August 2018

Obyekt Yönümlü Proqramlaşdırmada abstraksiya və kapsulasiya (Abstraction, encapsulation)

Abstraksiya və kapsulasiya OOP-nin bir-birini tamamlayan iki mühüm prinsipidir. Əsas məqsəd isə proqramın pərdə arxasında gedən prosesləri son istifadəçidən gizləmək və obyektin dəyişənlərinə birbaşa müraciətin qarşısını almaqdır (data hiding). Maşın nümunəsinə müraciət etməklə bunu daha açıq izah etmək olar. Maşını istifadə edən şəxs bunun üçün nəzərdə tutulmuş hissələri funksiyasına uyğun şəkildə hərəkət etdirməklə maşını öz istəyinə uyğun yönləndirə bilər. Amma maşının daxilində hansı detalların olmasını və orada gedən prosesləri bilməsi vacib deyil. Bunlar ətrad mühitdən gizlədilərək daha təhlükəsiz bir formada mühafizə edilir.
Eynilə, proqram yazarkən bu prinsiplərə əməl edilməsi proqramın daha güvənli və dayanıqlı olmasını təmin edir. Bu məqsədlə dəyişənlər üçün private  və protected açar sözlərindən istifadə edilməsi geniş qəbul görmüş bir üsuldur. Dəyişənlərin dəyərlərində dəyişiklik lazım gələrsə, "setter" metodu yazılır. Bununla yanaşı dəyişənin dəyərini almaq üçün də "getter" metodu nəzərdə tutula bilər.

Class.java faylı
public class Car
{
    private String name;
    private String model;
    private int year;
    private double engine;

    public void setName(String name)
    {
       this.name=name;
    }

    public String getName()
    {
       return name;
    }

    public void setModel(String model)
    {
       this.model=model;
    }

    public String getModel()
    {
       return model;
    }

    public void setYear(int year)
    {
       this.year=year;
    }

    public int getYear()
    {
       return year;
    }

    public void setEngine(double engine)
    {
       this.engine=engine;
    }

    public double getEngine()
    {
       return engine;
    }
}

Test.java faylı
public class Test
{
   public static void main (String [] arg)
   {
      //Car sinfindən bir nümunə yaradılır
      Car car=new Car();

      //Uyğun dəyişənlərə dəyərlər verilir
      car.setName("Mercedes Benz");
      car.setModel("C class");
      car.setYear(1995);
      car.setEngine(2.3);

      System.out.println("Maşının markası: "+car.getName());
      System.out.println("Maşının modeli: "+car.getModel());
      System.out.println("İstehsal ili: "+car.getYear());
      System.out.println("Mühərrik həcmi: "+car.getEngine());
   }
}

Mahiyyətini daha aydın başa düşmək üçün CarTest siniflərini ayrı ayrılıqda yazdım. Çünki Car sinfi içərisindən onun private olan sahələrinə müraciət etmək olur və belədə abstraksiya-kapsulasiya prinsipinin heç bir mənası qalmır. Daha yüksək səviyyəli proqramlarda da siniflər bu cür ayrı-ayrılıqda yazılır. Yuxarıdakı nümunədə Test sinfi içərisində yaradılmış Car tipindən obyekt onun private sahələrinə birbaşa müraciət edə bilməz. Buna görə setName(), setModel(), setYear(), setEngine() metodları vasitəsilə uyğun dəyişənlərin dəyərini istədiyimiz kimi dəyişə bilərik. Eyni qaydada, getName(), getModel(), getYear(), getEngine() metodları ilə də bu dəyərləri çıxışa vermək imkanımız var.

Obyekt Yönümlü Proqramlaşdırma (Object Oriented Programming-OOP)

Bu prinsip ona əsaslanır ki, burada qarşıya çıxan hər bir məsələyə bir obyekt kimi baxılır. Bunun izahı üçün gətirilmiş bir çox tipik misal var. Firmadakı işçilər, tələbələr, maşınlar, heyvanlar və s. Mənim qarşıma çıxan ilk misal maşınlarla olduğu üçün mən də bunun üzərindən getmək istəyirəm.
Bir obyekt olaraq maşının bir xüsusiyyəti və davranışları var. Markası, modeli, ili, motor həcmi və s. onun xüsusiyyətlərini təşkil edir. Maşının hərəkətə başlaması, sürətini artırması, yavaşlaması, dayanması kimi də davranışları var. Buna proqramlaşdırma nöqteyi nəzərindən baxsaq, bu cür ifadə etmək olar:
Maşın-bir sinifdir.
Xüsusiyyətlərini dəyişənlər vasitəsilə ifadə edə bilərik.
Metodlar vasitəsilə də davranışlarını göstərərik.

public class Car
{
    String name;
    String model;
    int year;
    double engine;

    public void start()
    {
    }

    public void accelerate()
    {
    }

    public void stop()
    {
    }
}

Metodların nə edəcəyi istəyə uyğun formada yazıla bilər. Burada sadəcə ümumi skeleti göstərilib. Bu sinifdən ayrı ayrı nümunə obyektlər yaradıb onlara xas dəyərlər verdikdə hərəsinin öz xüsusiyyətləri olan fərqli, amma eyni sinfə aid obyektlər əldə etmiş olarıq. Metodlar da bu dəyərləri emal edəcək şəkildə yazılarsa, hər obyektin öz davranışı olacaq. Məsələn,

public static void main (String [] arg)
{
   //Car sinfindən bir nümunə yaradılır
   Car car=new Car();

   //Uyğun dəyişənlərə dəyərlər verilir.
   car.name="Mercedes Benz";
   car.model="C class";
   car.year=1995;
   car.engine=2.3;
   car.start();
   car.accelerate();
   car.stop();
}

Eyni qayda ilə bir çox məsələyə bu cəhətdən yanaşıb analogiya yaratmaq mümkündür.
OOP'nin abstraction, encapsulation, polymorphism, inheritance kimi prinsipləri var

Sunday 22 July 2018

Java-da Non-access Modifiers

Java proqramlaşdırmada "Non-access modifiers". Access modifiers barədə yazmışdım. Qısaca xatırladım ki, bunlar sinif, dəyişən və metodların "görünəbilmə zonalarını" təyin edirdi. Non-access modifiers isə əlavə xüsusiyyətlər verir. Aşağıdakılardır:

static
final
abstract
synchronized
volatile

transient

static-bu barədə əvvəlki postlarda yazmışdım. Yenə yada salım, bu söz aid olduğu obyekt, dəyişən və metodları sinif səviyyəsində təyin edir. "static" olan bir elementə birbaşa sinif adı ilə müraciət etmək mümkündür. Məsələn, "Math.PI" Math sinfindəki PI adlı sabit bir dəyişəni göstərir. Bu bildiyimiz "pi" ədədidir. "Math.sqrt(double i)" metodu isə double tipdən daxil edilmiş bir ədədin kvadrat kökünü verir.

final-dəyişən üçün işlədildikə o dəyişənin dəyərinin yalnız bir dəfə təyin edilə bildiyini göstərir. Məsələn,

final int i=5;
i=10; //compile error

"static" və "final" sözlərinin birlikdə işlədilməsi sinif səviyyəsində bir sabit yaradır. Məsələn,

public static final PI=3.1415.....
public static final E=2.7182.....


Metodlar üçün "final" sözü o metodun altsiniflərdə "override" olmasının qarşısının alınması üçün işlədilir. Qısaca deyə bilərəm ki, "overriding" bir metodun altsiniflərdə daxili kodunun yenidən yazılmasıdır.

abstract-mücərrəd siniflər və mücərrəd metodlar yaratmaq üçün istifadə olunur. Mücərrəd metod odur ki, metod yalnız imzadan (signature) ibarət olur, daxili kod yazılmır. Bu metodun olduğu sinif də mücərrəd olur. Bu sinifdən törədilmiş altsinif bu metodu mütləq "override" etməlidir. Məsələn,

public abstract class Test1 //mücərrəd sinfin yaradılması
{
    abstract void print(); //mücərrəd metod, kod bloku və gövdə yoxdur.
}


public class Test2 extends Test1 //Test1 sinfindən Test2 sinfi törədilir
{
    public void print(). //print() metodunun gövdəsi yazılır
    {
       System.out.println("print () method overrode");
    }
}


synchronized-bu ifadə "thread" əməliyyatları ilə bağlıdır. İstənilən proqram iş parçacıqlarından (thread) ibarətdir. Java Multithreading-i dəstəkləyir. Bu isə o deməkdir ki, eyni zamanda bir neçə əməliyyat aparmaq mümkündür (əslində, tam da eyni zamanda demək olmaz, sadəcə biz bu şəkildə qəbul edirik). Əgər normal halda bir proqram öz işini bitirməmiş digər proqram icraya başlamırsa, multithreading-də hər iş parçacığı növbəli şəkildə eyni bir yaddaş ünvanına müraciət edə bilir. Amma bu zaman da bir iş parçacığının öz işini tam yekunlaşdırması tələb oluna bilir. Bu məqsədlə də metodlarda "synchronized" ifadəsi işlədilir.

volatile-yenə "multithreading" əməliyyatının bir elementidir. Bəzən komputer sürət xatirinə bir dəyişənin o ankı həqiqi dəyərini yox, keş yaddaşında saxlanmış dəyirini oxumağa çalışa bilir. Lakin elə bir dəyişən təyin edilmiş ola bilər ki, onun istənilən anda həqiqi dəyəri oxunmalı olsun. Bu zaman həmin dəyişən "volatile" kimi qeyd edilir. Bu, komputerin həmin dəyişənin dəyərini keş yaddaşından oxumasının qarşısını alır və hər zaman aktual dəyərini götürür.

Java-da paket anlayışı (Package)

Java proqramlaşdırmada paket (package) anlayışı.
Paketlər Java-da mühüm rola malik bir strukturdur.
Hər şeydən əvvəl, bir fayl içində birdən çox sinfin yazılmasına məhdudiyyət qoyulmasa da, hər sinif üçün ayrı bir fayl yaradılması məsləhət görülür. Bunun ən birinci səbəbi bir faylda yalnız bir sinfin "public" ola bilməsidir. Bu da "main" metodun olduğu sinif olmalıdır. Fayl içindəki digər siniflər heç bir "access modifier" olmadan yazılmalıdır ki, bu da onlara müraciət imkanını məhdudlaşdırır. Məsələn, Test.java adlı bir faylda bir neçə sinfin yazılması:

public class Test
{
    public static void main(String[] arg)
    {

     ...
    }
}


class Test1
{

 ...
}

class Test2
{

...
}

Əgər Test1 və Test2 siniflərinə istənilən yerdən müraciət etmək istəyiriksə, uyğun adlarda ayrı-ayrı fayllarda(Test1.java, Test2.java) yazmaq lazımdır. Bu halda onları "public" etmək mümkündür.
Bundan sonra paket anlayışı ortaya çıxır. Bir layihəyə aid siniflərin bir qovluqda toplanması daha kompakt bir struktur yaradır. Deyək ki, komputerin müəyyən bir yerində "java" qovluğu yaradıb ona aid layihə fayllarını da ayrı-ayrı qovluqlar altında (project1, project2, project3 və s.) qruplaşdırırıq. Əvvəlcə onu qeyd edim ki, paketləmənin işə yaraması üçün həmin "java" qovluğunun tam yolu Environment Variable kimi CLASSPATH altında sistemə tanıdılmalıdır (Bu da başqa bir mövzudur).
İndi yazılacaq sinfi paketə aid etmək lazımdır. Bu, kodun lap başında qeyd olunur. Məsələn, java>project1 paketinə adi ediləcək sinif belə olacaq.

package java.project1;

public class Test
{
.....
}


Burada altqovluğa keçid nöqtə-"." işarəsilə bildirilir. Java API-sinin də özünə aid hazır paketləri və uyğun paketlərdə sinifləri var. Əgər bu paketlərdən birini daxil etmək istəsək "import" sözündən istifadə olunur və əgər varsa, "package" ifadəsindən sonra yazılır:

package java.project1;
import java.util.*;
import java.project1.*;


Ulduz işarəsi onu bildirir ki, bu paket içərisindəki bütün siniflərə müraciət oluna bilər. Lakin altpaketlərə yox. Məsələn, java.util.concurrent paketindəki siniflər lazım olacaqsa, onu da ayrıca qeyd etmək lazımdır.

import java.util.*;
import java.util.concurrent.*;


Konkret bir sinfi də daxil etmək olar.

import java.util.Arrays;
import java.project1.Test1;


Paket strukturu istənilən qovluqda yaradılmış siniflərin bir birini görməsinə şərait yaradır.
Əgər yazılan sinif üçün heç bir paket bildirimi qeyd olunmayıbsa, o sinif "default package" altında qeyd olunur. Bu isə eyni qovluq altında olan sinifləri göstərir. Yəni heç bir paket bildirimi qeyd olunmayan siniflər ancaq eyni bir qovluq altında bir-birini görə bilər. Bunun üçünsə "import" ifadəsinin işlədilməsinə ehtiyac yoxdur.

Java-da Constructor (qurucular)

Java'da "constructor"lar.
Constructor anlayışı obyekt yönümlü proqramlaşdırmanın əsas elementlərindən biri hesab olunur. Əgər bir Java sinfindən yeni bir obyekt yaratmaq istəsək, "new" açar sözündən istifadə edilir. Bu zaman proqram constructor'u çağırır və yaddaşda yeni bir instance yaradılır:

public class Test
{
    Test t=new Test();
}


Buradakı "Test()" yazılışı constructor'dur. İlk baxışdan metodları xatırladır. Oxşar cəhətləri olduğu kimi fərqli xüsusiyyətləri də var. Oxşar cəhəti, ilk olaraq, yazılış formasıdır. Bundan başqa, constructor'lar da onun daxilində yazılmış kod sətirlərini icra edə bilir. Metodlar kimi overload etmək mümkündür və müxtəlif parametrlər qəbul edə bilir. Lakin fərqləri də var:

- constructor'lar sinif adı ilə eyni ada malik olmalıdır.
- constructor'ların hər hansı bir tipi olmur. Bunu metodların heç bir dəyər tipi olmayan "void" tipi ilə qarışdırmaq olmaz. Ümumiyyətlə, bu cür bir şey təyin edilmir.
- heç bir constructor yazılmadığı halda proqram özü avtomatik constructor yaradır. Buna "default constructor" deyirlər.

Constructor'un yazılmasında əsas məqsəd yaradılan obyektin başlanğıc durumunu təyin etməkdir. Məsələn, əgər tarixlə bağlı bir sinif yazası olsaq (Date), bu sinifdən yaradılan "Date" obyektinin cari tarixi göstərməsini istəyə bilərik.

public class Date
{
    public Date()
    {
       //cari tarixi quraşdıran kod sətirləri
    }
}


Constructor'lara parametrlər göndərərək istənilən başlanğıc dəyəri də vermək olur:

public class Date
{
   int day;
   int month;
   int year;


   public Date()
   {
      day= <cari gün>;
      month=<cari ay>;
      year=<cari il>;
   }


   public Date(int d, int m, int y)
   {
      day=d;
      month=m;
      year=y;
   }


   public static void main (String[] arg)
   {
      Date d1=new Date();
      Date d2=new Date(1, 1, 2019);
   }
}


Burada 2 constructor təyin edilib: no-arg və parameterized.
No-arg constructor-heç bir parametri olmur, lakin proqramda açıq şəkildə təyin edilir. Bunu proqramın avtomatik yaratdığı "default constructor" ilə qarışdırmaq olmaz.
Parameterized constructor- parametr qəbul edir. Əgər bir sinifdə yalnız "parameterized constructor" təyin edilərsə, proqram özü "constructor" yaratmır. Yəni bu halda sinfin "no-arg constructor"u olmur. Aşağıdakı kimi bir obyekt yaratmaq xətaya səbəb olacaq:

public class Date
{
   int day;
   int month;
   int year;


   public Date(int d, int m, int y)
   {
      day=d;
      month=m;
      year=y;
   }


   public static void main (String[] arg)
   {
      Date d1=new Date(); //xəta
   }
}


Yəni əgər parametrli constructor yazılırsa və parametrsiz constructor da nəzərdə tutulubsa, onda mütləq bunu da yazmaq lazımdır.

Java-da metodlar (2)

Java proqramlaşdırmada metodlar mövzusunun davamı. Əgər metod heç bir dəyər qaytarmırsa, metod tipi"void" kimi qeyd olunur. Məsələn,

public class Test
{
    int i;

    public void setValue(int k)
    {
       i=k;
    }
}


Burada metoda metoda parametr kimi daxil edilən "k" dəyişəni "i" dəyişəninə mənimsədilir.
Metodların çağırılması metodun "instance" və ya "class" (static) olmasına görə dəyişir. Instance metod yalnız həmin sinifdən yaradılmış bir nümunə obyekt vasitəsilə çağırılır və həmin obyektə aid bir əməliyyat yerinə yetirilir. Üstdəki nümunənin davamı olaraq:

public static void main (String[] arg)
{
    Test t1=new Test();
    Test t2=new Test();
    t1.setValue(5); //t1 üçün i=5 olur
    t2.setValue(10); // t2 üçün i=10 olur
}


"class" və ya "static" metodlar obyekt yaradılmadan da sinif vasitəsilə çağırıla bilir. Static metodlar static olmayan dəyişənlər üzərində əməliyyat apara bilməz. Buna görə də yalnız parametrlər daxil etməklə əməliyyat aparılır. Məsələn, Math sinfindəki bütün metodlar static'dir. Qüvvətə yüksəltmə metodu:

public static void main (String[] arg)
{
    double i=Math.pow(5, 2);
}


Burada 5-in 2-ci dərəcədən qüvvətinin dəyəri "i" dəyişəninə mənimsədilir. Bu cür metodlar əksər hallarda heç bir obyekt yaradılmasına ehtiyac duyulmayan helper/utility tipli siniflərdə olur.