Programovanie (1) v C/C++
1-INF-127, ZS 2024/25

Úvod · Pravidlá · Prednášky · Softvér · Testovač
· Kontaktujte nás pomocou e-mailovej adresy E-prg.png (bude odpovedať ten z nás, kto má príslušnú otázku na starosti alebo kto má práve čas).
· Prosíme študentov, aby si pravidelne čítali e-maily na @uniba.sk adrese alebo aby si tieto emaily preposielali na adresu, ktorú pravidelne čítajú.


Letný semester, prednáška č. 3: Rozdiel medzi revíziami

Z Programovanie
Skočit na navigaci Skočit na vyhledávání
 
(86 medziľahlých úprav od 2 ďalších používateľov nie je zobrazených)
Riadok 1: Riadok 1:
 
== Oznamy ==
 
== Oznamy ==
  
* Dnes po prednáške bude zverejnené zadanie prvej domácej úlohy, ktorej riešenia bude potrebné odovzdať najneskôr ''do pondelka 15. marca, 9:00'' (čiže do začiatku piatej prednášky).
+
* Prvú bonusovú domácu úlohu je v prípade záujmu potrebné odovzdať najneskôr do začiatku zajtrajších cvičení.
 +
* Počas budúcotýždňových cvičení – čiže ''v utorok 12. marca od 9:50'' – bude prebiehať prvý test zameraný na látku ''z prvých troch týždňov semestra''. Body z testu bude možné získať iba v prípade prítomnosti na cvičeniach v miestnosti I-H6.
  
 
==Opakovanie: triedy a objekty==
 
==Opakovanie: triedy a objekty==
 
* ''Objekt'' je predovšetkým súborom rôznych dát a metód na manipuláciu s nimi. Na objekty sa odkazuje pomocou ich identifikátorov, ktoré sú ''referenciami'' na ich „pamäťové adresy”.
 
* ''Objekt'' je predovšetkým súborom rôznych dát a metód na manipuláciu s nimi. Na objekty sa odkazuje pomocou ich identifikátorov, ktoré sú ''referenciami'' na ich „pamäťové adresy”.
 
* Každý objekt je ''inštanciou'' nejakej ''triedy'' (angl. ''class''). Triedu možno chápať ako „vzor”, podľa ktorého sa vytvárajú objekty. Trieda tiež reprezentuje typ jej objektov.
 
* Každý objekt je ''inštanciou'' nejakej ''triedy'' (angl. ''class''). Triedu možno chápať ako „vzor”, podľa ktorého sa vytvárajú objekty. Trieda tiež reprezentuje typ jej objektov.
* Trieda sa teda podobá na <tt>struct</tt> z jazykov C a C++ v tom, že môže obsahovať niekoľko hodnôt rôznych typov. Ide však o omnoho bohatší koncept &ndash; môže obsahovať metódy (funkcie) na prácu s dátami uloženými v inštancii danej triedy, umožňuje nastaviť viditeľnosť jednotlivých premenných a metód pomocou ''modifikátorov'', atď.  
+
* Trieda sa teda podobá na <tt>struct</tt> z jazykov C a C++ v tom, že môže združovať niekoľko hodnôt rôznych typov. Ide však o omnoho bohatší koncept &ndash; môže obsahovať metódy (funkcie) na prácu s dátami uloženými v inštancii danej triedy, umožňuje nastaviť viditeľnosť jednotlivých premenných a metód pomocou ''modifikátorov'', atď.  
* ''Konštruktory'' sú špeciálne metódy triedy slúžiace na vytvorenie objektu (inštancie triedy).  
+
* ''Konštruktory'' sú špeciálne kusy kódu slúžiace na vytvorenie objektu (inštancie triedy).  
* Základným princípom objektovo orientovaného programovania je ''zapuzdrenie'' (angl. ''encapsulation''): spojenie dát a súvisiaceho kódu do koherentného logického celku.  
+
* Dôležitým metodickým princípom objektovo orientovaného programovania je ''zapuzdrenie'' (angl. ''encapsulation''): spojenie dát a súvisiaceho kódu do koherentného logického celku.  
 
** Trieda väčšinou navonok ukazuje iba vhodne zvolenú časť metód.  
 
** Trieda väčšinou navonok ukazuje iba vhodne zvolenú časť metód.  
** Premenné a pomocné metódy sú skryté &ndash; je ich tak možné meniť bezo zmeny kódu využívajúceho triedu.
+
** Premenné a pomocné metódy sú skryté &ndash; prípadné zmeny vnútornej implementácie triedy sa tak nemusia prejaviť v kóde pracujúcom s touto triedu.
 
 
==Konvencie pomenúvania identifikátorov==
 
 
 
V jazyku Java existujú konvencie ohľadom odporúčaných mien tried, premenných, metód, atď. Najdôležitejšie z nich sú nasledujúce dve:
 
 
 
* Mená tried by mali začínať veľkým písmenom (napr. <tt>String</tt>). Pri viacslovných názvoch sa veľkým písmenom začína každé zo slov (napr. <tt>ArrayList</tt>).
 
* Mená metód a premenných by naopak mali začínať malým písmenom (napr. <tt>print</tt>). Pri viacslovných názvoch sa prvé slovo začína malým písmenom a zvyšné slová veľkým písmenom (napr. <tt>toString</tt>). Výnimkou sú samozrejme konštruktory, ktoré musia mať rovnaký názov, ako ich trieda.
 
 
 
Štandardné knižnice jazyka Java tieto (a mnohé ďalšie) konvencie rešpektujú. Dodržiavanie aspoň základných konvencií voľby pomenovaní je silno odporúčané, nakoľko značne uľahčuje čitateľnosť kódu.
 
  
 
== Dedenie ==
 
== Dedenie ==
 
Trieda môže byť podtriedou inej triedy. Napríklad trieda <tt>Pes</tt> môže byť podtriedou všeobecnejšej triedy <tt>Zviera</tt>: každý objekt, ktorý je inštanciou triedy <tt>Pes</tt> je potom súčasne aj inštanciou triedy <tt>Zviera</tt>.  
 
Trieda môže byť podtriedou inej triedy. Napríklad trieda <tt>Pes</tt> môže byť podtriedou všeobecnejšej triedy <tt>Zviera</tt>: každý objekt, ktorý je inštanciou triedy <tt>Pes</tt> je potom súčasne aj inštanciou triedy <tt>Zviera</tt>.  
Tento vzťah medzi triedami vyjadrujeme kľúčovým slovom <tt>extends</tt> v definícii triedy: píšeme teda napríklad
+
Tento vzťah medzi triedami vyjadrujeme kľúčovým slovom <tt>extends</tt> v definícii triedy.
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
 
class Pes extends Zviera {  
 
class Pes extends Zviera {  
     ...
+
     // ...
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
Hovoríme tiež, že trieda <tt>Pes</tt> dedí od triedy <tt>Zviera</tt>.
+
Hovoríme tiež, že trieda <tt>Pes</tt> ''dedí'' od triedy <tt>Zviera</tt>, alebo že trieda <tt>Pes</tt> triedu <tt>Zviera</tt> ''rozširuje''. V prípade vhodne zvolených prístupových modifikátorov (detaily neskôr) totiž inštancia triedy <tt>Pes</tt> zdedí metódy a premenné (nie konštruktory) definované v triede <tt>Zviera</tt> a tie sa potom správajú tak, ako keby boli priamo definované aj v triede <tt>Pes</tt>.
  
Dedenie umožňuje vyhnúť sa nutnosti písať podobný kód viackrát. Namiesto niekoľkých podobných tried s podobnými metódami možno vytvoriť ich nadtriedu a spoločné časti kódu presunúť tam.
+
Dedenie umožňuje vyhnúť sa nutnosti písať podobný kód viackrát. Namiesto implementácie podobných metód v niekoľkých triedach možno vytvoriť nadtriedu týchto tried a spoločné časti kódu presunúť tam.
  
 
===Príklad===
 
===Príklad===
Uvažujme triedy reprezentujúce rôzne geometrické útvary, ktoré môžeme posúvať v rovine. Takto by mohli vyzerať časti tried bez dedenia:
+
Uvažujme triedy reprezentujúce rôzne geometrické útvary v rovine a poskytujúce metódu <tt>move</tt> realizujúcu posunutie.
  
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
class Rectangle {
+
public class Rectangle {
     int x, y;             // suradnice laveho horneho rohu
+
     private double x, y;           // Suradnice laveho horneho rohu
     int width, height;   // vyska a sirka
+
     private double width, height; // Vyska a sirka obdlznika
      
+
 
     public void move(int deltaX, int deltaY) {
+
    public double getX() {
 +
        return x;
 +
    }
 +
 
 +
    public void setX(double x) {
 +
        this.x = x;
 +
    }
 +
 
 +
    public double getY() {
 +
        return y;
 +
    }
 +
 
 +
    public void setY(double y) {
 +
        this.y = y;
 +
    }
 +
 
 +
    public double getWidth() {
 +
        return width;
 +
    }
 +
 
 +
    public void setWidth(double width) {
 +
        this.width = width;
 +
    }
 +
 
 +
     public double getHeight() {
 +
        return height;
 +
    }
 +
 
 +
    public void setHeight(double height) {
 +
        this.height = height;
 +
    }
 +
 
 +
     public void move(double deltaX, double deltaY) {
 
         x += deltaX;
 
         x += deltaX;
 
         y += deltaY;
 
         y += deltaY;
 
     }
 
     }
   
 
    // sem mozu prist dalsie metody pre obdlznik
 
 
}
 
}
 +
</syntaxhighlight>
 +
 +
<syntaxhighlight lang="java">
 +
public class Circle {
 +
    private double x, y;    // Suradnice stredu
 +
    private double radius;  // Polomer kruznice
 +
 +
    public double getX() {
 +
        return x;
 +
    }
 +
 +
    public void setX(double x) {
 +
        this.x = x;
 +
    }
 +
 +
    public double getY() {
 +
        return y;
 +
    }
 +
 +
    public void setY(double y) {
 +
        this.y = y;
 +
    }
 +
 +
    public double getRadius() {
 +
        return radius;
 +
    }
 +
 +
    public void setRadius(double radius) {
 +
        this.radius = radius;
 +
    }
  
class Circle {
+
     public void move(double deltaX, double deltaY) {
    int x, y;            // suradnice stredu
 
    int radius;          // polomer
 
   
 
     public void move(int deltaX, int deltaY) {
 
 
         x += deltaX;
 
         x += deltaX;
 
         y += deltaY;
 
         y += deltaY;
 
     }
 
     }
   
 
    // sem mozu prist dalsie metody pre kruh
 
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Teraz to isté s dedením &ndash; spoločné premenné a metódy presunieme do spoločnej nadtriedy <tt>Shape</tt>:
+
Vidíme, že obidve triedy obsahujú pomerne veľa spoločného kódu, ktorý nie je nijak špecifický pre obdĺžniky alebo kruhy, ale naopak môže byť v nezmenenej podobe použitý aj pri ďalších rovinných geometrických útvaroch. Využijeme teda mechanizmus dedenia &ndash; spoločné premenné a metódy tried <tt>Rectangle</tt> a <tt>Circle</tt> presunieme do ich spoločnej nadtriedy <tt>Shape</tt>.
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
class Shape {
+
public class Shape {
     int x, y;             // suradnice vyznacneho bodu utvaru (roh, stred, ...)
+
     private double x, y; // Suradnice nejakeho vyznacneho bodu geometrickeho utvaru
      
+
 
     public void move(int deltaX, int deltaY) {
+
    public double getX() {
 +
        return x;
 +
    }
 +
 
 +
    public void setX(double x) {
 +
        this.x = x;
 +
    }
 +
 
 +
    public double getY() {
 +
        return y;
 +
    }
 +
 
 +
    public void setY(double y) {
 +
        this.y = y;
 +
     }
 +
 
 +
     public void move(double deltaX, double deltaY) {
 
         x += deltaX;
 
         x += deltaX;
 
         y += deltaY;
 
         y += deltaY;
 
     }
 
     }
 
}
 
}
 +
</syntaxhighlight>
 +
 +
<syntaxhighlight lang="java">
 +
public class Rectangle extends Shape {
 +
    private double width, height;
 +
 +
    public double getWidth() {
 +
        return width;
 +
    }
 +
 +
    public void setWidth(double width) {
 +
        this.width = width;
 +
    }
 +
 +
    public double getHeight() {
 +
        return height;
 +
    }
 +
 +
    public void setHeight(double height) {
 +
        this.height = height;
 +
    }
  
class Rectangle extends Shape {
+
     // Pripadne dalsie metody pre obdlznik
     int width, height;    // vyska a sirka
 
   
 
    // sem mozu prist metody pre obdlznik
 
 
}
 
}
 +
</syntaxhighlight>
 +
 +
<syntaxhighlight lang="java">
 +
public class Circle extends Shape {
 +
    private double radius;
 +
 +
    public double getRadius() {
 +
        return radius;
 +
    }
  
class Circle extends Shape {
+
    public void setRadius(double radius) {
    int radius;         // polomer
+
        this.radius = radius;
      
+
     }
     // sem mozu prist metody pre kruh
+
 
 +
     // Pripadne dalsie metody pre kruznicu
 
}
 
}
 +
 
</syntaxhighlight>
 
</syntaxhighlight>
  
V rámci triedy možno používať aj premenné a metódy definované v nadtriede, ako keby boli jej vlastné. Výnimkou sú premenné a metódy s modifikátorom <tt>private</tt> a v prípade, že trieda a jej nadtrieda nepatria do rovnakého balíčka, aj premenné a metódy bez modifikátora (o tom neskôr). Napríklad v metódach triedy <tt>Circle</tt> tak môžeme používať premenné <tt>x</tt>, <tt>y</tt>, ako aj metódu <tt>move</tt>.
+
V rámci triedy možno používať aj verejné metódy a premenné nadtriedy, ako keby boli jej vlastné &ndash; a často aj metódy a premenné s inými modifikátormi rôznymi od <tt>private</tt> (o tom neskôr). V metódach a konštruktoroch tried <tt>Rectangle</tt> a <tt>Circle</tt> tak napríklad môžeme používať metódy <tt>getX</tt>, <tt>setX</tt>, <tt>getY</tt>, <tt>setY</tt> a <tt>move</tt>.
  
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
class Rectangle extends Shape {
+
public class Rectangle extends Shape {
     int width, height;    // vyska a sirka
+
     // ...
 
      
 
      
     public Rectangle(int x, int y, int height, int width) {
+
     public Rectangle(double x, double y, double width, double height) {
         this.x = x;
+
         setX(x);
         this.y = y;
+
         setY(y);
         this.height = height;
+
         setWidth(width);
         this.width = width;
+
         setHeight(height);
 
     }
 
     }
 +
 +
    // ...
 
}
 
}
 +
</syntaxhighlight>
  
class Circle extends Shape {
+
<syntaxhighlight lang="java">
     int radius;          // polomer
+
public class Circle extends Shape {
 +
     // ...
 
      
 
      
     public Circle(int x, int y, int radius) {
+
     public Circle(double x, double y, double radius) {
         this.x = x;
+
         setX(x);
         this.y = y;
+
         setY(y);
         this.radius = radius;
+
         setRadius(radius);
 
     }
 
     }
 
      
 
      
     public void print() {
+
    @Override // Vyznam tejto znacky si vysvetlime o chvilu
         System.out.println("Stred: (" + x + "," + y + "). Polomer: " + radius + ".");
+
     public String toString() {
 +
         return "Stred: [" + getX() + "," + getY() + "]; Polomer: " + getRadius() + ".";
 
     }
 
     }
 +
 +
    // ...
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
* Ak máme objekt deklarovaný ako <tt>Circle c</tt>, môžeme napríklad zavolať metódy <tt>c.move(1,1)</tt> alebo <tt>c.print()</tt>, prípadne použiť premenné <tt>c.x</tt>, <tt>c.y</tt>, <tt>c.radius</tt> (hoci v praxi je väčšinou žiadúce premenné v triede skryť modifikátorom <tt>private</tt>).
+
* Inštanciu <tt>c</tt> triedy <tt>Circle</tt> teraz môžeme nielen vypísať na konzolu cez <tt>System.out.println(c)</tt> (použije sa metóda <tt>toString</tt>), ale môžeme pre ňu zavolať aj ľubovoľnú metódu triedy <tt>Shape</tt>, napríklad <tt>c.move(1, 1)</tt> alebo <tt>c.setX(2)</tt>.
  
 
===Dedenie a typy===
 
===Dedenie a typy===
  
* Premenná typu <tt>Shape</tt> môže obsahovať referenciu na objekt triedy <tt>Shape</tt> alebo jej ľubovoľnej podtriedy:
+
* ''Typom objektu'' je trieda určená konštruktorom, ktorým bol objekt vytvorený &ndash; napríklad volanie konštruktora triedy <tt>Circle</tt> má za následok vytvorenie objektu typu <tt>Circle</tt>.
 +
* Premenná, ktorej typom je trieda <tt>T</tt> však môže obsahovať aj referenciu na objekt, ktorého typom je podtrieda triedy <tt>T</tt>. Napríklad premenná typu <tt>Shape</tt> tak môže obsahovať referenciu na objekt triedy <tt>Shape</tt> alebo jej ľubovoľnej podtriedy. Vždy teda treba rozlišovať medzi ''typom referencie'' (premennej obsahujúcej referenciu na objekt) a samotným ''typom objektu''.
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
Circle c = new Circle(0,0,5);
+
Circle circle = new Circle(0, 0, 5);
Shape s = c;    // toto je korektne priradenie
+
Shape shape = circle;    // toto je korektne priradenie
// c = s;        // toto neskompiluje, kedze s nemusi byt kruh
+
// circle = shape;        // toto neskompiluje, kedze shape nemusi byt kruznica
c = (Circle) s;  // po pretypovani to uz skompilovat pojde; program ale moze padnut, ak s nie je instanciou Circle alebo null
+
circle = (Circle) shape;  // po pretypovani to uz skompilovat pojde; ak shape nie je instanciou Circle alebo null, vyhodi sa vynimka
 +
</syntaxhighlight>
  
// Istejsi pristup je teda najprv overit, ci je s skutocne instanciou triedy Circle:
+
* Istejší prístup je pri priraďovaní premennej typu <tt>Shape</tt> do premennej typu <tt>Circle</tt> najprv overiť, či premenná typu <tt>Shape</tt> obsahuje referenciu na inštanciu triedy <tt>Circle</tt>. Na to slúži operátor <tt>instanceof</tt>. Platí pritom, že ak je objekt inštanciou nejakej triedy, je súčasne aj inštanciou ľubovoľnej jej nadtriedy (samotný typ objektu je však daný iba najnižšou triedou v tomto usporiadaní). Napríklad podmienka <tt>shape instanceof Shape</tt> je splnená kedykoľvek je splnená podmienka <tt>shape instanceof Circle</tt>. Pre ľubovoľnú triedu <tt>Trieda</tt> má výraz <tt>null instanceof Trieda</tt> vždy hodnotu <tt>false</tt> (a rovnako pre premennú obsahujúcu <tt>null</tt>).
if (s instanceof Circle) {   
+
<syntaxhighlight lang="java">
     c = (Circle) s;
+
if (shape instanceof Circle) {   
 +
     circle = (Circle) shape;
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
* Vďaka tejto črte možno rôzne typy útvarov spracúvať tým istým kódom. Napríklad nasledujúca funkcia dostane pole útvarov (môžu v ňom byť útvary rôznych typov) a posunie každý z nich o daný vektor (<tt>deltaX</tt>, <tt>deltaY</tt>):
+
* Keďže teda môžeme inštancie tried <tt>Rectangle</tt> alebo <tt>Circle</tt> považovať aj za inštancie ich spoločnej nadtriedy <tt>Shape</tt>, môžeme rôzne typy útvarov spracúvať tým istým kódom. Napríklad nasledujúca metóda dostane pole útvarov a posunie každý z nich o daný vektor (<tt>deltaX</tt>, <tt>deltaY</tt>).
  
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
static void moveAll(Shape[] shapes, int deltaX, int deltaY) {
+
public static void moveAll(Shape[] shapes, double deltaX, double deltaY) {
     for (Shape x : shapes) {
+
     for (Shape shape : shapes) {
         x.move(deltaX, deltaY);
+
         shape.move(deltaX, deltaY);
 
     }
 
     }
 
}
 
}
Riadok 145: Riadok 246:
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
 
Shape[] shapes = new Shape[2];
 
Shape[] shapes = new Shape[2];
shapes[0] = new Rectangle(0,0,1,2);
+
shapes[0] = new Rectangle(0, 0, 1, 2);
shapes[1] = new Circle(0,0,1);
+
shapes[1] = new Circle(0, 0, 1);
  
 
moveAll(shapes, 2, 2);
 
moveAll(shapes, 2, 2);
Riadok 153: Riadok 254:
 
     if (x instanceof Circle) {
 
     if (x instanceof Circle) {
 
         System.out.println("Je to kruh.");
 
         System.out.println("Je to kruh.");
         Circle c = (Circle) x;
+
         Circle c = (Circle) x; // O chvilu uvidime, ze toto pretypovanie kvoli nasledujucemu riadku nie je nutne
         c.print();
+
         System.out.println(c);
 
     }
 
     }
 
     if (x instanceof Shape) {
 
     if (x instanceof Shape) {
Riadok 167: Riadok 268:
 
* Pri dedení si väčšinou každá trieda inicializuje &bdquo;svoje&rdquo; premenné.
 
* Pri dedení si väčšinou každá trieda inicializuje &bdquo;svoje&rdquo; premenné.
 
* Napríklad krajší spôsob realizácie konštruktorov pre geometrické útvary je nasledovný: <tt>Shape</tt> inicializuje <tt>x</tt> a <tt>y</tt>, pričom napríklad <tt>Circle</tt> nechá inicializáciu <tt>x</tt> a <tt>y</tt> na <tt>Shape</tt> a inicializuje už len <tt>radius</tt>.
 
* Napríklad krajší spôsob realizácie konštruktorov pre geometrické útvary je nasledovný: <tt>Shape</tt> inicializuje <tt>x</tt> a <tt>y</tt>, pričom napríklad <tt>Circle</tt> nechá inicializáciu <tt>x</tt> a <tt>y</tt> na <tt>Shape</tt> a inicializuje už len <tt>radius</tt>.
* Prvý príkaz konštruktora môže pozostávať z volania konštruktora predka pomocou kľúčového slova <tt>super</tt> (z angl. ''superclass'', t.j. nadtrieda).
+
* Prvý príkaz konštruktora môže pozostávať z volania konštruktora predka pomocou kľúčového slova <tt>super</tt> (z angl. ''superclass'', t. j. nadtrieda).
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
class Shape {
+
public class Shape {
     int x, y;           
+
     // ...   
 
+
 
     public Shape(int x, int y) {
+
     public Shape(double x, double y) {
         this.x = x;
+
         this.x = x; // alebo setX(x);
         this.y = y;
+
         this.y = y; // alebo setY(y);
 
     }
 
     }
 
      
 
      
     // zvysok triedy Shape
+
     // ...
 
}
 
}
 +
</syntaxhighlight>
  
class Rectangle extends Shape {
+
<syntaxhighlight lang="java">
     int width, height;   
+
public class Rectangle extends Shape {
 +
     // ...
 
      
 
      
     public Rectangle(int x, int y, int height, int width) {
+
     public Rectangle(double x, double y, double width, double height) {
         super(x,y);
+
         super(x, y);
         this.height = height;
+
         this.width = width;  // alebo setWidth(width);
         this.width = width;
+
         this.height = height; // alebo setHeight(height);
 
     }
 
     }
 
      
 
      
     // zvysok triedy Rectangle
+
     // ...
 
}
 
}
 +
</syntaxhighlight>
  
class Circle extends Shape {
+
<syntaxhighlight lang="java">
     int radius;          
+
public class Circle extends Shape {
 +
     // ...          
 
      
 
      
     public Circle(int x, int y, int radius) {
+
     public Circle(double x, double y, double radius) {
         super(x,y);
+
         super(x, y);
         this.radius = radius;
+
         this.radius = radius; // alebo setRadius(radius);
 
     }
 
     }
 
      
 
      
     // zvysok triedy Circle
+
     // ...
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
* Ak nezavoláme konštruktor predka ručne, automaticky sa zavolá konštruktor bez parametrov, t.j. <tt>super()</tt>. To môže pri kompilovaní vyústiť v chybu v prípade, keď nadtrieda nemá definovaný konštruktor bez parametrov (či už explicitne jeho implementáciou, alebo implicitne tým, že sa neuvedie implementácia ''žiadneho'' konštruktora nadtriedy). Napríklad v horeuvedenom príklade je teda volanie konštruktora nadtriedy nutnou podmienkou úspešnej kompilácie.  
+
* Ak nezavoláme konštruktor nadtriedy ručne, automaticky sa zavolá konštruktor bez parametrov, t. j. <tt>super()</tt>. To môže pri kompilovaní vyústiť v chybu v prípade, keď nadtrieda nemá definovaný konštruktor bez parametrov (či už explicitne jeho implementáciou, alebo implicitne tým, že sa neuvedie implementácia ''žiadneho'' konštruktora nadtriedy). Napríklad v horeuvedenom príklade je teda volanie konštruktora nadtriedy nutnou podmienkou úspešného skompilovania programu.  
* Výnimkou je prípad, keď sa na prvom riadku volá iný konštruktor tej istej triedy pomocou <tt>this(...)</tt> &ndash; vtedy sa volanie konštruktora nadtriedy nechá na práve zavolaný konštruktor.
+
* Výnimkou je prípad, keď sa v rámci prvého príkazu volá iný konštruktor tej istej triedy pomocou <tt>this(...)</tt> &ndash; vtedy sa volanie konštruktora nadtriedy nechá na práve zavolaný konštruktor.
  
 
== Prekrývanie metód a polymorfizmus ==
 
== Prekrývanie metód a polymorfizmus ==
Riadok 210: Riadok 315:
 
Podtrieda môže ''prekryť'' (angl. ''override'') niektoré zdedené metódy, aby sa chovali inak ako v predkovi.
 
Podtrieda môže ''prekryť'' (angl. ''override'') niektoré zdedené metódy, aby sa chovali inak ako v predkovi.
  
Napríklad môžeme mať útvar <tt>Segment</tt> (úsečka), ktorý je zadaný dvoma koncovými bodmi a v metóde <tt>move</tt> treba posunúť oba. Metódu z predka môžeme zavolať pomocou <tt>super.move</tt>, ale nemusí to byť na prvom riadku a nemusí byť použitá vôbec:
+
Uvažujme napríklad útvar <tt>Segment</tt> (úsečka), ktorý je zadaný dvoma koncovými bodmi a v metóde <tt>move</tt> treba posunúť oba. V triede <tt>Segment</tt> je teda potrebné metódu <tt>move</tt> prekryť. Metódu <tt>move</tt> z nadtriedy <tt>Shape</tt> pritom možno zavolať ako <tt>super.move</tt>, ale nemusí to byť v rámci prvého príkazu prekrývajúcej metódy <tt>move</tt> a metóda nadtriedy sa tam prípadne ani nemusí zavolať vôbec (volanie metódy <tt>super.move</tt> možno použiť aj v iných metódach a konštruktoroch triedy <tt>Segment</tt>).
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
class Segment extends Shape {
+
public class Segment extends Shape {
     int x2, y2;
+
     private double x2, y2;
      
+
 
     public Segment(int x, int y, int x2, int y2) {
+
     // ...
         super(x,y);
+
 
 +
     public Segment(double x, double y, double x2, double y2) {
 +
         super(x, y);
 
         this.x2 = x2;
 
         this.x2 = x2;
 
         this.y2 = y2;
 
         this.y2 = y2;
Riadok 222: Riadok 329:
  
 
     @Override
 
     @Override
     public void move(int deltaX, int deltaY) {
+
     public void move(double deltaX, double deltaY) {
         super.move(deltaX, deltaY); // volanie metody v predkovi
+
         super.move(deltaX, deltaY);
 
         x2 += deltaX;
 
         x2 += deltaX;
 
         y2 += deltaY;
 
         y2 += deltaY;
Riadok 229: Riadok 336:
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
Anotácia <tt>@Override</tt> je nepovinná, ale odporúčaná. Ide o informáciu pre kompilátor, ktorou sa vyjadruje snaha o prekrytie zdedenej metódy. Ak sa v predkovi nenachádza metóda s rovnakou hlavičkou, kompilátor vyhlási chybu. Tým sa dá predísť obzvlášť nepríjemným chybám.  
+
O prekrytie metódy z nadtriedy ide samozrejme iba v prípade, že má prekrývajúca metóda rovnaký názov a rovnakú postupnosť typov parametrov (t. j. rovnakú signatúru) ako metóda nadtriedy. V prípade rovnakého názvu metódy, ale rozdielnych typov parametrov ide len o preťaženie metódy, ako ho poznáme z minulej prednášky.
 +
 
 +
Návratový typ prekrývajúcej metódy sa musí buď zhodovať s návratovým typom prekrývanej metódy, alebo musí byť jeho &bdquo;špecializáciou&rdquo; (napr. môže ísť o podtriedu triedy, ktorá slúži ako návratový typ prekrývanej metódy). Modifikátor prístupu prekrývajúcej metódy musí byť nastavený tak, aby bola táto metóda prístupná kedykoľvek je prístupná prekrývaná metóda (ak je teda napr. prekrývaná metóda verejná, musí byť verejná aj prekrývajúca metóda). Pokiaľ tieto vlastnosti prekrývajúcej metódy nie sú splnené, program neskompiluje.
 +
 
 +
Anotácia <tt>@Override</tt> je pri prekrývaní metód nepovinná, ale odporúčaná. Ide o informáciu pre kompilátor, ktorou sa vyjadruje snaha o prekrytie zdedenej metódy. Ak sa v predkovi nenachádza metóda s rovnakou signatúrou, kompilátor vyhlási chybu. Tým sa dá predísť obzvlášť nepríjemným chybám, pri ktorých napríklad namiesto prekrytia metódy túto metódu neúmyselne preťažíme alebo napíšeme metódu s úplne iným názvom (napr. <tt>hashcode</tt> namiesto <tt>hashCode</tt>).  
  
 
S prekrývaním metód súvisí ''polymorfizmus'', pod ktorým sa v programovaní (hlavne pri OOP) rozumie schopnosť metód chovať sa rôzne:  
 
S prekrývaním metód súvisí ''polymorfizmus'', pod ktorým sa v programovaní (hlavne pri OOP) rozumie schopnosť metód chovať sa rôzne:  
Riadok 235: Riadok 346:
 
* Pri dedení sa navyše môže metóda chovať rôzne v závislosti od triedy, ku ktorej táto metóda patrí.
 
* Pri dedení sa navyše môže metóda chovať rôzne v závislosti od triedy, ku ktorej táto metóda patrí.
 
* To, ktorá verzia metódy sa zavolá, záleží od toho, akého typu je objekt, nie akého typu je referencia naň.
 
* To, ktorá verzia metódy sa zavolá, záleží od toho, akého typu je objekt, nie akého typu je referencia naň.
 +
* Takto to ale funguje iba pri nestatických metódach (keďže statické metódy príslušia samotným triedam, rozdiel medzi typom referencie a typom inštancie tam nemožno využiť). Pri statických metódach preto nehovoríme o ich prekrývaní, ale o ich ''skrývaní''; nemožno vtedy ani použiť anotáciu <tt>@Override</tt>.
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
   Shape s = new Segment(0,0,1,-5);
+
   Shape s = new Segment(0, 0, 1, -5);
   s.move(1,1);  // zavola prekrytu metodu z triedy Segment
+
   s.move(1, 1);  // zavola prekrytu metodu z triedy Segment
   s = new Circle(0,0,1);
+
   s = new Circle(0, 0, 1);
   s.move(1,1);  // zavola metodu z triedy Shape, lebo v Circle nie je prekryta
+
   s.move(1, 1);  // zavola metodu z triedy Shape, lebo v Circle nie je prekryta
  
 
   Shape[] shapes = new Shape[3];
 
   Shape[] shapes = new Shape[3];
  // vypln pole shapes
+
 
 
   //...
 
   //...
 +
 
   for(Shape x : shapes) {
 
   for(Shape x : shapes) {
       x.move(deltaX, deltaY);  // kazdy prvok sa posuva svojou metodou move, ak ju ma
+
       x.move(deltaX, deltaY);  // kazdy prvok pola sa posuva svojou metodou move, ak ju ma
 
   }
 
   }
 
</syntaxhighlight>
 
</syntaxhighlight>
Riadok 253: Riadok 366:
 
* V opačnom prípade sa vhodná verzia metódy <tt>f</tt> hľadá v nadtriede triedy <tt>T</tt>, v prípade neúspechu v nadtriede nadtriedy <tt>T</tt>, atď.
 
* V opačnom prípade sa vhodná verzia metódy <tt>f</tt> hľadá v nadtriede triedy <tt>T</tt>, v prípade neúspechu v nadtriede nadtriedy <tt>T</tt>, atď.
  
''Polymorfizus môže byť schovaný aj hlbšie'' &ndash; neprekrytá metóda z predka môže vo svojom tele volať prekryté metódy, čím sa jej správanie mení v závislosti od typu objektu:
+
''Polymorfizus môže byť schovaný aj hlbšie'' &ndash; neprekrytá metóda z predka môže vo svojom tele volať prekryté metódy, čím sa jej správanie mení v závislosti od typu objektu.
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
class SuperClass {
+
public class SuperClass {
     void doX() {  
+
     void f() {  
         System.out.println("doX in Super");
+
         System.out.println("Metoda f nadtriedy.");
 
     }
 
     }
 
    
 
    
     void doXTwice() {  
+
     void g() {  
         doX();
+
         f();
         doX();
+
         f();
 
     }     
 
     }     
 
}
 
}
 +
</syntaxhighlight>
  
class SubClass extends SuperClass {
+
<syntaxhighlight lang="java">
     void doX() {  
+
public class SubClass extends SuperClass {
         System.out.println("doX in Sub");
+
    @Override
 +
     void f() {  
 +
         System.out.println("Metoda f podtriedy.");
 
     }
 
     }
 
}
 
}
 +
</syntaxhighlight>
  
// v metode main:
+
<syntaxhighlight lang="java">
 
SuperClass a = new SubClass();
 
SuperClass a = new SubClass();
a.doXTwice(); // vypise 2x doX in Sub
+
a.g(); // vypise sa dvakrat "Metoda f podtriedy."
 
</syntaxhighlight>
 
</syntaxhighlight>
Zmysluplnejší príklad bude poskytovať metóda <tt>printArea</tt> v príklade nižšie.
+
Zmysluplnejším príkladom takéhoto správania bude metóda <tt>approximateArea</tt> v príklade nižšie.
  
 
== Abstraktné triedy a metódy ==
 
== Abstraktné triedy a metódy ==
Riadok 285: Riadok 402:
 
Vzniknutú situáciu možno riešiť nasledovne:
 
Vzniknutú situáciu možno riešiť nasledovne:
 
* Metódu <tt>area()</tt> v triede <tt>Shape</tt>, ako aj triedu <tt>Shape</tt> samotnú, označíme za ''abstraktnú'' modifikátorom <tt>abstract</tt>.  
 
* Metódu <tt>area()</tt> v triede <tt>Shape</tt>, ako aj triedu <tt>Shape</tt> samotnú, označíme za ''abstraktnú'' modifikátorom <tt>abstract</tt>.  
* Abstraktná metóda pozostáva iba z hlavičky bez samotnej implementácie.  
+
* Abstraktná metóda pozostáva iba z hlavičky bez samotnej implementácie a je určená na prekrytie v podtriedach (musí ísť o nestatickú metódu).  
* Abstraktná trieda je trieda, ktorá môže obsahovať abstraktné metódy. Zo zrejmých dôvodov z nej nemožno tvoriť inštancie (napríklad v našom príklade by tieto inštancie &bdquo;nevedeli, čo robiť&rdquo; pri volaní metódy <tt>area()</tt>), ale za účelom volania z podtried môže obsahovať definície konštruktorov. Abstraktná trieda slúži iba na dedenie, stále však môže byť typom ''referencie'' na objekt.
+
* Abstraktná trieda je trieda, ktorá môže obsahovať abstraktné metódy. Zo zrejmých dôvodov z nej nemožno priamo tvoriť inštancie &ndash; napríklad v našom príklade by tieto inštancie &bdquo;nevedeli, čo robiť&rdquo; pri volaní metódy <tt>area()</tt> &ndash; ale za účelom volania z podtried môže obsahovať definície konštruktorov. Abstraktná trieda slúži iba na dedenie a ako taká nemôže byť typom žiadneho objektu. Stále však môže byť typom ''referencie'' na objekt.
 
* Podtriedy abstraktnej triedy, ktoré nie sú abstraktné, musia implementovať všetky abstraktné metódy svojho predka.
 
* Podtriedy abstraktnej triedy, ktoré nie sú abstraktné, musia implementovať všetky abstraktné metódy svojho predka.
  
 
''Príklad'':  
 
''Príklad'':  
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
abstract class Shape {
+
public abstract class Shape {
 
     // ...
 
     // ...
 
      
 
      
 
     public abstract double area();
 
     public abstract double area();
   
+
 
     public void printArea() {
+
     public long approximateArea() {
         System.out.println("Plocha je " + area() + ".");
+
         return Math.round(area());
 
     }
 
     }
 
}
 
}
 +
</syntaxhighlight>
  
class Rectangle extends Shape {
+
<syntaxhighlight lang="java">
 +
public class Rectangle extends Shape {
 
     // ...
 
     // ...
 
      
 
      
Riadok 309: Riadok 428:
 
     }
 
     }
 
}
 
}
 +
</syntaxhighlight>
  
class Circle extends Shape {
+
<syntaxhighlight lang="java">
 +
public class Circle extends Shape {
 
     // ...
 
     // ...
 
      
 
      
Riadok 318: Riadok 439:
 
     }
 
     }
 
}
 
}
 +
</syntaxhighlight>
  
class Segment extends Shape {
+
<syntaxhighlight lang="java">
 +
public class Segment extends Shape {
 
     // ...
 
     // ...
 
      
 
      
Riadok 333: Riadok 456:
 
public static void main(String[] args) {
 
public static void main(String[] args) {
 
     Shape[] shapes = new Shape[3];
 
     Shape[] shapes = new Shape[3];
     shapes[0] = new Rectangle(0,0,1,2);
+
     shapes[0] = new Rectangle(0, 0, 1, 2);
     shapes[1] = new Circle(0,0,1);
+
     shapes[1] = new Circle(0, 0, 1);
     shapes[2] = new Segment(1,1,2,2);
+
     shapes[2] = new Segment(1, 1, 2, 2);
 
      
 
      
 
     for (Shape x : shapes) {
 
     for (Shape x : shapes) {
         x.printArea();
+
         System.out.println(x.area() + " " + x.approximateArea());
 
     }
 
     }
 
}
 
}
Riadok 344: Riadok 467:
 
potom vypíše nasledujúci výstup:
 
potom vypíše nasledujúci výstup:
 
<pre>
 
<pre>
Plocha je 2.0.
+
2.0 2
Plocha je 3.141592653589793.
+
3.141592653589793 3
Plocha je 0.0.
+
0.0 0
 
</pre>
 
</pre>
  
 
== Hierarchia tried a trieda Object ==
 
== Hierarchia tried a trieda Object ==
  
* V Jave môže každá trieda dediť iba od jednej triedy (na rozdiel napríklad od C++, kde je možné dedenie od viacerých tried).
+
* V Jave môže každá trieda dediť iba od jednej triedy (na rozdiel od niektorých iných jazykov, kde je možné dedenie od viacerých tried).
 
* Dedenie je však možné &bdquo;viacúrovňovo&rdquo;:
 
* Dedenie je však možné &bdquo;viacúrovňovo&rdquo;:
  
Riadok 358: Riadok 481:
 
}
 
}
  
class Civava extends Pes { // hierarchia tried nemusi verne zodpovedat realite
+
class Civava extends Pes { // Hierarchia tried nemusi verne zodpovedat realite
}
 
</syntaxhighlight>
 
 
 
* Všetky triedy sú automaticky potomkami triedy <tt>Object</tt>; tá sa tiež považuje za priamu nadtriedu tried, ktoré explicitne nerozširujú žiadnu triedu.
 
* Trieda [https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html <tt>Object</tt>] obsahuje metódy (napr. <tt>toString()</tt>), ktoré je často užitočné prekrývať.
 
 
 
''Príklad'':
 
 
 
* Nasledujúci kus kódu je o niečo elegantnejším spôsobom vypisovania geometrických útvarov, než pomocou metódy <tt>Circle.print</tt>:
 
 
 
<syntaxhighlight lang="java">
 
class Circle extends Shape {
 
    // ...
 
   
 
    @Override
 
    public String toString() {
 
        return "Stred: (" + x + "," + y + "). Polomer: " + radius + "."; 
 
    }
 
   
 
    // ...
 
 
}
 
}
 
// ...
 
 
// V metode main:
 
 
Circle c = new Circle(0, 0, 1);
 
System.out.println(c.toString());
 
System.out.println(c);  // ekvivalentne predchadzajucemu volaniu
 
 
// ...     
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 +
* Všetky triedy sú automaticky potomkami triedy [https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Object.html <tt>Object</tt>]; tá sa tiež považuje za priamu nadtriedu tried, ktoré explicitne nerozširujú žiadnu triedu.
 +
* Trieda <tt>Object</tt> obsahuje metódy &ndash; napríklad <tt>toString</tt> alebo <tt>equals</tt> &ndash; ktoré je často užitočné prekrývať.
 +
* To vysvetľuje, prečo sme pri metóde <tt>toString</tt> triedy <tt>Circle</tt> použili anotáciu <tt>Override</tt>: prekryli sme totiž jej definíciu z triedy <tt>Object</tt>.
 +
* Vypisovanie kružnice <tt>Circle circle</tt> cez <tt>System.out.println(circle)</tt> je zas ukážkou polymorfizmu. Ide tu o použitie verzie metódy <tt>println</tt>, ktorá ako argument očakáva inštanciu triedy <tt>Object</tt> a na výpis používa metódu <tt>toString</tt> tejto inštancie. V prípade, že metódu <tt>println</tt> zavoláme pre inštanciu podtriedy triedy <tt>Object</tt>, použije sa pri výpise prekrytá verzia metódy <tt>toString</tt>.
 
* Veľmi špeciálnym druhom objektov v Jave sú polia, pričom polia typu <tt>T</tt> (kde <tt>T</tt> je trieda alebo primitívny typ) sa považujú za inštancie triedy <tt>T[]</tt>, ktorá je podtriedou triedy <tt>Object</tt>. Aj na polia teda v princípe možno aplikovať metódu <tt>toString</tt> triedy <tt>Object</tt>, ''avšak od jej použitia nemožno očakávať žiadne rozumné správanie, keďže nebola zmysluplným spôsobom prekrytá''.
 
* Veľmi špeciálnym druhom objektov v Jave sú polia, pričom polia typu <tt>T</tt> (kde <tt>T</tt> je trieda alebo primitívny typ) sa považujú za inštancie triedy <tt>T[]</tt>, ktorá je podtriedou triedy <tt>Object</tt>. Aj na polia teda v princípe možno aplikovať metódu <tt>toString</tt> triedy <tt>Object</tt>, ''avšak od jej použitia nemožno očakávať žiadne rozumné správanie, keďže nebola zmysluplným spôsobom prekrytá''.
  
 
== Rozhrania ==
 
== Rozhrania ==
  
Rozhranie (<tt>interface</tt>) je podobným konceptom ako abstraktná trieda. Existuje však medzi nimi niekoľko rozdielov, z ktorých najpodstatnejšie sú tieto:
+
Rozhranie (angl. ''interface'') je podobným konceptom ako abstraktná trieda. Existuje však medzi nimi niekoľko rozdielov, z ktorých najpodstatnejšie sú tieto:
* Rozhranie nemôže obsahovať konštruktory, ani iné ako finálne premenné.
+
* Rozhranie slúži predovšetkým ako zoznam ''abstraktných'' metód &ndash; kľúčové slovo <tt>abstract</tt> tu netreba uvádzať. Pri triedach implementujúcich rozhranie je garantované, že na prácu s nimi bude možné použiť metódy deklarované v rozhraní (odtiaľ aj termín &bdquo;rozhranie&rdquo;). Napríklad rozhranie pre zásobníky by mohlo deklarovať metódy ako <tt>push</tt>, <tt>pop</tt> a <tt>isEmpty</tt> a triedy pre zásobníky implementované pomocou polí resp. spájaných zoznamov by toto rozhranie mohli implementovať.
* Rozhranie slúži predovšetkým ako zoznam ''abstraktných'' metód &ndash; kľúčové slovo <tt>abstract</tt> tu netreba uvádzať. Naopak implementované nestatické metódy musia byť označené kľúčovým slovom <tt>default</tt>.
+
* Naopak implementované metódy musia byť v rozhraní označené kľúčovým slovom <tt>default</tt> (čo sa však typicky využíva iba vo veľmi špeciálnych situáciách), prípadne musia byť statické.
* Kým od tried sa dedí pomocou kľúčového slova <tt>extends</tt>, rozhrania sa ''implementujú'' pomocou kľúčového slova <tt>implements</tt>. Rozdiel je predovšetkým v tom, že implementovať možno aj viacero rozhraní. Jedno rozhranie môže navyše rozširovať iné (dopĺňať ho o ďalšie požadované funkcie): v takom prípade používame kľúčové slovo <tt>extends</tt>.
+
* Rozhranie nemôže definovať konštruktory, ani iné ako finálne premenné (t. j. konštanty).
* Všetky položky v rozhraní sa chápu ako verejné (<tt>public</tt>).
+
* Kým od tried sa dedí pomocou kľúčového slova <tt>extends</tt>, rozhrania sa ''implementujú'' pomocou kľúčového slova <tt>implements</tt>. Rozdiel je predovšetkým v tom, že implementovať možno aj viacero rozhraní. Jedno rozhranie môže navyše rozširovať iné (dopĺňať ho o ďalšie požadované metódy): v takom prípade používame kľúčové slovo <tt>extends</tt>.
 +
* Všetky položky v rozhraní sa chápu ako verejné (modifikátor <tt>public</tt> teda nie je potrebné explicitne uvádzať).
 +
* Podobne ako abstraktná trieda, môže byť aj rozhranie typom referencie.
 +
* Hoci nejde o prekrývanie v pravom slova zmysle, aj pri implementovaní metód z rozhraní používame anotáciu <tt>@Override</tt>.
  
 
''Príklad použitia'':
 
''Príklad použitia'':
  
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
interface Stack {
+
public interface Movable {
     void push(int item);
+
     void move(double deltaX, double deltaY);
    int pop();
 
 
}
 
}
 +
</syntaxhighlight>
  
interface Printable {
+
<syntaxhighlight lang="java">
     void print();
+
public interface Measurable {
 +
     double area();
 +
    long approximateArea();
 
}
 
}
 +
</syntaxhighlight>
 +
 +
<syntaxhighlight lang="java">
 +
public abstract class Shape implements Movable, Measurable {
 +
    // ...
  
class LinkedStack implements Stack, Printable {
 
    static class Node {
 
        private int data;
 
        private Node next;
 
       
 
        public Node(int data, Node next) {
 
            this.data = data;
 
            this.next = next;
 
        }
 
       
 
        public int getData() {
 
            return data;
 
        }
 
       
 
        public Node getNext() {
 
            return next;
 
        }
 
    }
 
   
 
    private Node top;
 
   
 
 
     @Override
 
     @Override
     public void push(int item) {
+
     public void move(double deltaX, double deltaY) {
         Node p = new Node(item, top);
+
         x += deltaX;
         top = p;
+
         y += deltaY;
 
     }
 
     }
   
+
 
    @Override
 
    public int pop() {
 
        if (top == null) {
 
            return -1;
 
        }
 
        int result = top.getData();
 
        top = top.getNext();
 
        return result;
 
    }
 
   
 
 
     @Override
 
     @Override
     public void print() {
+
     public abstract double area(); // Deklaracia tejto abstraktnej metody sa stava nepotrebnou
        Node p = top;
 
        while (p != null) {
 
            System.out.print(p.getData() + " ");
 
            p = p.getNext();
 
        }
 
        System.out.println();
 
    }
 
}
 
 
 
class ArrayStack implements Stack, Printable {
 
    private int[] a;
 
    private int n;
 
   
 
    public ArrayStack() {
 
a = new int[100];
 
n = 0;
 
    }
 
  
 
     @Override
 
     @Override
     public void push(int item) {
+
     public long approximateArea() {
         a[n] = item;
+
         return Math.round(area());
        n++;
 
    }
 
   
 
    @Override
 
    public int pop() {
 
        if (n <= 0) {
 
            return -1;
 
        }
 
n--;
 
        return a[n];
 
 
     }
 
     }
  
     @Override
+
     // ...
    public void print() {
 
        for (int i = 0; i <= n-1; i++) {
 
            System.out.print(a[i] + " ");
 
        }
 
        System.out.println();
 
    }
 
 
}
 
}
 +
</syntaxhighlight>
  
class Blabol implements Printable {
+
<syntaxhighlight lang="java">
    @Override
+
     public static void main(String[] args) {
     public void print() {  
+
         Measurable[] elements = new Shape[3];  // Podobne ako abstraktne triedy mozu byt aj rozhrania typmi referencie
         System.out.println("Blabla");  
+
        elements[0] = new Rectangle(0, 0, 1, 2);
    }
+
        elements[1] = new Circle(0, 0, 1);
}
+
        elements[2] = new Segment(1, 1, 2, 2);
  
public class InterfaceExample {
+
        for (Measurable element : elements) {
    static void fillStack(Stack stack) {
+
            System.out.println(element.area() + " " + element.approximateArea());
        stack.push(10);
+
         }
        stack.push(20);
 
    }
 
 
 
    static void printTwice(Printable what) {
 
        what.print();
 
         what.print();
 
 
     }
 
     }
 
    public static void main(String[] args) {
 
        LinkedStack s1 = new LinkedStack();
 
        Stack s2 = new ArrayStack();
 
        Blabol b = new Blabol();
 
        fillStack(s1);
 
        fillStack(s2);
 
        printTwice(s1);
 
        //printTwice(s2); // s2 je Stack a nevie, ze sa vie vypisat
 
        printTwice((ArrayStack) s2);
 
        printTwice(b);
 
    }
 
}
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Riadok 531: Riadok 556:
 
''Modifikátory prístupu'':
 
''Modifikátory prístupu'':
 
* <tt>public</tt>: triedy, rozhrania a ich súčasti prístupné odvšadiaľ.
 
* <tt>public</tt>: triedy, rozhrania a ich súčasti prístupné odvšadiaľ.
* (žiaden modifikátor): viditeľnosť len v rámci balíčka (<tt>package</tt>).
+
* (žiaden modifikátor): viditeľnosť len v rámci balíka (<tt>package</tt>).
* <tt>protected</tt>: viditeľnosť v triede, jej podtriedach a v rámci balíčka.
+
* <tt>protected</tt>: viditeľnosť v triede, jej podtriedach a v rámci balíka (len pre premenné, metódy a konštruktory; nedá sa aplikovať na triedu samotnú).
* <tt>private</tt>: viditeľnosť len v danej triede.
+
* <tt>private</tt>: viditeľnosť len v danej triede (len pre premenné, metódy a konštruktory; nedá sa aplikovať na triedu samotnú).
  
 
''Iné modifikátory'':
 
''Iné modifikátory'':
Riadok 540: Riadok 565:
 
** Ak je trieda <tt>final</tt>, nedá sa z nej ďalej dediť.
 
** Ak je trieda <tt>final</tt>, nedá sa z nej ďalej dediť.
 
** Ak je metóda <tt>final</tt>, nedá sa v podtriede prekryť.
 
** Ak je metóda <tt>final</tt>, nedá sa v podtriede prekryť.
** Ak je premenná alebo parameter <tt>final</tt>, ide o konštantu, ktorú nemožno meniť.
+
** Ak je premenná alebo parameter <tt>final</tt>, ide o &bdquo;konštantu&rdquo;, ktorú nemožno meniť (možno ju ale inicializovať aj za behu).
 
* <tt>static</tt>:
 
* <tt>static</tt>:
** Statické premenné a metódy sa týkajú celej triedy, nie konkrétnej inštancie.
+
** Statické premenné a metódy príslušia triede samotnej, nie jej inštanciám.
 
** Statické triedy vo vnútri inej triedy nie sú viazané na jej konkrétnu inštanciu (viac neskôr).
 
** Statické triedy vo vnútri inej triedy nie sú viazané na jej konkrétnu inštanciu (viac neskôr).
  
 
== Aritmetický strom s využitím dedenia ==
 
== Aritmetický strom s využitím dedenia ==
  
V minulom semestri sme upozorňovali na návrhový nedostatok pri realizácii aritmetického stromu: niektoré položky uložené v <tt>struct</tt>-och sa využívali len v niektorých uzloch stromu (hodnoty iba v listoch a operátory iba vo vnútorných uzloch). Tomuto sa vieme vyhnúť pomocou dedenia.
+
V minulom semestri ste boli upozornení na návrhový nedostatok pri realizácii aritmetického stromu: niektoré položky uložené v štruktúrach reprezentujúcich uzly takýchto stromov sa využívali len pre určité druhy uzlov (hodnoty iba v listoch a operátory iba vo vnútorných uzloch). Tomuto sa vieme vyhnúť pomocou dedenia.
* Jednotlivé typy vrcholov budú podtriedy abstraktnej triedy <tt>Node</tt>
+
* Jednotlivé typy uzlov budú podtriedy abstraktnej triedy <tt>Node</tt>.
* Namiesto použitia príkazu <tt>switch</tt> na typ vrchola tu prekryjeme potrebné funkcie, napríklad <tt>evaluate</tt>.
+
* Namiesto použitia príkazu <tt>switch</tt> na typ uzla tu prekryjeme potrebné metódy, napríklad <tt>evaluate</tt>.
 
<syntaxhighlight lang="java">
 
<syntaxhighlight lang="java">
 
abstract class Node {
 
abstract class Node {
 
     public abstract int evaluate();
 
     public abstract int evaluate();
 
}
 
}
 +
</syntaxhighlight>
  
abstract class NularyNode extends Node {
+
<syntaxhighlight lang="java">
 +
abstract class NullaryNode extends Node {
 
}
 
}
 +
</syntaxhighlight>
  
 +
<syntaxhighlight lang="java">
 
abstract class UnaryNode extends Node {
 
abstract class UnaryNode extends Node {
     Node child;
+
     private Node child;
 
      
 
      
 +
    public Node getChild() {
 +
        return child;
 +
    }
 +
 
     public UnaryNode(Node child) {
 
     public UnaryNode(Node child) {
 
         this.child = child;  
 
         this.child = child;  
 
     }
 
     }
 
}
 
}
 +
</syntaxhighlight>
  
 +
<syntaxhighlight lang="java">
 
abstract class BinaryNode extends Node {
 
abstract class BinaryNode extends Node {
     Node left;
+
     private Node left;
     Node right;
+
     private Node right;
 +
 
 +
    public Node getLeft() {
 +
        return left;
 +
    }
 +
 
 +
    public Node getRight() {
 +
        return right;
 +
    }
  
 
     public BinaryNode(Node left, Node right) {  
 
     public BinaryNode(Node left, Node right) {  
Riadok 575: Riadok 618:
 
     }
 
     }
 
}
 
}
 +
</syntaxhighlight>
  
class Constant extends NularyNode {
+
<syntaxhighlight lang="java">
     int value;
+
class Constant extends NullaryNode {
 +
     private int value;
 
      
 
      
 
     public Constant(int value) {  
 
     public Constant(int value) {  
Riadok 593: Riadok 638:
 
     }
 
     }
 
}
 
}
 +
</syntaxhighlight>
  
 +
<syntaxhighlight lang="java">
 
class UnaryMinus extends UnaryNode {
 
class UnaryMinus extends UnaryNode {
 
     public UnaryMinus(Node child){
 
     public UnaryMinus(Node child){
Riadok 601: Riadok 648:
 
     @Override  
 
     @Override  
 
     public int evaluate() {  
 
     public int evaluate() {  
         return -child.evaluate();
+
         return -getChild().evaluate();
 
     }
 
     }
  
 
     @Override  
 
     @Override  
 
     public String toString() {  
 
     public String toString() {  
         return "(-" + child.toString() + ")";
+
         return "(-" + getChild().toString() + ")";
 
     }
 
     }
 
}
 
}
 +
</syntaxhighlight>
  
 +
<syntaxhighlight lang="java">
 
class Plus extends BinaryNode {  
 
class Plus extends BinaryNode {  
 
     public Plus(Node left, Node right) {  
 
     public Plus(Node left, Node right) {  
         super(left,right);
+
         super(left, right);
 
     }
 
     }
  
 
     @Override
 
     @Override
 
     public int evaluate() {  
 
     public int evaluate() {  
         return left.evaluate() + right.evaluate();
+
         return getLeft().evaluate() + getRight().evaluate();
 
     }
 
     }
  
 
     @Override  
 
     @Override  
 
     public String toString() {  
 
     public String toString() {  
         return "(" + left.toString() + "+" + right.toString() + ")";
+
         return "(" + getLeft().toString() + "+" + getRight().toString() + ")";
 
     }
 
     }
 
}
 
}
 +
</syntaxhighlight>
  
 +
<syntaxhighlight lang="java">
 
public class Expressions {
 
public class Expressions {
  

Aktuálna revízia z 19:40, 2. marec 2024

Oznamy

  • Prvú bonusovú domácu úlohu je v prípade záujmu potrebné odovzdať najneskôr do začiatku zajtrajších cvičení.
  • Počas budúcotýždňových cvičení – čiže v utorok 12. marca od 9:50 – bude prebiehať prvý test zameraný na látku z prvých troch týždňov semestra. Body z testu bude možné získať iba v prípade prítomnosti na cvičeniach v miestnosti I-H6.

Opakovanie: triedy a objekty

  • Objekt je predovšetkým súborom rôznych dát a metód na manipuláciu s nimi. Na objekty sa odkazuje pomocou ich identifikátorov, ktoré sú referenciami na ich „pamäťové adresy”.
  • Každý objekt je inštanciou nejakej triedy (angl. class). Triedu možno chápať ako „vzor”, podľa ktorého sa vytvárajú objekty. Trieda tiež reprezentuje typ jej objektov.
  • Trieda sa teda podobá na struct z jazykov C a C++ v tom, že môže združovať niekoľko hodnôt rôznych typov. Ide však o omnoho bohatší koncept – môže obsahovať metódy (funkcie) na prácu s dátami uloženými v inštancii danej triedy, umožňuje nastaviť viditeľnosť jednotlivých premenných a metód pomocou modifikátorov, atď.
  • Konštruktory sú špeciálne kusy kódu slúžiace na vytvorenie objektu (inštancie triedy).
  • Dôležitým metodickým princípom objektovo orientovaného programovania je zapuzdrenie (angl. encapsulation): spojenie dát a súvisiaceho kódu do koherentného logického celku.
    • Trieda väčšinou navonok ukazuje iba vhodne zvolenú časť metód.
    • Premenné a pomocné metódy sú skryté – prípadné zmeny vnútornej implementácie triedy sa tak nemusia prejaviť v kóde pracujúcom s touto triedu.

Dedenie

Trieda môže byť podtriedou inej triedy. Napríklad trieda Pes môže byť podtriedou všeobecnejšej triedy Zviera: každý objekt, ktorý je inštanciou triedy Pes je potom súčasne aj inštanciou triedy Zviera. Tento vzťah medzi triedami vyjadrujeme kľúčovým slovom extends v definícii triedy.

class Pes extends Zviera { 
    // ...
}

Hovoríme tiež, že trieda Pes dedí od triedy Zviera, alebo že trieda Pes triedu Zviera rozširuje. V prípade vhodne zvolených prístupových modifikátorov (detaily neskôr) totiž inštancia triedy Pes zdedí metódy a premenné (nie konštruktory) definované v triede Zviera a tie sa potom správajú tak, ako keby boli priamo definované aj v triede Pes.

Dedenie umožňuje vyhnúť sa nutnosti písať podobný kód viackrát. Namiesto implementácie podobných metód v niekoľkých triedach možno vytvoriť nadtriedu týchto tried a spoločné časti kódu presunúť tam.

Príklad

Uvažujme triedy reprezentujúce rôzne geometrické útvary v rovine a poskytujúce metódu move realizujúcu posunutie.

public class Rectangle {
    private double x, y;           // Suradnice laveho horneho rohu
    private double width, height;  // Vyska a sirka obdlznika

    public double getX() {
        return x;
    }

    public void setX(double x) {
        this.x = x;
    }

    public double getY() {
        return y;
    }

    public void setY(double y) {
        this.y = y;
    }

    public double getWidth() {
        return width;
    }

    public void setWidth(double width) {
        this.width = width;
    }

    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        this.height = height;
    }

    public void move(double deltaX, double deltaY) {
        x += deltaX;
        y += deltaY;
    }
}
public class Circle {
    private double x, y;    // Suradnice stredu
    private double radius;  // Polomer kruznice

    public double getX() {
        return x;
    }

    public void setX(double x) {
        this.x = x;
    }

    public double getY() {
        return y;
    }

    public void setY(double y) {
        this.y = y;
    }

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }

    public void move(double deltaX, double deltaY) {
        x += deltaX;
        y += deltaY;
    }
}

Vidíme, že obidve triedy obsahujú pomerne veľa spoločného kódu, ktorý nie je nijak špecifický pre obdĺžniky alebo kruhy, ale naopak môže byť v nezmenenej podobe použitý aj pri ďalších rovinných geometrických útvaroch. Využijeme teda mechanizmus dedenia – spoločné premenné a metódy tried Rectangle a Circle presunieme do ich spoločnej nadtriedy Shape.

public class Shape {
    private double x, y;  // Suradnice nejakeho vyznacneho bodu geometrickeho utvaru

    public double getX() {
        return x;
    }

    public void setX(double x) {
        this.x = x;
    }

    public double getY() {
        return y;
    }

    public void setY(double y) {
        this.y = y;
    }

    public void move(double deltaX, double deltaY) {
        x += deltaX;
        y += deltaY;
    }
}
public class Rectangle extends Shape {
    private double width, height;

    public double getWidth() {
        return width;
    }

    public void setWidth(double width) {
        this.width = width;
    }

    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        this.height = height;
    }

    // Pripadne dalsie metody pre obdlznik
}
public class Circle extends Shape {
    private double radius;

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }

    // Pripadne dalsie metody pre kruznicu
}

V rámci triedy možno používať aj verejné metódy a premenné nadtriedy, ako keby boli jej vlastné – a často aj metódy a premenné s inými modifikátormi rôznymi od private (o tom neskôr). V metódach a konštruktoroch tried Rectangle a Circle tak napríklad môžeme používať metódy getX, setX, getY, setY a move.

public class Rectangle extends Shape {
    // ...
    
    public Rectangle(double x, double y, double width, double height) {
        setX(x);
        setY(y);
        setWidth(width);
        setHeight(height);
    }

    // ...
}
public class Circle extends Shape {
    // ...
    
    public Circle(double x, double y, double radius) {
        setX(x);
        setY(y);
        setRadius(radius);
    }
    
    @Override // Vyznam tejto znacky si vysvetlime o chvilu
    public String toString() {
        return "Stred: [" + getX() + "," + getY() + "]; Polomer: " + getRadius() + ".";
    }

    // ...
}
  • Inštanciu c triedy Circle teraz môžeme nielen vypísať na konzolu cez System.out.println(c) (použije sa metóda toString), ale môžeme pre ňu zavolať aj ľubovoľnú metódu triedy Shape, napríklad c.move(1, 1) alebo c.setX(2).

Dedenie a typy

  • Typom objektu je trieda určená konštruktorom, ktorým bol objekt vytvorený – napríklad volanie konštruktora triedy Circle má za následok vytvorenie objektu typu Circle.
  • Premenná, ktorej typom je trieda T však môže obsahovať aj referenciu na objekt, ktorého typom je podtrieda triedy T. Napríklad premenná typu Shape tak môže obsahovať referenciu na objekt triedy Shape alebo jej ľubovoľnej podtriedy. Vždy teda treba rozlišovať medzi typom referencie (premennej obsahujúcej referenciu na objekt) a samotným typom objektu.
Circle circle = new Circle(0, 0, 5);
Shape shape = circle;     // toto je korektne priradenie
// circle = shape;        // toto neskompiluje, kedze shape nemusi byt kruznica
circle = (Circle) shape;  // po pretypovani to uz skompilovat pojde; ak shape nie je instanciou Circle alebo null, vyhodi sa vynimka
  • Istejší prístup je pri priraďovaní premennej typu Shape do premennej typu Circle najprv overiť, či premenná typu Shape obsahuje referenciu na inštanciu triedy Circle. Na to slúži operátor instanceof. Platí pritom, že ak je objekt inštanciou nejakej triedy, je súčasne aj inštanciou ľubovoľnej jej nadtriedy (samotný typ objektu je však daný iba najnižšou triedou v tomto usporiadaní). Napríklad podmienka shape instanceof Shape je splnená kedykoľvek je splnená podmienka shape instanceof Circle. Pre ľubovoľnú triedu Trieda má výraz null instanceof Trieda vždy hodnotu false (a rovnako pre premennú obsahujúcu null).
if (shape instanceof Circle) {  
    circle = (Circle) shape;
}
  • Keďže teda môžeme inštancie tried Rectangle alebo Circle považovať aj za inštancie ich spoločnej nadtriedy Shape, môžeme rôzne typy útvarov spracúvať tým istým kódom. Napríklad nasledujúca metóda dostane pole útvarov a posunie každý z nich o daný vektor (deltaX, deltaY).
public static void moveAll(Shape[] shapes, double deltaX, double deltaY) {
    for (Shape shape : shapes) {
        shape.move(deltaX, deltaY);
    }
}
  • Cvičenie: čo vypíše nasledujúci kód?
Shape[] shapes = new Shape[2];
shapes[0] = new Rectangle(0, 0, 1, 2);
shapes[1] = new Circle(0, 0, 1);

moveAll(shapes, 2, 2);

for (Shape x : shapes) {
    if (x instanceof Circle) {
        System.out.println("Je to kruh.");
        Circle c = (Circle) x;  // O chvilu uvidime, ze toto pretypovanie kvoli nasledujucemu riadku nie je nutne
        System.out.println(c);
    }
    if (x instanceof Shape) {
        System.out.println("Je to utvar.");
    }
}

Dedenie a konštruktory

  • Typickou úlohou konštruktora je správne nainicializovať objekt.
  • Pri dedení si väčšinou každá trieda inicializuje „svoje” premenné.
  • Napríklad krajší spôsob realizácie konštruktorov pre geometrické útvary je nasledovný: Shape inicializuje x a y, pričom napríklad Circle nechá inicializáciu x a y na Shape a inicializuje už len radius.
  • Prvý príkaz konštruktora môže pozostávať z volania konštruktora predka pomocou kľúčového slova super (z angl. superclass, t. j. nadtrieda).
public class Shape {
    // ...    

    public Shape(double x, double y) {
        this.x = x; // alebo setX(x);
        this.y = y; // alebo setY(y);
    }
    
    // ...
}
public class Rectangle extends Shape {
    // ...
    
    public Rectangle(double x, double y, double width, double height) {
        super(x, y);
        this.width = width;   // alebo setWidth(width);
        this.height = height; // alebo setHeight(height);
    }
    
    // ...
}
public class Circle extends Shape {
    // ...         
    
    public Circle(double x, double y, double radius) {
        super(x, y);
        this.radius = radius; // alebo setRadius(radius);
    }
    
    // ...
}
  • Ak nezavoláme konštruktor nadtriedy ručne, automaticky sa zavolá konštruktor bez parametrov, t. j. super(). To môže pri kompilovaní vyústiť v chybu v prípade, keď nadtrieda nemá definovaný konštruktor bez parametrov (či už explicitne jeho implementáciou, alebo implicitne tým, že sa neuvedie implementácia žiadneho konštruktora nadtriedy). Napríklad v horeuvedenom príklade je teda volanie konštruktora nadtriedy nutnou podmienkou úspešného skompilovania programu.
  • Výnimkou je prípad, keď sa v rámci prvého príkazu volá iný konštruktor tej istej triedy pomocou this(...) – vtedy sa volanie konštruktora nadtriedy nechá na práve zavolaný konštruktor.

Prekrývanie metód a polymorfizmus

Podtrieda môže prekryť (angl. override) niektoré zdedené metódy, aby sa chovali inak ako v predkovi.

Uvažujme napríklad útvar Segment (úsečka), ktorý je zadaný dvoma koncovými bodmi a v metóde move treba posunúť oba. V triede Segment je teda potrebné metódu move prekryť. Metódu move z nadtriedy Shape pritom možno zavolať ako super.move, ale nemusí to byť v rámci prvého príkazu prekrývajúcej metódy move a metóda nadtriedy sa tam prípadne ani nemusí zavolať vôbec (volanie metódy super.move možno použiť aj v iných metódach a konštruktoroch triedy Segment).

public class Segment extends Shape {
    private double x2, y2;

    // ...

    public Segment(double x, double y, double x2, double y2) {
        super(x, y);
        this.x2 = x2;
        this.y2 = y2;
    }

    @Override
    public void move(double deltaX, double deltaY) {
        super.move(deltaX, deltaY);
        x2 += deltaX;
        y2 += deltaY;
    }
}

O prekrytie metódy z nadtriedy ide samozrejme iba v prípade, že má prekrývajúca metóda rovnaký názov a rovnakú postupnosť typov parametrov (t. j. rovnakú signatúru) ako metóda nadtriedy. V prípade rovnakého názvu metódy, ale rozdielnych typov parametrov ide len o preťaženie metódy, ako ho poznáme z minulej prednášky.

Návratový typ prekrývajúcej metódy sa musí buď zhodovať s návratovým typom prekrývanej metódy, alebo musí byť jeho „špecializáciou” (napr. môže ísť o podtriedu triedy, ktorá slúži ako návratový typ prekrývanej metódy). Modifikátor prístupu prekrývajúcej metódy musí byť nastavený tak, aby bola táto metóda prístupná kedykoľvek je prístupná prekrývaná metóda (ak je teda napr. prekrývaná metóda verejná, musí byť verejná aj prekrývajúca metóda). Pokiaľ tieto vlastnosti prekrývajúcej metódy nie sú splnené, program neskompiluje.

Anotácia @Override je pri prekrývaní metód nepovinná, ale odporúčaná. Ide o informáciu pre kompilátor, ktorou sa vyjadruje snaha o prekrytie zdedenej metódy. Ak sa v predkovi nenachádza metóda s rovnakou signatúrou, kompilátor vyhlási chybu. Tým sa dá predísť obzvlášť nepríjemným chybám, pri ktorých napríklad namiesto prekrytia metódy túto metódu neúmyselne preťažíme alebo napíšeme metódu s úplne iným názvom (napr. hashcode namiesto hashCode).

S prekrývaním metód súvisí polymorfizmus, pod ktorým sa v programovaní (hlavne pri OOP) rozumie schopnosť metód chovať sa rôzne:

  • S určitou formou polymorfizmu sme sa už stretli, keď sme mali viacero metód s rovnakým menom, avšak s rôznymi typmi parametrov (tzv. preťažovanie metód, angl. overloading).
  • Pri dedení sa navyše môže metóda chovať rôzne v závislosti od triedy, ku ktorej táto metóda patrí.
  • To, ktorá verzia metódy sa zavolá, záleží od toho, akého typu je objekt, nie akého typu je referencia naň.
  • Takto to ale funguje iba pri nestatických metódach (keďže statické metódy príslušia samotným triedam, rozdiel medzi typom referencie a typom inštancie tam nemožno využiť). Pri statických metódach preto nehovoríme o ich prekrývaní, ale o ich skrývaní; nemožno vtedy ani použiť anotáciu @Override.
  Shape s = new Segment(0, 0, 1, -5);
  s.move(1, 1);  // zavola prekrytu metodu z triedy Segment
  s = new Circle(0, 0, 1);
  s.move(1, 1);  // zavola metodu z triedy Shape, lebo v Circle nie je prekryta

  Shape[] shapes = new Shape[3];

  //...

  for(Shape x : shapes) {
      x.move(deltaX, deltaY);  // kazdy prvok pola sa posuva svojou metodou move, ak ju ma
  }

Vo všeobecnosti sa pri volaní o.f(par1,...,parn) pre objekt o typu T aplikuje nasledujúci princíp:

  • Ak má trieda T svoju implementáciu metódy f s vhodnými parametrami, vykoná sa táto verzia metódy.
  • V opačnom prípade sa vhodná verzia metódy f hľadá v nadtriede triedy T, v prípade neúspechu v nadtriede nadtriedy T, atď.

Polymorfizus môže byť schovaný aj hlbšie – neprekrytá metóda z predka môže vo svojom tele volať prekryté metódy, čím sa jej správanie mení v závislosti od typu objektu.

public class SuperClass {
    void f() { 
        System.out.println("Metoda f nadtriedy.");
    }
   
    void g() { 
        f();
        f();
    }    
}
public class SubClass extends SuperClass {
    @Override
    void f() { 
        System.out.println("Metoda f podtriedy.");
    }
}
SuperClass a = new SubClass();
a.g(); // vypise sa dvakrat "Metoda f podtriedy."

Zmysluplnejším príkladom takéhoto správania bude metóda approximateArea v príklade nižšie.

Abstraktné triedy a metódy

Aby sa metóda chovala v určitej skupine tried polymorfne, musí byť definovaná v ich spoločnej nadtriede. V tejto nadtriede však nemusí existovať jej zmysluplná implementácia.

  • Uvažujme napríklad metódu area(), ktorá zráta plochu geometrického útvaru.
  • Pre triedy Rectangle, Circle, resp. Segment je implementácia takejto metódy zrejmá. Zmysluplná implementácia v ich spoločnej nadtriede Shape by však bola prinajmenšom problematická.

Vzniknutú situáciu možno riešiť nasledovne:

  • Metódu area() v triede Shape, ako aj triedu Shape samotnú, označíme za abstraktnú modifikátorom abstract.
  • Abstraktná metóda pozostáva iba z hlavičky bez samotnej implementácie a je určená na prekrytie v podtriedach (musí ísť o nestatickú metódu).
  • Abstraktná trieda je trieda, ktorá môže obsahovať abstraktné metódy. Zo zrejmých dôvodov z nej nemožno priamo tvoriť inštancie – napríklad v našom príklade by tieto inštancie „nevedeli, čo robiť” pri volaní metódy area() – ale za účelom volania z podtried môže obsahovať definície konštruktorov. Abstraktná trieda slúži iba na dedenie a ako taká nemôže byť typom žiadneho objektu. Stále však môže byť typom referencie na objekt.
  • Podtriedy abstraktnej triedy, ktoré nie sú abstraktné, musia implementovať všetky abstraktné metódy svojho predka.

Príklad:

public abstract class Shape {
    // ...
    
    public abstract double area();

    public long approximateArea() {
        return Math.round(area());
    }
}
public class Rectangle extends Shape {
    // ...
    
    @Override
    public double area() {
        return width * height;
    }
}
public class Circle extends Shape {
    // ...
    
    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}
public class Segment extends Shape {
    // ...
    
    @Override
    public double area() {
        return 0;
    }
}

Napríklad program

public static void main(String[] args) {
    Shape[] shapes = new Shape[3];
    shapes[0] = new Rectangle(0, 0, 1, 2);
    shapes[1] = new Circle(0, 0, 1);
    shapes[2] = new Segment(1, 1, 2, 2);
    
    for (Shape x : shapes) {
        System.out.println(x.area() + " " + x.approximateArea());
    }
}

potom vypíše nasledujúci výstup:

2.0 2
3.141592653589793 3
0.0 0

Hierarchia tried a trieda Object

  • V Jave môže každá trieda dediť iba od jednej triedy (na rozdiel od niektorých iných jazykov, kde je možné dedenie od viacerých tried).
  • Dedenie je však možné „viacúrovňovo”:
class Pes extends Zviera {
}

class Civava extends Pes { // Hierarchia tried nemusi verne zodpovedat realite
}
  • Všetky triedy sú automaticky potomkami triedy Object; tá sa tiež považuje za priamu nadtriedu tried, ktoré explicitne nerozširujú žiadnu triedu.
  • Trieda Object obsahuje metódy – napríklad toString alebo equals – ktoré je často užitočné prekrývať.
  • To vysvetľuje, prečo sme pri metóde toString triedy Circle použili anotáciu Override: prekryli sme totiž jej definíciu z triedy Object.
  • Vypisovanie kružnice Circle circle cez System.out.println(circle) je zas ukážkou polymorfizmu. Ide tu o použitie verzie metódy println, ktorá ako argument očakáva inštanciu triedy Object a na výpis používa metódu toString tejto inštancie. V prípade, že metódu println zavoláme pre inštanciu podtriedy triedy Object, použije sa pri výpise prekrytá verzia metódy toString.
  • Veľmi špeciálnym druhom objektov v Jave sú polia, pričom polia typu T (kde T je trieda alebo primitívny typ) sa považujú za inštancie triedy T[], ktorá je podtriedou triedy Object. Aj na polia teda v princípe možno aplikovať metódu toString triedy Object, avšak od jej použitia nemožno očakávať žiadne rozumné správanie, keďže nebola zmysluplným spôsobom prekrytá.

Rozhrania

Rozhranie (angl. interface) je podobným konceptom ako abstraktná trieda. Existuje však medzi nimi niekoľko rozdielov, z ktorých najpodstatnejšie sú tieto:

  • Rozhranie slúži predovšetkým ako zoznam abstraktných metód – kľúčové slovo abstract tu netreba uvádzať. Pri triedach implementujúcich rozhranie je garantované, že na prácu s nimi bude možné použiť metódy deklarované v rozhraní (odtiaľ aj termín „rozhranie”). Napríklad rozhranie pre zásobníky by mohlo deklarovať metódy ako push, pop a isEmpty a triedy pre zásobníky implementované pomocou polí resp. spájaných zoznamov by toto rozhranie mohli implementovať.
  • Naopak implementované metódy musia byť v rozhraní označené kľúčovým slovom default (čo sa však typicky využíva iba vo veľmi špeciálnych situáciách), prípadne musia byť statické.
  • Rozhranie nemôže definovať konštruktory, ani iné ako finálne premenné (t. j. konštanty).
  • Kým od tried sa dedí pomocou kľúčového slova extends, rozhrania sa implementujú pomocou kľúčového slova implements. Rozdiel je predovšetkým v tom, že implementovať možno aj viacero rozhraní. Jedno rozhranie môže navyše rozširovať iné (dopĺňať ho o ďalšie požadované metódy): v takom prípade používame kľúčové slovo extends.
  • Všetky položky v rozhraní sa chápu ako verejné (modifikátor public teda nie je potrebné explicitne uvádzať).
  • Podobne ako abstraktná trieda, môže byť aj rozhranie typom referencie.
  • Hoci nejde o prekrývanie v pravom slova zmysle, aj pri implementovaní metód z rozhraní používame anotáciu @Override.

Príklad použitia:

public interface Movable {
    void move(double deltaX, double deltaY);
}
public interface Measurable {
    double area();
    long approximateArea();
}
public abstract class Shape implements Movable, Measurable {
    // ...

    @Override
    public void move(double deltaX, double deltaY) {
        x += deltaX;
        y += deltaY;
    }

    @Override
    public abstract double area(); // Deklaracia tejto abstraktnej metody sa stava nepotrebnou

    @Override
    public long approximateArea() {
        return Math.round(area());
    }

    // ...
}
    public static void main(String[] args) {
        Measurable[] elements = new Shape[3];  // Podobne ako abstraktne triedy mozu byt aj rozhrania typmi referencie
        elements[0] = new Rectangle(0, 0, 1, 2);
        elements[1] = new Circle(0, 0, 1);
        elements[2] = new Segment(1, 1, 2, 2);

        for (Measurable element : elements) {
            System.out.println(element.area() + " " + element.approximateArea());
        }
    }

Prehľad niektorých modifikátorov tried, premenných a metód

Modifikátory prístupu:

  • public: triedy, rozhrania a ich súčasti prístupné odvšadiaľ.
  • (žiaden modifikátor): viditeľnosť len v rámci balíka (package).
  • protected: viditeľnosť v triede, jej podtriedach a v rámci balíka (len pre premenné, metódy a konštruktory; nedá sa aplikovať na triedu samotnú).
  • private: viditeľnosť len v danej triede (len pre premenné, metódy a konštruktory; nedá sa aplikovať na triedu samotnú).

Iné modifikátory:

  • abstract: neimplementovaná metóda alebo trieda s neimplementovanými metódami.
  • final:
    • Ak je trieda final, nedá sa z nej ďalej dediť.
    • Ak je metóda final, nedá sa v podtriede prekryť.
    • Ak je premenná alebo parameter final, ide o „konštantu”, ktorú nemožno meniť (možno ju ale inicializovať aj za behu).
  • static:
    • Statické premenné a metódy príslušia triede samotnej, nie jej inštanciám.
    • Statické triedy vo vnútri inej triedy nie sú viazané na jej konkrétnu inštanciu (viac neskôr).

Aritmetický strom s využitím dedenia

V minulom semestri ste boli upozornení na návrhový nedostatok pri realizácii aritmetického stromu: niektoré položky uložené v štruktúrach reprezentujúcich uzly takýchto stromov sa využívali len pre určité druhy uzlov (hodnoty iba v listoch a operátory iba vo vnútorných uzloch). Tomuto sa vieme vyhnúť pomocou dedenia.

  • Jednotlivé typy uzlov budú podtriedy abstraktnej triedy Node.
  • Namiesto použitia príkazu switch na typ uzla tu prekryjeme potrebné metódy, napríklad evaluate.
abstract class Node {
    public abstract int evaluate();
}
abstract class NullaryNode extends Node {
}
abstract class UnaryNode extends Node {
    private Node child;
    
    public Node getChild() {
        return child;
    }

    public UnaryNode(Node child) {
        this.child = child; 
    }
}
abstract class BinaryNode extends Node {
    private Node left;
    private Node right;

    public Node getLeft() {
        return left;
    }

    public Node getRight() {
        return right;
    }

    public BinaryNode(Node left, Node right) { 
        this.left = left;
        this.right = right; 
    }
}
class Constant extends NullaryNode {
    private int value;
    
    public Constant(int value) { 
        this.value = value;
    }

    @Override 
    public int evaluate() { 
        return value;
    }

    @Override
    public String toString() { 
        return Integer.toString(value);
    }
}
class UnaryMinus extends UnaryNode {
    public UnaryMinus(Node child){
        super(child); 
    }

    @Override 
    public int evaluate() { 
        return -getChild().evaluate();
    }

    @Override 
    public String toString() { 
        return "(-" + getChild().toString() + ")";
    }
}
class Plus extends BinaryNode { 
    public Plus(Node left, Node right) { 
        super(left, right);
    }

    @Override
    public int evaluate() { 
        return getLeft().evaluate() + getRight().evaluate();
    }

    @Override 
    public String toString() { 
        return "(" + getLeft().toString() + "+" + getRight().toString() + ")";
    }
}
public class Expressions {

    public static void main(String[] args) {
        Node expr = new Plus(new UnaryMinus(new Constant(2)),
			     new Constant(3));
        System.out.println(expr);
        System.out.println(expr.evaluate());
    }
}

Odkazy