Programovanie (1) v C/C++
1-INF-127, ZS 2024/25
Letný semester, prednáška č. 10: Rozdiel medzi revíziami
Riadok 67: | Riadok 67: | ||
Rozhranie <tt>Comparator<E></tt> je príkladom takzvaného ''funkcionálneho rozhrania'' – čiže rozhrania, ktoré deklaruje ''jedinú abstraktnú metódu''; v tomto prípade ide o metódu [https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/Comparator.html#compare(T,T) <tt>compare</tt>]. Toto rozhranie síce definuje niekoľko ďalších statických metód a metód s modifikátorom <tt>default</tt>; jedinou metódou, ktorú je potrebné implementovať kedykoľvek implementujeme rozhranie <tt>Comparator<E></tt>, je ale metóda <tt>compare</tt>. To okrem iného znamená, že proces vytvárania tried implementujúcich rozhranie <tt>Comparator<E></tt> sa často môže redukovať iba na implementáciu tejto jednej metódy. | Rozhranie <tt>Comparator<E></tt> je príkladom takzvaného ''funkcionálneho rozhrania'' – čiže rozhrania, ktoré deklaruje ''jedinú abstraktnú metódu''; v tomto prípade ide o metódu [https://docs.oracle.com/en/java/javase/15/docs/api/java.base/java/util/Comparator.html#compare(T,T) <tt>compare</tt>]. Toto rozhranie síce definuje niekoľko ďalších statických metód a metód s modifikátorom <tt>default</tt>; jedinou metódou, ktorú je potrebné implementovať kedykoľvek implementujeme rozhranie <tt>Comparator<E></tt>, je ale metóda <tt>compare</tt>. To okrem iného znamená, že proces vytvárania tried implementujúcich rozhranie <tt>Comparator<E></tt> sa často môže redukovať iba na implementáciu tejto jednej metódy. | ||
− | Ako skrátený zápis pre definíciu inštancie anonymnej triedy ''implementujúcej funkcionálne rozhranie'' | + | Ako skrátený zápis pre definíciu inštancie anonymnej triedy ''implementujúcej funkcionálne rozhranie'', obsahujúcej iba definíciu jedinej abstraktnej metódy deklarovanej v tomto rozhraní, slúžia v Jave takzvané ''lambda výrazy''. Utriedenie poľa tak možno realizovať pomocou príkazu |
<syntaxhighlight lang="java"> | <syntaxhighlight lang="java"> | ||
Collections.sort(a, (o1, o2) -> {return o2.compareTo(o1);}); | Collections.sort(a, (o1, o2) -> {return o2.compareTo(o1);}); |
Verzia zo dňa a času 15:27, 13. apríl 2021
Oznamy
- Stredajšie cvičenia budú aj napriek dekanskému voľnu (účasť je samozrejme dobrovoľná, rovnako ako aj na ostatných cvičeniach, na ktorých sa nepíše test). Bude zverejnených niekoľko nebodovaných úloh zameraných na látku z dnešnej prednášky (úlohy na JavaFX sa nebudú odovzdávať na testovač).
- Štvrtú domácu úlohu treba odovzdať do pondelka 26. apríla, 9:00 a tretiu bonusovú úlohu do stredy 28. apríla, 11:30.
- Najneskôr na budúcej prednáške, prosím, dajte vedieť o prípadných námietkach proti vypísaným termínom skúšky.
Lambda výrazy
Príklad: komparátor ako lambda výraz
Pripomeňme si z piatej prednášky zostupné triedenie zoznamu celých čísel pomocou metódy Collections.sort, ktorá ako druhý parameter berie komparátor, čiže inštanciu nejakej triedy implementujúcej rozhranie Comparator<? super Integer>. Videli sme pritom tri možné spôsoby, ako komparátor definovať:
- Vytvoriť „bežnú” triedu implementujúcu rozhranie Comparator<Integer> a metódu sort zavolať pre novovytvorenú inštanciu tejto triedy.
- To isté s použitím lokálnej triedy:
import java.util.*; public class Trieda { public static void main(String[] args) { class DualComparator implements Comparator<Integer> { @Override public int compare(Integer o1, Integer o2) { return o2.compareTo(o1); } } List<Integer> a = new ArrayList<>(); a.add(6); a.add(1); a.add(3); a.add(2); a.add(3); Collections.sort(a, new DualComparator()); System.out.println(a); } }
- Definíciu triedy a vytvorenie jej inštancie spojiť do jediného príkazu s využitím mechanizmu anonymných tried:
import java.util.*; public class Trieda { import java.util.*; public class Trieda { public static void main(String[] args) { List<Integer> a = new ArrayList<>(); a.add(6); a.add(1); a.add(3); a.add(2); a.add(3); Collections.sort(a, new Comparator<>() { @Override public int compare(Integer o1, Integer o2) { return o2.compareTo(o1); } }); System.out.println(a); } }
Aj posledný z uvedených spôsobov definície komparátora je však stále trochu ťažkopádny – jediná pre účely triedenia podstatná informácia je tu daná riadkom return o2.compareTo(o1);, pričom zvyšok konštrukcie by bol rovnaký aj pri definícii ľubovoľného iného komparátora.
Rozhranie Comparator<E> je príkladom takzvaného funkcionálneho rozhrania – čiže rozhrania, ktoré deklaruje jedinú abstraktnú metódu; v tomto prípade ide o metódu compare. Toto rozhranie síce definuje niekoľko ďalších statických metód a metód s modifikátorom default; jedinou metódou, ktorú je potrebné implementovať kedykoľvek implementujeme rozhranie Comparator<E>, je ale metóda compare. To okrem iného znamená, že proces vytvárania tried implementujúcich rozhranie Comparator<E> sa často môže redukovať iba na implementáciu tejto jednej metódy.
Ako skrátený zápis pre definíciu inštancie anonymnej triedy implementujúcej funkcionálne rozhranie, obsahujúcej iba definíciu jedinej abstraktnej metódy deklarovanej v tomto rozhraní, slúžia v Jave takzvané lambda výrazy. Utriedenie poľa tak možno realizovať pomocou príkazu
Collections.sort(a, (o1, o2) -> {return o2.compareTo(o1);});
alebo pomocou ešte kratšieho príkazu
Collections.sort(a, (o1, o2) -> o2.compareTo(o1));
V zátvorkách pred šípkou sú postupne uvedené identifikátory argumentov metódy compare a za šípkou nasleduje blok obsahujúci telo metódy compare, prípadne výraz udávajúci výstupnú hodnotu tejto metódy.
Syntax lambda výrazov v Jave
Lambda výraz je teda skráteným zápisom anonymnej triedy implementujúcej funkcionálne rozhranie. Alternatívne ho možno považovať aj za reprezentáciu implementácie abstraktnej metódy v tomto rozhraní deklarovanej; preto sa v súvislosti s lambda výrazmi často hovorí aj o anonymných funkciách. Pomenovanie „lambda výraz” odkazuje na lambda kalkul – formalizmus, v ktorom sa podobným spôsobom definujú matematické funkcie (napríklad λx.x2 + 5 je funkcia f daná predpisom f: x ↦ x2 + 5) a ktorý je okrem iného aj teoretickým základom funkcionálneho programovania.
V prípade, že má jediná abstraktná metóda deklarovaná v implementovanom funkcionálnom rozhraní hlavičku
T f(T1 arg1, T2 arg2, ..., Tn argn);
môže byť lambda výraz reprezentujúci triedu implementujúcu toto rozhranie tvaru
(arg1, arg2, ..., argn) -> { /* Telo implementacie metody f */ }
V prípade, že je počet argumentov metódy f nulový, píšeme
() -> { /* Telo implementacie metody f */ }
a v prípade, že ide o metódu s jediným argumentom arg, možno vynechať zátvorky okolo argumentov a písať iba
arg -> { /* Telo implementacie metody f */ }
V prípade, že návratový typ T metódy f nie je void, možno na pravej strane lambda výrazu namiesto bloku obsahujúceho telo implementovanej metódy uviesť iba výraz, ktorý sa vyhodnotí na typ T; v takom prípade pôjde o definíciu funkcie, ktorá počíta tento výraz. V prípade, že návratovým typom metódy f je void, možno namiesto bloku s telom implementovanej metódy uviesť volanie jednej metódy s návratovým typom void (ktoré možno z určitého pohľadu chápať ako „výraz typu void”).
Anotácia @FunctionalInterface používaná v nasledujúcich príkladoch je nepovinná (všetko by rovnako dobre fungovalo aj bez nej), ale odporúčaná – kompilátor totiž vyhodí chybu kedykoľvek je táto anotácia použitá inde ako pri funkcionálnom rozhraní.
Príklad 1:
@FunctionalInterface
public interface MyFunctionalInterface {
int f(int a, int b);
}
public class Trieda {
public static void main(String[] args) {
MyFunctionalInterface instance1 = (a, b) -> {
if (a < b) {
return 0;
}
return a + b;
};
MyFunctionalInterface instance2 = (a, b) -> a * b;
System.out.println(instance1.f(2, 3));
System.out.println(instance2.f(2, 3));
}
}
Príklad 2:
@FunctionalInterface
public interface MyFunctionalInterface {
int f(int a);
}
public class Trieda {
public static void main(String[] args) {
MyFunctionalInterface instance1 = (a) -> {return a + 1;};
MyFunctionalInterface instance2 = a -> {return a - 1;};
MyFunctionalInterface instance3 = x -> x + 3;
System.out.println(instance1.f(2));
System.out.println(instance2.f(2));
System.out.println(instance3.f(2));
}
}
Príklad 3:
@FunctionalInterface
public interface MyFunctionalInterface {
void f();
}
public class Trieda {
public static void main(String[] args) {
MyFunctionalInterface instance1 = () -> {System.out.println("Hello, lambda!");};
MyFunctionalInterface instance2 = () -> System.out.println("Hello, lambda!");
instance1.f();
instance2.f();
}
}
Ďalšie príklady použitia lambda výrazov
Referencie na metódy
Úvod do JavaFX
JavaFX je knižnica, ktorú možno využiť ako nástroj na tvorbu aplikácií s grafickým používateľským rozhraním (GUI). Namiesto konzolových aplikácií teda budeme v nasledujúcich niekoľkých prednáškach vytvárať aplikácie grafické (typicky pozostávajúce z jedného alebo niekoľkých okien s ovládacími prvkami, akými sú napríklad tlačidlá, textové polia, a podobne).
- Staršia verzia JavaFX bola priamo súčasťou Java SE 8 a mala dobrú podporu v NetBeans (ide o verzie nainštalované v počítačových učebniach).
- Novšie verzie Java SE už JavaFX neobsahujú, ale táto je vyvíjaná ako samostatný modul, ktorý je potrebné aj samostatne nainštalovať. Podpora v NetBeans je slabšia.
- Viac o inštalácii JavaFX pre novšie verzie Java SE a NetBeans (návod popisuje aj vytvorenie JavaFX projektu v novších verziách NetBeans).
- Dokumentácia a tutoriály pre JavaFX 8.
- Dokumentácia a tutoriály pre JavaFX 14.
Vytvorenie aplikácie s jedným grafickým oknom
Minimalistickú JavaFX aplikáciu zobrazujúcu jedno prázdne okno o 300 krát 250 pixeloch s titulkom „Hello, World!” vytvoríme nasledovne:
package aplikacia;
import javafx.application.*;
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.layout.*;
public class Aplikacia extends Application {
@Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
Scene scene = new Scene(pane, 300, 250);
primaryStage.setTitle("Hello, World!");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Uvedený kód si teraz rozoberme:
- Hlavná trieda JavaFX aplikácie (tzn. trieda obsahujúca metódu main) sa vyznačuje tým, že dedí od abstraktnej triedy Application definovanej v balíku javafx.application, ktorý je potrebné importovať.
- Každá trieda dediaca od triedy Application musí implementovať jej abstraktnú metódu start, ktorej argumentom je objekt primaryStage typu Stage reprezentujúci hlavné grafické okno aplikácie (trieda Stage je definovaná v balíku javafx.stage, ktorý je potrebné importovať). Metóda start sa vykoná hneď po spustení aplikácie. V rámci metódy start sa typicky vytvárajú jednotlivé ovládacie prvky aplikácie a špecifikujú sa ich vlastnosti.
- V našom prípade je kľúčovým riadkom metódy start volanie primaryStage.show(), ktorým zobrazíme hlavné okno aplikácie. Bez tohto volania by aplikácia bežala „na pozadí”.
- Volaním primaryStage.setTitle("Hello, World!") nastavíme titulok hlavného okna na text „Hello, World!”.
- Uvedené dva riadky často stačia na zobrazenie grafického okna s titulkom „Hello, World!” a „náhodne” zvolenou veľkosťou. V závislosti od systému sa však môže stať aj to, že sa nezobrazí nič – grafické okno totiž zatiaľ nič neobsahuje a systém nemá ako „rozumne” vypočítať jeho veľkosť; môže teda túto situáciu vyhodnotiť aj tak, že ešte nie je čo zobraziť.
- Zvyšnými riadkami už len hovoríme, že „obsahom” hlavného okna má byť prázdna oblasť o veľkosti 300 krát 250 pixelov:
- Kontajnerom pre obsah okna je trieda Scene. Ide tu o analógiu s divadelnou terminológiou: okno zodpovedá javisku; na javisku následne možno umiestniť scénu pozostávajúcu z jednotlivých rekvizít. Scénu scene možno oknu primaryStage priradiť volaním primaryStage.setScene(scene). Trieda Scene je definovaná v balíku javafx.scene, ktorý je potrebné importovať.
- Scéna je interpretovaná ako hierarchický strom uzlov (detaily neskôr), pričom uzlami môžu byť napríklad oblasti, ale aj ovládacie prvky ako napríklad tlačidlá, či textové polia. Volaním konštruktora Scene scene = Scene(pane, 300, 250) vytvoríme scénu o rozmeroch 300 krát 250 pixelov, ktorej koreňovým uzlom je objekt pane; ten bude v našom prípade reprezentovať prázdnu oblasť.
- Volaním konštruktora Pane pane = new Pane() vytvoríme novú oblasť pane. Tá môže neskôr slúžiť ako kontajner pre pridávanie rôznych ovládacích prvkov a podobne. Trieda Pane je definovaná v balíku javafx.scene.layout, ktorý je potrebné importovať.
- Metóda main JavaFX aplikácie typicky pozostáva z jediného riadku, v ktorom sa volá statická metóda launch triedy Application. Tá sa postará o vytvorenie inštancie našej triedy Aplikacia, o vytvorenie hlavného grafického okna aplikácie, ako aj o následné zavolanie metódy start, ktorá dostane vytvorené okno ako argument.
Okno s niekoľkými jednoduchými ovládacími prvkami
Podbne ako v príklade vyššie vytvorme aplikáciu pozostávajúcu s jediného grafického okna:
package aplikacia;
import javafx.application.*;
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.layout.*;
public class Aplikacia extends Application {
@Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
Scene scene = new Scene(pane, 340, 100);
primaryStage.setTitle("Zadávanie textu");
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Pridáme teraz do hlavného okna niekoľko ovládacích prvkov tak, ako na obrázku vpravo. Naším cieľom bude vytvorenie aplikácie umožňujúcej zadať text, ktorý sa pri kliknutí na tlačidlo OK zjaví v textovom popisku červenej farby. Ovládacie prvky ako textové pole alebo tlačidlo sú definované v balíku javafx.scene.control, ktorý je tak nutné importovať. Podobne na prácu s fontmi budeme potrebovať balík javafx.scene.text a na prácu s farbami balík javafx.scene.paint.
Začnime s pridaním textového popisku „Zadaj text”. Takéto textové popisky sú v JavaFX reprezentované triedou Label, pričom popisok label1 obsahujúci nami požadovaný text vytvoríme nasledovne:
Label label1 = new Label("Zadaj text:");
Rovnako dobre by sme mohli použiť aj konštruktor bez argumentov, ktorý je ekvivalentný volaniu konštruktora s argumentom "" – text popisku label1 možno upraviť aj neskôr volaním label1.setText("Nový text").
Po jeho vytvorení ešte musíme popisok label1 pridať do našej scény – presnejšie do oblasti pane, ktorá je jej koreňovým uzlom (čo znamená, že všetky ostatné uzly budú umiestnené v tejto oblasti). Vytvorený popisok label1 teda pridáme do zoznamu synov oblasti pane nasledujúcim volaním:
pane.getChildren().add(label1);
Následne môžeme upraviť niektoré vlastnosti vytvoreného popisku, ako napríklad jeho pozíciu a font:
label1.setFont(Font.font("Tahoma", FontWeight.BOLD, 12));
label1.setLayoutX(20);
label1.setLayoutY(10);
Analogicky vytvoríme aj ostatné komponenty:
TextField textField = new TextField();
pane.getChildren().add(textField);
textField.setFont(Font.font("Tahoma", FontWeight.BOLD, 12));
textField.setLayoutX(20);
textField.setLayoutY(30);
textField.setPrefWidth(300);
Label label2 = new Label("(Zatiaľ nebolo zadané nič)");
pane.getChildren().add(label2);
label2.setFont(Font.font("Tahoma", 12));
label2.setTextFill(Color.RED);
label2.setLayoutX(20);
label2.setLayoutY(70);
Button button = new Button("OK");
pane.getChildren().add(button);
button.setFont(Font.font("Tahoma", FontWeight.BOLD, 12));
button.setLayoutX(280);
button.setLayoutY(60);
button.setPrefWidth(40);
button.setPrefHeight(30);
Uvedený spôsob grafického návrhu scény má však hneď dva zásadné nedostatky:
- Môžeme si všimnúť, že takéto pevné rozloženie ovládacích prvkov na scéne nevyzerá dobre, keď zmeníme veľkosť okna. Provizórne môžeme tento problém vyriešiť tým, že menenie rozmerov okna jednoducho zakážeme: primaryStage.setResizable(false). Takéto riešenie má však ďaleko od ideálneho. Odporúčaným prístupom je využiť namiesto triedy Pane niektorú z jej „inteligentnejších” podtried umožňujúcich (polo)automatické škálovanie scény v závislosti od veľkosti okna. V takom prípade sa absolútne súradnice ovládacích prvkov zvyčajne vôbec nenastavujú.
- Formát jednotlivých ovládacích prvkov (ako napríklad font alebo farba) by sa po správnosti nemal nastavovať priamo v zdrojovom kóde. Namiesto toho je odporúčaným prístupom využitie štýlov definovaných v pomocných súboroch JavaFX CSS. Takto je možné meniť formátovanie bez väčších zásahov do zdrojového kódu.
Obidvoma týmito problematikami sa budeme zaoberať v rámci nasledujúcej prednášky.
Oživenie ovládacích prvkov (spracovanie udalostí)
Dokončime našu jednoduchú aplikáciu so zadávaním textu pridaním jej kľúčovej funkcionality: po stlačení tlačidla OK (t.j. button) sa má do „červeného” popisku prekopírovať text zadaný používateľom do textového poľa.
Po stlačení tlačidla button je systémom (Java Virtual Machine) vygenerovaná tzv. udalosť, ktorá je v tomto prípade typu ActionEvent. Udalosť je teda akýsi objekt nesúci informáciu o tom, že bolo stlačené dané tlačidlo. Každé tlačidlo – objekt typu Button – má navyše k dispozícii (zdedenú) metódu
public final void setOnAction(EventHandler<ActionEvent> value)
umožňujúcu „zaregistrovať” pre dané tlačidlo jeho spracovávateľa udalostí typu ActionEvent. Ním môže byť ľubovoľná trieda implementujúca rozhranie EventHandler<ActionEvent>, ktoré vyžaduje implementáciu jedinej metódy
void handle(ActionEvent event)
Po zaregistrovaní objektu eventHandler ako spracovávateľa udalostí ActionEvent pre tlačidlo button volaním
button.setOnAction(eventHandler);
sa po každom stlačení tlačidla button vykoná metóda eventHandler.handle.
Nami požadovanú funkcionalitu tlačidla button tak vieme vyjadriť napríklad pomocou lokálnej triedy ButtonActionEventHandler:
import javafx.event.*;
...
public void start(Stage primaryStage) {
...
class ButtonActionEventHandler implements EventHandler<ActionEvent> {
@Override
public void handle(ActionEvent event) {
label2.setText(textField.getText());
}
}
EventHandler<ActionEvent> eventHandler = new ButtonActionEventHandler();
button.setOnAction(eventHandler);
...
}
Skrátene môžeme to isté napísať s použitím anonymnej triedy:
public void start(Stage primaryStage) {
...
button.setOnAction(new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
label2.setText(textField.getText());
}
});
...
}
Ak si navyše uvedomíme, že rozhranie EventHandler pozostáva z jedinej metódy, môžeme tento zápis ešte ďalej zjednodušiť použitím lambda výrazu:
public void start(Stage primaryStage) {
...
button.setOnAction((ActionEvent e) -> {
label2.setText(textField.getText());
});
...
}
Podrobnejšie sa spracúvaním udalostí v JavaFX budeme zaoberať na nasledujúcej prednáške.
Geometrické útvary
Špeciálnym typom uzlov, ktoré možno umiestňovať do scén, sú geometrické útvary ako napríklad Circle, Rectangle, Arc, Ellipse, Line, Polygon, atď. Všetky útvary dedia od spoločnej abstraktnej nadtriedy Shape. Sú definované v balíku javafx.scene.shape, ktorý je nutné na prácu s nimi importovať.
Aj keď útvary nevedia vyvolať udalosť typu ActionEvent, môžu vyvolávať udalosti iných typov. Napríklad kliknutie na útvar myšou vyústi v udalosť typu MouseEvent (definovanú v balíku javafx.scene.input) a spracovávateľa takejto udalosti možno pre útvar shape zaregistrovať pomocou metódy shape.setOnMouseClicked.
Nasledujúci kód vykreslí „tabuľku” o 10 krát 10 útvaroch, pričom pre každý sa náhodne určí, či pôjde o štvorec, alebo o kruh. Farba každého z útvarov sa taktiež určí náhodne. Navyše po kliknutí myšou na ktorýkoľvek z útvarov sa jeho farba náhodne zmení.
package aplikacia;
import javafx.application.*;
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.shape.*;
import javafx.scene.paint.*;
import javafx.scene.input.*;
import java.util.*;
public class Aplikacia extends Application {
Color randomColor(Random random) {
return Color.color(random.nextDouble(), random.nextDouble(), random.nextDouble());
}
@Override
public void start(Stage primaryStage) {
Pane pane = new Pane();
Random random = new Random();
for (int i = 0; i <= 9; i++) {
for (int j = 0; j <= 9; j++) {
Shape shape;
boolean isSquare = random.nextBoolean();
Color color = randomColor(random);
if (isSquare) {
shape = new Rectangle(i * 60 + 5, j * 60 + 5, 50, 50);
} else {
shape = new Circle(i * 60 + 30, j * 60 + 30, 25);
}
shape.setFill(color);
shape.setOnMouseClicked((MouseEvent e) -> {
shape.setFill(randomColor(random));
});
pane.getChildren().add(shape);
}
}
Scene scene = new Scene(pane, 600, 600);
primaryStage.setTitle("Geometrické útvary");
primaryStage.setResizable(false);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}