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 25

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

Úvod do predmetu

Ciele predmetu

  • prehĺbiť a rozšíriť zručnosti v algoritmickom uvažovaní, písaní a ladení programov z predchádzajúceho semestra
  • oboznámiť sa so základnými programovými a dátovými štruktúrami jazyka Java
  • zvládnuť základy objektovo-orientovaného programovania a tvorby programov s grafickým užívateľským rozhraním
  • oboznámiť sa so základnými algoritmami na prácu s grafmi

Technické detaily

  • Budeme používať verziu Java SE 8 (nie najnovšiu Java SE 9)
  • Budeme naďalej používať systém Netbeans, ale
    • Pre Javu je lepšie prispôsobený ako pre C/C++
    • Netbeans čisto pre Javu by mal byť jednoduchšie nainštalovateľný pod Windows
    • Ak viete ako, môžete používať aj v iné prostredia, napr. Eclipse, prípadne textový editor. Pozor, na skúške len štandardné Linuxové prostredie v učebniach.

Literatúra

Pravidlá na tento semester

  • Presné znenie
  • Zmeny oproti minulému semestru:
    • Týždenne iba jedna prednáška a jedny cvičenia
    • Trochu viac domácich úloh, niektoré budú väčšie, vyžadujú priebežnú prácu
    • Bude nepovinný bonusový projekt za 10%. Témy na projekt a bližšie informácie oznámime neskôr. Odporúčame robiť vo dvojiciach.
    • Rozcvička na cvičeniach cca raz za dva týždne, ostatné úlohy na cvičeniach sa neodovzdávajú a nebodujú
    • Občas môžu byť ďalšie príklady za malý počet bonusových bodov
    • Rozcvička bude z učiva, ktoré sa už cvičilo na predchádzajúcich cvičeniach, mali by ste teda na ňu byť pripravení
    • Na rozcvičkách môžu byť aj témy z minulého semestra (práca s poľami, zoznamami, stromami, rekurzia,...), ale v Jave
    • Iba jeden test na konci semestra, treba získať aspoň polovicu bodov (bude aj opravný termín)
    • Na skúške treba mať celkovo aspoň polovicu bodov, ale nemusí byť jeden príklad celý dobre
    • Test pre pokročilých bude podobne ako v zimnom semestri, domáce úlohy sú ale pre všetkých (nedá sa použiť Rýchlostné programovanie)

Odporúčania

  • Neopisujte
  • Pracujte na DÚ priebežne, nie tesne pred termínom
  • Ak niečomu nerozumiete alebo potrebujete poradiť s DÚ, pýtajte sa
  • Využite cvičenia na precvičenie učebnej látky

Začiatok semestra

  • Prvá úloha už zverejnená, termín odovzdania budúcu stredu
  • Druhá úloha zverejnená budúci týždeň
  • V stredu prvé cvičenia, bude aj malý bonusový príklad
  • Prvá rozcvička budúci týždeň
  • Test pre pokročilých budúci týždeň
    • Prihlásenie sa a hlasovanie o čase formulárom do stredy

Hello world

V Netbeans

Vytvorenie projektu:

  • V menu zvolíme New Project
  • Na prvej obrazovke zvolíme Categories: Java a Projects: Java Application
  • Na ďalšej obrazovke Project name: hello a Create Main Class: hello.Hello
  • Do súboru Hello.java napíšeme text:
 
package hello;

public class Hello {

    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}
  • Potom spúšťame podobne ako program v jazyku C++

V Linuxe na príkazovom riadku

Ak chcete Javu skúsiť bez použitia Netbeans:

  • Vytvoríme adresár hello, v ňom súbor Hello.java s rovnakým obsahom ako vyššie
  • Kompilácia javac hello/Hello.java (vznikne súbor hello/Hello.class)
  • Spustenie java hello.Hello
  • Pozor, meno adresára musí sedieť s menom balíčka (hello), meno súboru s menom triedy (Hello)
  • Ak vynecháme riadok package hello, môžeme mať súbor Hello.java priamo v aktuálnom adresári.

Väčší program

  • Ukážme si teraz väčší program, v ktorom bude aj načítanie vstupu, polia a rekurzia.
  • Je to javová verzia C++ programu na generovanie variácií bez opakovania z minulého semestra.
  • Jednotlivé jazykové konštrukty použité v programe rozoberieme nižšie v texte.

Najskôr v C++:

#include <iostream>
using namespace std;

void vypis(int a[], int k) {
    for (int i = 0; i < k; i++) {
        cout << a[i];
    }
    cout << endl;
}

void generuj(int a[], bool bolo[], int i, int k, int n) {
    /* v poli a dlzky k mame prvych i cifier,
     * v poli bolo mame zaznamenane, ktore cifry su uz pouzite,
     * chceme vygenerovat vsetky moznosti
     * poslednych k-i cifier */
    if (i == k) {
        vypis(a, k);
    } else {
        for (int x = 0; x < n; x++) {
            if (!bolo[x]) {
                a[i] = x;
                bolo[x] = true;
                generuj(a, bolo, i + 1, k, n);
                bolo[x] = false;
            }
        }
    }
}

int main(void) {
    int k, n;
    cin >> k >> n;
    int *a = new int[k];
    bool *bolo = new bool[n];
    for (int i = 0; i < n; i++) {
        bolo[i] = false;
    }
    generuj(a, bolo, 0, k, n);
    delete[] a;
    delete[] bolo;
}
  • A teraz v Jave:
package hello;

import java.util.Scanner;

public class Hello {

    static void vypis(int[] a) {
        for (int x : a) {
            System.out.print(" " + x);
        }
        System.out.println();
    }

    static void generuj(int[] a, boolean[] bolo, int i, int n) {
        /* v poli a dlzky k mame prvych i cifier,
         * v poli bolo mame zaznamenane, ktore cifry su uz pouzite,
         * chceme vygenerovat vsetky moznosti
         * poslednych k-i cifier */
        if (i == a.length) {
            vypis(a);
        } else {
            for (int x = 0; x < n; x++) {
                if (!bolo[x]) {
                    a[i] = x;
                    bolo[x] = true;
                    generuj(a, bolo, i + 1, n);
                    bolo[x] = false;
                }
            }
        }
    }

    public static void main(String[] args) {
        int k, n;
        Scanner s = new Scanner(System.in);
        k = s.nextInt();
        n = s.nextInt();
        int[] a = new int[k];
        boolean[] bolo = new boolean[n];
        for (int i = 0; i < n; i++) {
            bolo[i] = false;
        }
        generuj(a, bolo, 0, n);
    }
}

Základy jazyka Java

Primitívne typy, polia a referencie

Primitívne typy (podobné na C/C++)

  • int: 32-bitové číslo so znamienkom, hodnoty v rozsahu -2,147,483,648..2,147,483,647 (ďalšie celočíselné typy byte, short, long)
  • double: 64-bitové desatinné číslo s pohyblivou desatinnou čiarkou (a 32-bitový float)
  • boolean: hodnota true alebo false
  • char: 16-bitový znak v kódovaní Unicode (podporuje teda napr. slovenskú diakritiku)

Lokálne premenné treba inicializovať, inak kompilátor vyhlási chybu:

int y;
System.out.println(y); // variable y might not have been initialized

V poliach a v objektoch kompilátor inicializuje premenné na 0, null, resp. false.

Polia

  • Polia v Jave vedia svoju dĺžku, nemusíme ju ukladať v ďalšej premennej
  • Pole musíme alokovať príkazom new:
double[] a;                  // deklarujeme premennú typu pole desatinných čísel, zatiaľ ma neinicializovanú hodnotu
a = new double[3];           // alokujeme pole troch desatinných čísel
for (int i = 0; i < a.length; i++) {  // do poľa uložíme čísla 0..2
    a[i] = i;
}
  • Alebo mu môžeme priradiť počiatočné hodnoty: double[] a = {0.0, 1.0, 2.0};
  • Java kontroluje hranice polí, napr. System.out.println(a[3]); spôsobí chybu počas behu programu: Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 3

Referencie

  • Každá premenná v Jave obsahuje buď hodnotu primitívneho typu alebo referenciu.
  • Referencia, podobne ako smerník v C, predstavuje adresu v pamäti.
  • Referencia môže ukazovať na pole alebo objekt, ale nie na primitívny typ.
  • Nefunguje smerníková aritmetika.
  • Referencie môžu mať hodnotu null, ak neukazujú na žiadnu pamäť.
  • Na jedno pole alebo objekt môžeme mať viac referencií:
double[] a = {0.0, 1.0, 2.0};
double[] b = a;  // skopíruje referenciu na to iste pole do b
a[1]+=2;         // zmeníme pole, na ktoré ukazujú a aj b
System.out.println(b[1]);  // vypíše 3.0
a = new double[2];  // a a b teraz ukazujú na rôzne polia 
  • V Jave nemusíme polia odalokovať, program to spraví sám, keď už na nich nie je žiadna referencia (garbage collection)

Operátory, cykly, podmienky

  • Operátory podobne ako C/C++, napr. aritmetické +, -, *, /, %, priradenie =, +=,..., ++, --, logické !, &&, ||, porovnávanie ==, !=, >=,...
  • Pozor, pri referenciách operátor == testuje, či ukazujú na tú istú pamäť, nie či je v tej pamäti tá istá hodnota
  • Podmienky if, else, switch rovnako ako v C
  • Cykly for, while, do .. while podobne ako v C, podobne break a continue

Navyše Java má cyklus for, ktorý ide cez všetky hodnoty v poli aj bez indexovej premennej

  • Tu vidíme dva spôsoby ako vypísať obsah poľa
double[] a = {0.0, 1.0, 2.0};
for (int i = 0; i < a.length; i++) {
    System.out.println(a[i]);
}
for (double x : a) {
    System.out.println(x);
}
  • Pozor, takýto cyklus sa nedá použiť na zmenenie hodnôt v poli:
for (double x : a) {
    x = 0; // nemení pole, iba lokálnu premennú x
}

Funkcie (statické metódy) a ich parametre

  • Ak chceme písať menší program bez vlastných objektov, ako sme robili v C, použijeme statické metódy umiestnené v jednej triede
  • Pred každé meno metódy okrem návratového typu píšeme slovo static
  • Pred main píšeme aj slovo public, aby bola viditeľná aj mimo aktuálneho balíčku.
  • Návratový typ funkcie main je void, argumenty sú v poli reťazcov (nie je tam meno programu ako v C)
package pocet;
public class Pocet {
    public static void main(String[] args) {
        System.out.println("Pocet argumentov: " + args.length);
    }
}
  • Parametre funkcií sa odovzdávajú hodnotou
    • Ak ide o primitívny typ, funkcii sa skopíruje jeho hodnota
    • Ak ide o referenciu na pole alebo objekt, funkcii sa skopíruje táto referencia, funkcia môže teda meniť tento objekt alebo pole
  • Nedá sa teda napísať funkcia swap, ktorá vymení obsah dvoch premenných
  • Tu je ilustratívny príklad:
(a) Situácia na začiatku vykonávania metódy pokus, (b) situácia na konci vykonávania metódy pokus.
static void pokus(int[] a, int x) {
    a[1] = 5;        // zmena v poli, na ktoré ukazuje a aj b
    a = new int[3];  // a ukazuje na nové pole, b na staré
    System.out.println(a[1]);  // vypíše 0
    x = 6;           // zmena x, y sa nemení
}

public static void main(String[] args) {
    int[] b = {1, 2, 3};
    int y = 4;
    pokus(b, y);
    System.out.println(b[1]);  // vypíše 5
    System.out.println(y);     // vypíše 4
}
  • Návratový typ môže byť void, primitívny typ alebo referencia
    • Príkaz return ako v C

Práca s maticami

  • V poli môžeme mať aj referencie na iné polia, dostávame tak viacrozmerné matice, podobne ako v C-čku.
  • Deklarácia 3-rozmerného poľa: int[][][] a;
  • Ak sú všetky rozmery známe, môžeme ho jedným príkazom alokovať, napr. a=new int[2][3][4];
  • Môžeme však spraviť napr. trojuholníkovú maticu, v ktorej má každý riadok inú dĺžku:
package hello;
public class Hello {

    static void vypis(int[][] a) {
        /* vypiseme cisla v matici a na konzolu */
        for (int[] riadok : a) {
            for (int x : riadok) {
                System.out.print(" " + x);
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        int[][] a = new int[3][];
        for (int i = 0; i < a.length; i++) {
            a[i] = new int[i+1];
            for (int j = 0; j < a[i].length; j++) {
                a[i][j] = i * j;
            }
        }
        vypis(a);
    }
}

Výstup:

 0
 0 1
 0 2 4
  • Podobne 3-rozmerné pole s rôzne veľkými podmaticami a riadkami:
    static void vypis(int[][][] a) {
        /* vypiseme cisla v 3D poli a na konzolu */
        for (int[][] matica : a) {
            for (int[] riadok : matica) {
                System.out.print("[");
                for (int x : riadok) {
                    System.out.print(" " + x);
                }
                System.out.print(" ] ");
            }
            System.out.println();
        }
    }

    public static void main(String[] args) {
        int[][][] a = new int[3][][];
        for (int i = 0; i < a.length; i++) {
            a[i] = new int[i + 1][];
            for (int j = 0; j < a[i].length; j++) {
                a[i][j] = new int[j + 1];
                for (int k = 0; k < a[i][j].length; k++) {
                    a[i][j][k] = i * j * k;
                }
            }
        }
        vypis(a);
    }

Výstup:

[ 0 ] 
[ 0 ] [ 0 1 ] 
[ 0 ] [ 0 2 ] [ 0 4 8 ] 

Reťazce

  • Reťazec je objekt triedy String, po vytvorení sa nedá meniť
  • Text medzi úvodzovkami je považovaný za String
  • Inicializácia konštantným reťazcom: String greeting = "Hello world!";
  • Operátor + konkatenuje (zliepa) reťazce. Ak je jeden operand reťazec, iné typy konvertuje na String:
int x=1;
String str = "Hodnota x: " + x;

Prístup k reťazcu:

  • dĺžka sa počíta metódou length() a i-ty znak metódou charAt(i)
String str = "Ahoj!";
int len = str.length();  // dlzka retazca
for (int i = 0; i < len; i++) {
    System.out.println(i + ". znak: " + str.charAt(i));
} 

Výstup:

0. znak: A
1. znak: h
2. znak: o
3. znak: j
4. znak: !
  • Porovnanie reťazcov na rovnosť metódou equals (Pozor, porovnanie == testuje, či ide o to isté miesto v pamäti)
String str1 = "abc";      // reťazec abc
String str2 = str1;       // referencia na ten istý reťazec
String str3 = str1 + "";  // vznikne nový reťazec abc
if (str1 == str2) {       // true, lebo to isté miesto
    System.out.println("str1==str2"); 
}
if (str1 == str3) {       // false, lebo rôzne miesta
     System.out.println("str1==str3"); 
}
if (str1.equals(str3)) {  // true, lebo zhodné reťazce
     System.out.println("str1.equals(str3)"); 
}
  • Veľa ďalších metód, pozri dokumentáciu
  • Ak potrebujete reťazec meniť, napr. k nemu postupne pridávať, môžete použiť StringBuilder
    • Rýchlejšie ako stále vyrábať nové reťazce pomocou operátora + (pre spájanie malého počtu častí stačí String)
    • Napr. dva spôsoby ako vytvoriť reťazec abeceda obsahujúci písmená a..z:
// Pomocou String, postupne vytvorí 27 rôznych String-ov
String abeceda = "";
for (char c = 'a'; c <= 'z'; c++) {
     abeceda = abeceda + c;  // vytvorí nový String, naplní ho novým obsahom
}
// Pomocou StringBuilder, vytvorí jeden StringBuilder a jeden String
StringBuilder buffer = new StringBuilder();
for (char c = 'a'; c <= 'z'; c++) {
     buffer.append(c);  // modifikuje objekt buffer
}
String abeceda = buffer.toString();  // vytvorí nový String

Vstup, výstup, súbory

  • Java má rozsiahle knižnice, uvádzame len návod na základnú prácu s textovými súbormi.
  • Vo väčšine prípadov potrebujeme triedy z balíčku java.io, takže si ich môžeme naimportovať všetky: import java.io.*;
    • Trieda Scanner je v balíčku java.util, použijeme teda import java.util.Scanner;
  • V prípade, že pri práci so súbormi nastane nečakaná chyba, Java použije mechanizmus výnimiek (exception)
    • O výnimkách sa budeme učiť neskôr, nateraz len do metódy main (a prípadne ďalších metód) pridáme upozornenie, že výnimka môže nastať:
public static void main(String[] args) throws java.io.IOException { ... }

Písanie na konzolu

  • System.out.print(retazec)
  • System.out.println(retazec) - pridá koniec riadku
  • Reťazec môžeme vyskladať z viacerých častí rôznych typov pomocou +
  • Formátovanie podobné na printf v C-čku: System.out.format("%.1f%n", 3.15); vypíše číslo na jedno desatinné miesto, t.j. 3.2 a koniec riadku podľa operačného systému.

Čítanie z konzoly

  • Objekt System.in je typu FileInputStream a podporuje iba čítanie jednotlivých bajtov resp. polí bajtov
  • Lepšie sa pracuje, ak ho použijeme ako súčasť objektu, ktorý vie tieto bajty spracovať do riadkov, čísel a pod.
  • Trieda BufferedReader umožňuje čítať celý riadok naraz ale aj znak po znaku. Tu je príklad jej použitia:
package hello;
import java.io.*;   // potrebujeme triedy z balíčka java.io
public class Hello {
    public static void main(String[] args)
            throws java.io.IOException {  // musíme pridať oznam, že môže vzniknúť výnimka - chyba pri čítaní
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        while (true) {
            // načítame riadok do reťazca
            String line = in.readLine();
            // skončíme, keď užívateľ zadá prázdny riadok alebo keď prídeme na koniec vstupu (null)
            if (line == null || line.equals("")) { 
                break;
            }
            // vypíšeme načítany riadok
            System.out.println("Napísali ste riadok \"" + line + "\"");
        }
        System.out.println("Končíme...");
    }
}

Príklad behu programu:

Ahoj
Napísali ste riadok "Ahoj"
1 2 3
Napísali ste riadok "1 2 3"

Končíme...
    • Metóda readLine() teda číta celý riadok (odstráni znak pre koniec riadku), metóda read() číta jeden znak (na konci súboru vráti -1)
  • Trieda Scanner rozkladá vstup na slová oddelené bielymi znakmi (medzery, konce riadku a pod.) a prípadne ich premieňa na čísla.
    • Príklad programu, ktorý vypisuje slová načítané od užívateľa, kým nezadá END alebo neskončí vstup
package hello;
import java.util.Scanner;
public class Hello {

    public static void main(String[] args) {
        Scanner s = new Scanner(System.in);  // inicializujeme Scanner
        int num = 0;
        while (s.hasNext()) {        // kym neskonci vstup
            String word = s.next();  // nacitame slovo
            if (word.equals("END")) { // skoncili sme ak najdeme END
                break;
            }
            System.out.println("Slovo " + num + ": " + word); // vypiseme slovo
            num++;
        }
    }
}

Príklad behu programu:

Ahoj
Slovo 0: Ahoj
1 2 3 END
Slovo 1: 1
Slovo 2: 2
Slovo 3: 3
  • Metóda nextInt() vráti ďalšie slovo konvertované na int (pozri program s rekurziou vyššie). Či nasleduje číslo, si môžeme vopred overiť metódou hasNextInt(). Podobne nextDouble().

Práca so súbormi

Čítanie zo súboru funguje podobne ako čítanie z konzoly, iba inak inicializujeme použitý objekt:

  • Scanner vytvoríme príkazom Scanner s = new Scanner(new File("vstup.txt"));
    • File reprezentuje súbor s určitým menom, potrebujeme pridať import java.io.File; alebo import java.io.*;
  • BufferedReader vytvoríme príkazom BufferedReader in = new BufferedReader(new FileReader("vstup.txt"));
  • Scanner aj BufferedReader umožňujú zavrieť súbor metódou close()

Písanie do súboru môžeme robiť napr. triedou PrintStream

  • Otvorenie súboru: PrintStream out = new PrintStream("vystup.txt");
  • Potom používame staré známe metódy print, println, format ako pri System.out (napr. out.println("Ahoj"))
  • Na konci zavoláme out.close()
  • Tento spôsob otvárania súborov existujúci obsah premaže
  • Ak chceme pridávať na koniec súboru, použijeme PrintStream out = new PrintStream(new FileOutputStream("vystup.txt",true));

Matematika a pseudonáhodné čísla

  • V triede Math nájdete rôzne matematické konštanty a funkcie
  • Napr. Math.PI, Math.cos(x), Math.min(x,y), Math.pow(x,y), ...
  • Triedy na prácu s veľkými číslami a ďalšie matematické funkcie nájdete v balíčku java.math

Pseudonáhodné čísla

  • Math.random() vygeneruje double z intervalu [0,1)
  • Väčšie možnosti poskytuje trieda Random v balíčku java.util (generuje celé čísla, bity), umožňuje nastaviť počiatočnú hodnotu