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 30

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

Prednáška 30

Opakovanie OOP

  • Java je objektovo orientovaný jazyk - všetko sú objekty
  • Program pozostáva z objektov, ktoré navzájom 'komunikujú' prostredníctvom svojich metód

Objekty, zapúzdrenie

  • Trieda (class) definícia objektu
class MyClass {
premenné
konštruktory
metódy
}
  • Konštruktor - vytváranie objektu
    • používanie this(..) resp. super(..) na volanie iného konštruktora tej istej triedy, resp. nadtriedy
    • Pes punto=new Pes("Punto");
  • Úrovne prístupu - Modifikátory
  • Zapuzdrenie - oddelenie rozhrania a implementácie
    • Nemení sa to, čo je viditeľné zvonku
    • Pozor na prístup k premenným

Dedenie objektov, polymorfizmus

  • Trieda môže byť potomkom inej triedy - potom zdedí jej premenné a metódy
    • Dediť môžeme iba od jednej triedy (ale môžeme viacúrovňovo)
    • Triedy, ktoré nemajú predchodcu, dedia všeobecnú triedu Object
    • Aj primitívne typy premenných majú svoje objektové varianty (Integer, Boolean atd)
  • Metódy je možné preťažiť - potomok tým prepíše chovanie metódy
  • Potomka je vždy možné použiť na mieste predka, keďže má všetky jeho 'vlastnosti' - spolu s preťaženými metódami to umožňuje polymorfizmus

Interface

  • Abstraktná trieda, ktorá nemá žiadnu metódu implementovanú
    • Trieda môže implementovať interface - musí implementovať všetky jej metódy
  • Umožňuje "viacnásobnú dedičnosť" a dobré zapuzdrenie

Generické programovanie

  • Ako mať rôzne typy premenných vnútri nejakej štruktúry (napr. zásobník)
    • Bude skladovať Object a keďže potomka môžeme použiť namiesto predka môžeme tam skladovať čokoľvek - musíme pretypovávať
    • Generické programovanie: parametrický typ class Stack <T>, kde T je parameter reprezentujúci typ objektov, pri definícii povieme konkrétny typ
  • Aj metódy môžu mať generické typy - typový parameter, ktorý sa píše pred návratový typ
   static <E> void fillArray(E[] a, E e)
  • Generické funkcie (napr. triedenie) s využitím interface
   static <E extends Comparable> void sort(E[] a)

Testovanie programov

  • Cieľom testovania je nájsť chyby v programe, teda preukázať, že program nefunguje podľa špecifikácie (aby sme potom vedeli chybu nájsť a opraviť)
  • Test pozostáva zo vstupu, správneho výstupu a popisu jeho významu
  • Program sa spustí na vstupe a jeho výsledok sa porovná so správnou odpoveďou
  • Tradičný prístup: najprv sa napíše kód, potom sa vytvárajú testy
  • Test-driven development: najprv sa napíšu testy, potom sa programuje kód, ktorý ich dokáže splniť

Black-box testing

  • Vytvorenie sady testov len na základe špecifikácie
  • Snažíme sa zachytiť okrajové aj typické prípady
  • Napr.:
    /** Z pola a vyhodi prvy vyskyt objektu rovneho x
     * pricom rovnost sa testuje metodou equals.
     * Vsetky dalsie prvky posunie o jedno dolava a na koniec
     * pola da null.
     * Vrati true, ak bolo pole modifikovane, inak false.
     * Ak a je null alebo x je null, vyhodi java.lang.NullPointerException
     */
    public static boolean remove(Object[] a, Object x) {

Príklady testov:

  • prázdne pole a
  • pole obsahujúce iba x
  • pole obsahujúce x na začiatku
  • pole obsahujúce x na konci
  • pole obsahujúce x v strede
  • pole obsahujúce viacero kópií x
  • pole obsahujúce null prvky
  • pole obsahujúce objekty rôznych typov

Podrobnejšie rozpísanie jedného testu:

  • vstup: a = {1,2,3}, x = 1
  • výstup: a = {2,3,null}, návratová hodnota true
  • význam testu: testovanie prípadu, keď pole a obsahuje x na začiatku

White-box testing

Testy vytvárame na základe kódu, snažíme sa preveriť všetky vetvy výpočtu

  • V cykle vyskúšame 0 iterácií, 1 iteráciu, maximálny počet iterácií
  • V podmienke vyskúšame vetvu true aj false
  • Avšak tým, že sa sústredíme na kód, môžeme vynechať prípady, na ktoré sa v ňom nemyslelo

Tento kód nespĺňa úplne špecifikáciu:

    /** Z pola a vyhodi prvy vyskyt objektu rovneho x
     * pricom rovnost sa testuje metodou equals.
     * Vsetky dalsie prvky posunie o jedno dolava a na koniec
     * pola da null.
     * Vrati true, ak bolo pole modifikovane, inak false.
     * Ak a je null alebo x je null, hodi java.lang.NullPointerException
     */
    public static boolean remove(Object[] a, Object x) {
        int i;
        for (i = 0; i < a.length; i++) {
            if (a[i].equals(x)) {
                break;
            }
        }
        if (i == a.length) {
            return false;
        }
        while (i < a.length - 1) {
            a[i] = a[i + 1];
            i++;
        }
        a[i] = null;
        return true;
    }

JUnit

  • Systém JUnit umožňuje vytvárať špeciálne triedy obsahujúce testy iných tried
  • Sadu testov môžeme ľahko automaticky spustiť a vyhodnotiť, vidíme všetky výsledky
  • Môžeme program testovať po každej zmene
  • Dobrá podpora v Netbeans
  • Krátky návod: [1]

Príklad niekoľkých testov pre funkciu remove vyššie:

package prog;

import org.junit.Test;
import static org.junit.Assert.*;

public class ProgTest {

    @Test
    public void testEmpty() { 
        // hladame x v poli dlzky nula
        Object[] working = new Object[0]; // vstupne pole
        Object[] correct = new Object[0]; // spravna odpoved
        Object x = new Object();
        boolean result = Prog.remove(working, x); // spustime testovanu metodu
        assertEquals(result, false);              // testujeme navratovu hodnotu
        assertTrue(java.util.Arrays.equals(working,correct)); // testujeme pole
    }

    @Test
    public void testXOnly() {
        // hladame x v poli obsahujucom iba x
        Object[] working = {7};
        Object[] correct = {null};
        Object x = 7;
        boolean result = Prog.remove(working, x);
        assertEquals(result, true);
        assertTrue(java.util.Arrays.equals(working,correct));
    }

    @Test(expected = java.lang.NullPointerException.class)
    public void testANull() {
        // Testujeme, ci hodi vynimku ked je pole null
        Object[] working = null;
        Object x = 7;
        boolean result = Prog.remove(working, x);        
    }
}

Tento príklad je možné rôzne vylepšovať

  • Opakujúce sa časti kódu môžeme dať do pomocných metód
  • Môžeme pridať výpisy výsledkov, aby sme v prípade chyby videli, čo sa stalo.
  • Môžeme pridať premenné triede ProgTest, konštruktor, ako aj špeciálne metódy, ktoré sa vykonajú pred každým testom, prípadne po každom teste.

JavaFX

  • Doteraz sme mali aplikácie, ktoré pracovali s textovým vstupom a výstupom - konzolové aplikácie
  • Ukážeme si jednoduché aplikácie, ktoré majú grafické UI
    • Grafické okno, ovládacie prvky, ...
    • Udalosti, ktoré vedú k akciám

Program s jedným grafickým oknom

V klasickej java aplikácii (ako sme používali doteraz) aplikáciu urobiť nedokážeme, musíme dopredu povedať, že ideme robiť grafickú aplikáciu.

  • File–>New Project vyberieme kategóriu JavaFX a projekt typu JavaFX Application
  • Projekt si môžeme pre jednoduchosť nazvať "HelloWorld"
  • Bude automaticky vygenerovaný kód, ktorý obsahuje
    • hlavičku a niekoľko (veľa) importovaných knižníc
    • triedu public class HelloWorld extends Application s metódou start (obsahuje nejaký kód, ktorý na začiatok zmažeme, neskôr si ho môžete nechať a upravovať) a metódou main, ktorá je skoro prázdna a tak aj ostane

Naša metóda start bude na začiatok obsahovať iba nasledovný kód

    @Override
    public void start(Stage primaryStage) {
        primaryStage.setTitle("Hello World!");
        primaryStage.show();
    }
  • Takto vytvorený program vytvorí grafické okno a zobrazí ho
  • Hlavné okno je primaryStage, ktorému nastavíme nadpis (setTitle) a zobrazíme ho - zistíme však, že okno sa zobrazí prázdne alebo sa vôbec nezobrazí (v závislosti od OS), pretože nevie, čo v ňom zobraziť
  • Chceme aspoň nejaký prázdny panel - pri starších grafických programoch existovali rôzne Form, ktoré nevyžadovali viac informácií, v JavaFX potrebujeme každému panelu nastaviť aj spôsob, akým má byť zobrazovaný a nezobrazujeme priamo panel ale toto zobrazenie


    @Override
    public void start(Stage primaryStage) {
        Pane pane = new Pane();             //panel
        Scene scene = new Scene(pane, 300, 250);      //jeho zobrazenie
 
        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);                 //primarnemu oknu povieme, že má zobraziť zobrazenie panelu
        primaryStage.show();
    }

Text v okienku

  • Do takto vytvoreného okna si môžeme vytvárať grafické (ovládacie) prvky label, button, ...
    • pochopiteľne pre tieto prvky má JavaFX vlastné triedy, ktorých inštancie vytvárame
  • Pre jednoduchosť začneme textovým popiskom - label, ktorý je inštanciou Label
    • Label je needitovateľný text, ktorý sa používateľovi objaví na paneli
    Label label = new Label("Label"); 
    pane.getChildren().add(label);
  • Ďalej môžeme s týmto popiskom pracovať (napríklad mu zmeniť text)
  • Môžeme tiež umiestniť label na iné miesto label.setLayoutX(100); label.setLayoutY(100);

Grafické objekty

  • V JavaFX síce existuje priamo Canvas, ale je určený čisto na vykresľovanie.
  • Ku grafickým objektom sa však môžeme chovať ako k ľubovoľnému inému prvku - napríklad textu a umiestniť ho na iné komponenty napríklad na rôzne typy Pane.
    • Trieda Node (čiže objekty, ktoré je možné ukladať na panely) obsahuje aj podtriedu Shape -- geometrické útvary ako Arc, Circle, CubicCurve, Ellipse, Line, Path, Polygon, Polyline, QuadCurve, Rectangle, SVGPath, Text. Tieto teda jednoducho vložíme na panel a o ich vykreslenie sa nám stará aplikácia podobne ako o texty, gombíky a podobné objekty.
@Override public void start(Stage primaryStage) {
    Pane pane = new Pane();
      
    Rectangle square=new Rectangle();
    square.setHeight(10); square.setWidth(10);
    square.setX(10);      square.setY(10);
    
    pane.getChildren().add(square);
    
    Scene scene = new Scene(pane, 300, 250);
    primaryStage.setTitle("Drawing a square");
    primaryStage.setScene(scene);
    primaryStage.show();
}

Button a jeho stlačenie

  • Doteraz sme síce mali grafické okno, ale nemali sme (okrem zavretia) žiadnu interakciu s používateľom
  • Ukážeme si najjednoduchší ovládací prvok - Button a jednoduchú akciu - stlačenie gombíka
    • Button pridáme podobne ako Label pomocou pane.getChildren().add()
    • Stlačenie gombíka je akcia, ktorá sa môže alebo aj nemusí stať - záleží od používateľa - preto na ňu musíme čakať a v prípade, že nastane nejako ju spracovať
    • Použijeme na to interface, ktorý implementujeme anonymnou triedou. V našom prípade potrebujeme interface EventHandler<ActionEvent>, ktorý má jedinú funkciu handle() s parametrom ActionEvent, ktorý hovorí parametre tej udalosti (na ktorom objekte nastalo a nejaké bližšie informácie)
package helloworld;
 
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.*;
import javafx.stage.Stage;
 
public class HelloWorld extends Application {
 
    @Override
    public void start(Stage primaryStage) {
        Button btn = new Button();
        btn.setText("Say 'Hello World'");

/*      btn.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                System.out.println("Hello World!");
            }
        });  */ 

        btn.setOnAction((ActionEvent e) -> {
            System.out.println("Hello World!");
        });

        Pane pane = new Pane();
        pane.getChildren().add(btn);
 
        Scene scene = new Scene(pane, 300, 250);
        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
 
    public static void main(String[] args) { launch(args); }
}
  • môžeme priamo pracovať s objektami grafického okna - nastavovať a meniť nápisy na gombíku ale aj iných grafických prvkoch
  • problém môže byť, že kým sa to spracováva tak grafické okno 'zatuhne' (čiže nedávať počítať gombíku prvočíselný rozklad)
btn.setOnAction((ActionEvent e) -> {
    btn.setText("ha");
    ((Button)e.getSource()).setText("he"); //zmení text na gombíku, na ktorom bol Event vyvolaný - tu je to btn
});
  • Jeden EventHandler môžem nasadiť aj na viac objektov - napríklad niekoľko gombíkov
    • Urobím si konkrétnu inštanciu triedy EventHandler<ActionEvent> a tú potom v oboch gombíkoch použijem
    • Pozor gombíkom treba nastaviť súradnice, ináč sú cez seba a akciu vykoná vrchný z nich
EventHandler<ActionEvent> xxx = new EventHandler<ActionEvent>() {
    @Override public void handle(ActionEvent event) {
        System.out.println("Hello World!");
        ((Button)event.getSource()).setText("he");
    }
};
btn1.setOnAction(xxx);
btn2.setOnAction(xxx);

TextField

  • Prvok TextField je textové pole, do ktorého môžeme umiestniť text a nechať používateľa text upravovať, vyberať a pod.
  • Asi najpoužívanejšie metódy tohto prvku sú getText(), setText(), getSelectedText()
  • Najbežnejšia udalosť je zadanie textu ukončené klávesou Enter. Túto udalosť môžeme odchytiť pomocou metódy setOnAction()
TextField txt = new TextField();
txt.setText("Write something...");
        
txt.setOnAction( 
    (ActionEvent event) -> {
        String s=((TextField)event.getSource()).getText();
        System.out.println("You wrote:"+s); }
    );

StackPane root = new StackPane();
root.getChildren().add(txt);

Viac akcií

  • Pre rôzne ovládacie prvky existujú rôzne akcie, ktoré sa môžu vykonať - v závislosti od konkrétneho ovládacieho prvku
    • Už pre gombík môžeme okrem stlačenia pozorovať veľa akcií (môžete si ich pozrieť medzi medódami triedy Button [2] - začínajú 'setOn')
    • V našom prípade som pridala akciu s myšou -- keď myš vojde do priestoru gombíku, gombík ju pozdraví
btn1.setOnMouseEntered(new EventHandler<MouseEvent>(){
    @Override
        public void handle(MouseEvent event) {
            System.out.println("Hello Mouse!");
        }
});
  • Takýmto spôsobom môžeme mať na jeden prvok zavesených viacerých, ktorí čakajú na nejakú svoju udalosť
    • Na jednu akciu však môžeme mať iba jednu reakciu -- jeden EventHandler
    • Treba si však uvedomiť, že udalosti sú rôznych typov a EventHandler k nemu prislúchajúci by mal mať vhodný typový parameter (pri udalosti Action ActionEvent, pri práci s myšou MouseEvent atď.)

Kompletný program

package p30_javafxapplication;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class P30_JavaFXApplication extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        Label lab= new Label("Basic components.");
        lab.setLayoutY(0);
        
        Button btn = new Button();
        btn.setText("Say 'Hello World'");
        btn.setOnAction((ActionEvent e) -> {
            System.out.println("Hello World!");
        });
        btn.setOnMouseEntered((MouseEvent e) -> {
            System.out.println("Hello Mouse!");
        });
        btn.setLayoutY(50);
        
        TextField txt = new TextField("Say something.");
        txt.setOnAction( 
            (ActionEvent e) -> {
                String s=((TextField)e.getSource()).getText();
                System.out.println("You wrote:"+s); }
        );
        txt.setLayoutY(100);
        
        Rectangle square=new Rectangle();
        square.setHeight(10); square.setWidth(10);
        square.setLayoutY(150);    //square.alebo setY(150)
        
        Pane root = new Pane();
        root.getChildren().add(lab);
        root.getChildren().add(btn);
        root.getChildren().add(txt);
        root.getChildren().add(square);
        
        Scene scene = new Scene(root, 300, 250);
        
        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
    
}