Programovanie (2) v Jave
1-INF-166, LS 2017/18

Úvod · Pravidlá · Prednášky · Netbeans · Testovač · Test a skúška
· Vyučujúcich môžete kontaktovať 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).
· DÚ10 je zverejnená, odovzdávajte do stredy 16.5. 22:00.
· Bonusový projekt odovzdávajte do pondelka 21.5. 22:00 na testovači. Predvádzanie projektov bude po prvom termíne skúšky v stredu 23.5. (t.j. začneme medzi 11:45 a 12:00 v H6). Ak vtedy nemôžete prísť, kontaktujte vyučujúce, dohodneme iný termín.
· Opravný/náhradný test bude v pondelok 28.5. o 10:00 v F1-108. V prípade záujmu sa prihláste v AISe.
· Na termíny skúšok sa zapisujte v AIS. Termíny: streda 23.5. (riadny), štvrtok 7.6. (riadny alebo 1. opravný), streda 20.6. (1. alebo 2. opravný) plus v prípade potreby ešte jeden termín v poslednom týždni skúškového. Prípadné konflikty nám dajte vedieť čím skôr. Ďalšie informácie na stránke Test a skúška.


Prednáška 26

Z Programovanie
Prejsť na: navigácia, hľadanie

Organizačné poznámky

  • Test pre pokročilých bude v stredu 28.2. 11:30-13:00 v M-V.
  • DU5 odovzdávajte do stredy 28.2. 22:00. Nová DU6 má termín odovzdania 14.3., ale nenechávajte si ju na poslednú chvíľu.
  • Na cvičení 28.2. bude prvá rozcvička.

Objektovo orientované programovanie (OOP)

  • Java je objektovo-orientovaný jazyk a teda skoro všetko v Jave je objekt
  • Základným pojmom OOP je trieda (class)
    • Trieda je typ združujúci niekoľko hodnôt, podobne ako struct v C
    • Navyše ale trieda obsahuje metódy (funkcie), ktoré s týmito hodnotami pracujú
  • Objektyinštancie triedy
    • Napríklad trieda Zlomok môže mať položky citatel a menovatel a konkrétnou inštanciou, objektom je napríklad zlomok z čitateľom 2 a menovateľom 3 vytvorený v programe

Napríklad v Cčku by jednoduchý zásobník int-ov implementovaný pomocou poľa a funkcia pop, ktorá z neho vyberie prvok, mohli vyzerať takto:

struct Stack {
    int *data;
    int pocet;
};

int pop(Stack &s) {
    s.pocet--;
    return s.data[s.pocet];
}       

Keď to prepíšeme ako triedu v Jave, vyzerá to podobne, ale:

  • slovo struct sa nahradí slovom class
  • metóda pop sa presunie do vnútra definície triedy
  • metóda pop nedostane zásobník ako argument a k jeho položkám pristupuje priamo ich menami, t.j. napr. data a pocet
public class Stack {
    int data[];
    int pocet;

    int pop() {
       pocet--;
       return data[pocet];
    }
}       

Metódy sa potom volajú pre konkrétny zásobník, napr.

Stack s;
// tu pridu prikazy na vytvorenie a naplnenie zasobnika
int x = s.pop()  // vyberie prvok zo zasobnika s

V Cčku by sme písali

Stack s;
// tu pridu prikazy na vytvorenie a naplnenie zasobnika
int x = pop(s);

Ak máme premennú s typu Stack, k jej premenným a metódam pristupujeme pomocou operátora .

  • napr. s.pop(), s.pocet
  • Java nemá operátor ->
  • Ale pozor, premenná s typu Stack je referencia
  • Po príkaze Stack t = s; premenné s a t ukazujú na to isté miesto v pamäti, na ten istý zásobnik
  • Čo by podobný príkaz spravil v Cčku? V tomto prípade asi nie to, čo chceme...

Konštruktor a vznik objektov

V Cčku sme pre zásobník mali metódu init, ktorá inicializovala hodnoty pre prázdný zásobník, napr. takto:

void init(Stack &s) {
    s.data = new int[MAXN];
    s.pocet = 0;
}

Objekty sa inicializujú špeciálnou metódou, konštruktorom

  • Názov konštruktora je názov triedy. Teda konštruktor triedy Stack bude metóda Stack()
  • Konštruktor nemá v hlavičke návratovú hodnotu, môže však mať parametre.
public class Stack {
    Stack() {
        data = new int[MAXN];
        pocet = 0;
    }
    ...
}

Príkaz Stack s; vytvorí referenciu s, ktorá je však zatiaľ neinicializovaná, t.j. nikam neukazuje a Java nám ju nedovolí použiť.

  • mohli by sme ju nastaviť na null

Na vytvorenie nového objektu použijeme príkaz new:

s=new Stack();

Príkaz new

  • dynamicky alokuje pamäť pre objekt
  • zavolá konštruktor objektu
  • vráti referenciu - pamäťovú adresu objektu

Viac detailov neskôr

Kontrola prístupu, modifikátory

Trieda a jej súčasti môžu byť odniekiaľ prístupné a odinakiaľ nie. Na úpravu prístupových práv používame modifikátory.

  • modifikátor private: premenná/metóda je prístupná iba z metód príslušnej triedy
  • keď nepoužijeme modifikátor: trieda/premenná/metóda je prístupná z balíčka (package), kde sa nachádza
  • modifikátor protected: podobne ako bez modifikátora, rozdiel uvidíme pri dedení
  • modifikátor public: trieda/premenná/metóda je prístupná ľubovoľne

Mená súborov, main:

  • public trieda musí byť v súbore nazvanom po tejto triede, ale môžu tam s ňou byť aj ďalšie (pomocné) triedy, ktoré nie sú public
  • spustiteľná metóda main musí byť public a umiestnená v public triede

O ďalších modifikátoroch, napr. abstract, static, final, sa dozvieme neskôr

Zapuzdrenie (encapsulation)

  • Jedným z hlavných princípov OOP je zapuzdrenie
  • Dáta a k nim prislúchajúce metódy zabalíme do triedy
  • Kód mimo triedy by k dátam objektu mal pristupovať iba pomocou poskytnutých metód
  • Väčšinou teda premenným nastavíme modifikátor private alebo protected a pomocným metódam tiež
  • public metódy triedy tvoria našu ponuku pre používateľov triedy
  • ak zmeníme vnútornú implementáciu triedy, ale zanecháme rovnaké public metódy a ich správanie, používateľov triedy by to nemalo ovplyvniť
  • napríklad v triede Stack sa môžeme rozhodnúť namiesto poľa použiť spájaný zoznam, čím potrebujeme preprogramovať triedu Stack, ale program, ktorý ju používa, sa meniť nemusí
  • Zapuzdrenie umožňuje rozdeliť väčší projekt na pomerne nezávislé časti s dobre definovaným rozhraním
public class Stack {
    public static final int MAXN = 100;
    private int data[];
    private int pocet;

    public Stack() {
        data = new int[MAXN];
        pocet = 0;
    }

    public int pop() {
        pocet--;
        return data[pocet];
    }
    public void push(int x) {
	data[pocet] = x;
        pocet++;
    }
    public boolean isEmpty() {
        return pocet==0;
    }
}

Get a set metódy

Nakoľko premenné v triedach sú väčšinou private, niektoré triedy ponúkajú nepriamy prístup cez get a set metódy, napr.

class Contact {
   private String name;
   private String email;
   private String phone;
   public String getName() { return name; }
   public String getEmail() { return email; }
   public void setEmail(String newEmail) { email = newEmail; } 
   public String getPhone() { return phone; }
   public void setPhone(String newPhone) { phone = newPhone; } 
}
  • get a set metódy nerobíme mechanicky pre všetky premenné, iba pre tie, ktoré je rozumné sprístupniť mimo triedu
  • ak poskytneme iba get metódu, premenná je zvonku v podstate read-only
  • v set metódach môžeme kontrolovať, či je zadaná hodnota rozumná (napr. menovateľ zlomku nemá byť 0)
  • get a set metódy nemusia presne korešpondovať s premennými a teda môže sa nám podariť ich zachovať aj po zmene vnútornej reprezentácie
    • napr. ak getAngle a setAngle berú uhol v stupňoch, ale rozhodneme sa ho ukladať radšej v radiánoch, môžeme do týchto metód naprogramovať konverziu
class SomeGeometricObject {
   private double angle;  // uhol v radianoch
   public double getAngle() { return angle*180.0/Math.PI; }
   public void setAngle(double x) { angle = x*Math.PI/180.0; }
}

Ďalšie detaily

Premenná this

V rámci metód triedy premenná this je referencia na konkrétny objekt, na ktorom bola metóda zavolaná.

Napr. ak zavoláme s.pop(), tak vo vnútri metódy pop premenná this ukazuje na s.

  • this.premenna je to isté ako premenna
  • this.metoda(...) to isté ako metoda(...)

Jedno využitie this je poslanie objektu ako argumentu inej metóde, napr.

public static emptyStack(Stack s) {
     while(!s.empty()) {
         s.pop();
     }
}

V triede Stack potom môžeme mať napr. metódu

public empty() {
  emptyStack(this);
}

Samozrejme logickejšie by bolo naprogramovať vyprázdnenie zásobníka priamo v triede a nie volať externé metódy.

Premenná this sa tiež hodí, ak sa argument metódy volá rovnako ako premenná triedy. Vtedy sa pomocou this vieme dostať k premennej a bez this k argumentu

class Contact {
   private String email;
   /** nastav novú emailovú adresu */
   public void setEmail(String email) {  
       this.email = email;
   }
}

Viac metód s tým istým menom: overloading

Trieda môže mať niekoľko metód s tým istým menom, ale rôznymi typmi alebo počtom parametrov. Kompilátor vyberie tú, ktorá sa najlepšie hodí použitiu. Napr.

class Contact {
   private String email;
   public void setEmail(String email) {  
       this.email = email;
   }
   public void setEmail(String username, String domain) {  
       email = username + "@" + domain;
   }
}


Contact c = new Contact();
c.setEmail("jozkomrkvicka@gmail.com"); // prva metoda
c.setEmail("jozkomrkvicka", "gmail.com"); // druha metoda

Overloading sa dá použiť aj na konštruktory:

class Node {
    private int data;
    private Node next;
    
    public Node() {}
    public Node(int data) { this.data = data; }
    public Node(Node next) { this.next = next; }
    public Node(int data, Node next) { this.data = data; this.next = next;}
    
    public int getData() { return data;}
    public void setData(int data) { this.data = data;}
    public Node getNext() { return next;}
    public void setNext(Node next) {this.next = next;}
}

Detaily inicializácie objektov

  • príkaz new najskôr inicializuje jednotlivé premenné (na 0, false, null) alebo na hodnotu, ktorú zadáme
class Node {
  private int data = -1;
  private Node next;  // bude inicializovany na null
}
  • až potom spúšťa konštruktor
  • prvý riadok konštruktora môže volať iný konštruktor tej istej triedy pomocou this(...) - často s menším alebo väčším počtom parametrov
class Node {
    private int data;
    private Node next;

    public Node(int data, Node next) { this.data = data; this.next = next;}
    public Node(int data) { this(data, null) }
    ...
}
  • V prípade, že nedefinujeme pre triedu žiaden konštruktor, bude automaticky vygenerovaný konštruktor bez parametrov
    • tento inicializuje premenné na defaultné hodnoty
    • defaultný konštruktor je vytvorený iba ak žiaden iný konštruktor neexistuje.
  • Ďalšie detaily na prednáške o dedení

Nie všetko v Jave je objekt

Opakovanie:

  • Ako sme videli na minulej prednáške, každá premenná obsahuje buď hodnotu primitívneho typu (int, double, bool, char a pod) alebo referenciu
  • Referencia môže ukazovať na objekt alebo pole
  • Pole môže obsahovať primitívne typy alebo referencie na iné objekty/polia

Wrapper

  • Ku každému primitívnemu typu existuje aj zodpovedajúca trieda (wrapper), napr. Integer, Double, ... (viac pozri [1])
  • Java medzi primitívnymi typmi a týmito triedami podľa potreby automaticky konvertuje (viac neskôr)

Polia

  • Polia sú špeciálny typ objektov, viď napr. premennú a.length, ale aj ďalšie metódy (neskôr)

Javadoc

  • Javadoc je systém na vytváranie dokumentácie
  • Javadoc komentár začína /** a končí ako klasický komentár */ pričom každý riadok začína *
  • Javadoc komentáre sa umiestnia pred triedu, premennú alebo metódu, ktorú chceme popísať
  • Prvý riadok Javadoc komentára resp. po prvú bodku je stručný slovný popis. Ďalej pokračujú rôzne podrobnosti.
  • Javadoc poskytuje rôzne tag-y [2]

Program Javadoc vie na základe kódu a Javadoc komentárov vygenerovať dokumentáciu (napr. v html formáte)

  • dá sa spustiť cez Netbeans v časti Run, Generate Javadoc
  • automaticky vytvára dokumentáciu iba k public položkám (keďže tie tvoria rozhranie, API k iným triedam)
  • vo vlastnostiach aplikácie časť Documenting sa dá nastavovať

Viď príklad Javadocu v triede nižšie.

Binárny vyhľadávací strom

Príklad binárneho vyhľadávacieho stromu s pomocnou triedou Node a triedou BinarySearchTree.

  • Trieda Node obsahuje pomocné metódy a rekurzívne funkcie
  • Trieda BinarySearchTree skrýva tieto implementačné detaily pred používateľom, pričom ponúka možnosť pridať prvok a vypísať všetky prvky v utriedenom poradí
/** Trieda reprezentujúca jeden vrchol binárneho vyhľadávacieho stromu.
 * Každý vrchol v strome obsahuje dáta typu int a referenciu na ľavý a
 * pravý podstrom. Pre každý vrchol platí, že že všetky vrcholy v jeho
 * ľavom podstrome majú hodnotu menšiu ako on a všetky vrcholy v
 * pravom podstrome väčšiu.
 *
 * @author Jana Katreniaková a Broňa Brejová
 */
class Node {
    /** Dáta typu int uložené vo vrchole */
    private int data;
    /** Referencia na ľavé dieťa alebo null ak neexistuje */
    private Node left;
    /** Referencia na pravé dieťa alebo null ak neexistuje */
    private Node right;

    /** Konštruktor, ktorý vytvorí nový list
     * so zadanou hodnotou <code>data</code>.
     * @param data Dáta uložené v novom vrchole.
     */
    public Node(int data) {
        this.data = data;
    }

    /** Metóda vráti dáta uložené vo vrchole.
     * @return  dáta uložené vo vrchole  */
    public int getData() {
        return data;
    }

    /** Metóda, ktorá do stromu vloží nový vrchol <code>newNode</code>.
     *
     * @param newNode Nový vrchol vložený do stromu. Mal by byť listom.
     */
    public void addNode(Node newNode) {
        if (newNode.data <= this.data) {
            if (left == null) {
                left = newNode;
            }
            else {
                left.addNode(newNode);
            }
        }
        else {
            if (right == null) {
                right = newNode;
            }
            else {
                right.addNode(newNode);
            }
        }

    }

    /** Metóda, ktorá vypíše hodnoty uložené vo vrcholoch podstromu
     * v inorder poradí, každý na jeden riadok. */
    public void printInorder() {
        if (this.left != null) left.printInorder();
        System.out.println(data);
        if (this.right != null) right.printInorder();
    }
}

/** Trieda reprezentujúca binárny vyhľadávací strom, ktorý má v každom
 * vrchole dáta typu int. Strom umožňuje pridávať nové dáta a
 * vypísať dáta v utriedenom poradí.
 *
 * @author Jana Katreniaková a Broňa Brejová
 */
public class BinarySearchTree {
    /** Premenná obsahujúca koreň stromu, alebo null, ak je strom prázdny. */
    private Node root;

    /** Konštruktor vytvorí prázdny strom. */
    public BinarySearchTree() {
    }

    /** Metóda do stromu pridá novú hodnotu <code>data</code>.
     * Malo by ísť o hodnotu, ktorá sa ešte v strome nenachádza.
     * @param data Nová hodnota pridaná do stromu.
     */
    public void add(int data) {
        Node p = new Node(data);
        if (root == null) {
            root = p;
        } else {
            root.addNode(p);
        }
    }

    /** Metóda vypíše všetky hodnoty v strome v utriedenom poradí,
     * každú na jeden riadok. */
    public void printSorted() {
        if (root != null) {
	    root.printInorder();
	}
    }

    /** Metóda je ukážkou použitia binárneho vyhľadávacieho stromu.
     * Do stromu vloží tri čísla a potom ich vypíše. */
    public static void main(String args[]) {
        BinarySearchTree t = new BinarySearchTree();
        t.add(2);
        t.add(3);
        t.add(1);
        t.printSorted();
    }
}

Pomocné triedy

Nakoniec dva typy pomocných tried, ktoré môžeme použiť na obídenie obmedzení javovských funkcií (metód).

Odovzdávanie parametrov hodnotou

  • Všetky parametre sa v Jave odovzdávajú hodnotou - teda vytvorí sa lokálna kópia parametra a jej zmenou nedocielime zmenenie pôvodnej premennej
  • Ak je ale parametrom referencia, nakopíruje sa adresa a môžeme teda meniť obsah pamäte, kam ukazuje
  • Ak by sme teda parameter chceli meniť, podobne ako pri odovzdávaní premenných referenciou v C, môžeme si vytvoriť wrapper class, ktorý danú hodnotu obalí a umožní k nej pristúpiť cez referenciu
  • Knižničné wrapper triedy ako Integer nemôžeme použiť, lebo tie tiež neumožňujú meniť hodnotu už vytvoreného objektu
  class MyInteger {
   private int x;                   // data
   public MyInteger(int x) { this.x = x; } // konštruktor
   public int getValue() { return x; }  // získanie hodnoty
   public void setValue(int x) { this.x = x;} // nastavenie hodnoty
  }
  static void swap(MyInteger rWrap, MyInteger sWrap) {
    // interchange values inside objects
   int t = rWrap.getValue();
   rWrap.setValue(sWrap.getValue());
   sWrap.setValue(t);
  }

Návratová hodnota

Návratová hodnota metódy je buď void, základný typ alebo referencia

  • ak teda chceme vrátiť niekoľko hodnôt, musíme si spraviť triedu, ktorá ich spája do jedného celku
   static class MyReturn {
      public int x;                   // data
      public char c; 
      public MyReturn(int x, char c) { this.x = x; this.c = c; }
   }

  static MyReturn funkcia(int x, char c){
      MyReturn temp=new MyReturn(x, c);
      return temp;
  }

  public static void main(String[] args) {
      MyReturn temp = funkcia(7,'a');   
      System.out.println(temp.x+" "+temp.c);
  } 

V oboch prípadoch je ale lepšie skúsiť navrhnúť metódy tak, aby neboli takéto pomocné triedy potrebné.