Dnes v tomto seriálu přijdou na řadu cykly, jež umožňují vykonávat daný blok programu opakovaně, a které jsou společně s podmínkami základními stavebními kameny všech programů. Jejich možnosti si předvedeme na skromné vlastní implementaci třídy Math.

Třída MyMath

Třídu, kterou dnes budeme programovat, nazveme MyMath, abychom nekolidovali s jménem již zmíněné třídy Math, což by nás nutilo při jejím případném použití specifikovat vždy plně kvalifikované jméno (což by bylo nešikovné).

Všechny metody naší třídy budou statické, abychom umožnili jejich použití bez konstrukce instance (třídní proměnné ani nejsou zapotřebí). Protože vytváření instancí ani nemá smysl, tak deklarujeme prázdný soukromý konstruktor, čímž znemožníme jeho volání (respektive volání new).

Final

Aby restriktivním opatřením nebyl konec, tak do deklarace třídy vepíšeme klíčové slovo final. To, co v Javě označíme jako final, je neměnné a nelze to jakkoliv upravovat. V případě třídy to znamená, že z ní nepůjdou odvodit žádné podtřídy, což by také nemělo smysl, protože všechny metody jsou statické a ty vztahují se pouze k této konkrétní třídě. V případě metody to znamená, že se v případném podtypu již nesmí znovu deklarovat (překrýt). A konečně u proměnné, že do ní již po její okamžité inicializaci nesmíme vložit jiná data (buď primitivní, nebo odkaz na jiný objekt).

Final proměnné jsou ale menším chytákem. Zatímco u proměnných primitivního typu vše funguje, jak bychom očekávali, tak u referencí se ono omezení vztahuje pouze na referenci samotnou, nikoliv na celý objekt. Proto můžeme skrz final referenci bez problémů modifikovat stav objektu, na nějž ukazuje (nemůžeme pouze do reference přiřadit jiný objekt).

Konstanty

Pro definování konstant používáme kličové slovo final v kombinaci se slovy public a static. Takto definované proměnné budou veřejné, neměnné a sdílené prostřednictvím třídy. Konstanty pojmenováváme striktně velkými písmeny a čísly, jednotlivá slova v jejich názvu oddělujeme podtržítky.

public final class MyMath {
    public static final double PI = 3.141592654;
    public static final double EULER_NUMBER = 2.718281828;
    /**
     * Znemoznime vytvareni instanci
     */
    private MyMath(){}
}

While

Nejjednodušším druhem cyklu je klíčové slovo while, po kterém následuje podmínka v kulatých závorkách a blok, který chceme opakovat. Kód umístěný v tomto bloku se provádí tak dlouho, dokud je splněna podmínka.


Příklad

Funkce faktoriál čísla n je definována na přirozených číslech jakožto součin všech čísel 1 až n. Protože tato funce roste obrovskou rychlostí, tak výsledek nebudeme předávat jako celočíselný typ int, ale jako typ double. Double má jednak výrazně větší rozsah hodnot, které může reprezentovat, a druhak má ochranu proti přetečení. Pokud k maximální hodnotě integeru přičteme číslo 1, tak přetečeme zpět do záporných čísel. V případě double dostaneme hodnotu +inf.

    public static double factorial(int nr){
        if(nr < 0) return -1;
        double result = 1;
        int i = 2;
        while(i <= nr){
            result *= i;
            i++;
        }
        return result;
    }

Častou pastí cyklu while je opomenutí inkrementace řídicí proměnné, které vyústí v nekonečnou smyčku.

Do while

Obdobou cyklu while je konstrukce do while. Fakticky se jedná o totožný příkaz s tím rozdílem, že před blok píšeme klíčové slovo do a za blok uvádíme slovo while následované podmínkou a středníkem. Podmínka ukončení cyklu je proto kontrolována až po provedení bloku a kód v bloku uvedený tedy proběhne vždy alespoň jednou.


Příklad

Euklidův algoritmus slouží k nalezení nejvyššího společného dělitele dvou čísel.

Algoritmus v každém svém kroku (iteraci cyklu) vydělí se zbytkem první číslo A druhým číslem B. Pokud zbytek není nulový, tak se do A přiřadí číslo B a zbytek po dělení se přiřadí do právě uvolněné proměnné B, a celá procedura se opakuje.

V okamžiku, kdy je zbytek po dělení nulový, tak je v B uložen nejvyšší společných dělitel původních dvou čísel.

Pro čísla 133 a 15 by byl postup následující:

133 = 8 \\cdot 15 + 13
15 = 1 \\cdot 13 + 2
13 = 6 \\cdot 2 + 1
2 = 2 \\cdot 1 + 0

Nejvyšším společným dělitelem 133 a 15 je číslo 1.

Toto vysvětlení bylo skutečně jenom telegrafické a nic nevysvětlující. Pokud máte zájem pochopit, proč tento algoritmus funguje, tak si přečtěte tento článek. Pokud se spokojíte s tvrzením, že takto to skutečně funguje, tak si pouze projděte (a zkuste sami napsat) kód tohoto algoritmu.

/**
 * Vrátí nejvyssiho spolecneho delitele cisel A a B
 * @param a cislo A
 * @param b cislo B
 * @return gcd(a, b), -1 v pripade neplatnych argumentu
 */
    public static int gcd(int a, int b) {
        if (a < 1 || b < 1) return -1;
        int remainder = 0;
        do {
            remainder = a % b; //v tento okamzik v posledni iteraci plati ona podminka, ze zbytek == 0
            a = b; //ale kvuli dalsi pripadne iteraci posunujeme promenne
            b = remainder; //v b je proto 0, v a je gcd
        } while (b != 0);
        return a;
    }

For smyčka

Nejsložitějším, ale nejpoužívanějším cyklem, je smyčka for. V kulatých závorkách se za klíčovým slovem skrývá hned trojice příkazů oddělených středníkem. První z nich je deklarace řidicích proměnných cyklu, druhým je podmínka provedení cyklu (tj. ta podmínka, kterou známe z while). Posledním příkazem je změna hodnoty řidicích proměnných, která se provede vždy po provedení cyklu.

V první části příkazu můžeme hromadně nadeklarovat více proměnných a v poslední části můžeme čárkou oddělit více přiřazení nových hodnot. Tohoto postupu se ale obvykle nevyužívá, protože znepřehedňuje kód.

Výhodou tohoto cyklu je, že se všechny potřebné části deklarují na jednom místě a nemůže se stát, že bychom zapomněli například na změnu hodnoty proměnných.

Příklad 1

Nejprve si ukážeme funkci suma, která sečte všechna čísla až do zadané horní meze (včetně).

    /**
     * Secte vsechna cisla az do nr (vcetne)
     * @param nr mez scitani (vcetne)
     * @return soucet
     */
    public static int sum(int nr){
        int sum = 0;
        for(int i = 1; i <= nr; i++){
            sum += i;
        }
        return sum;
    }

Příklad 2

Fibonacciho posloupnost byla vymyšlena italským matematikem dvanáctého století Leonardem Pisanem jako model přírůstku králíků. Tato posloupnost se řídí těmito pravidly:

  • Na začátku se narodí 1 pár králíků.
  • Králící se mohou množit od druhého měsíce života.
  • Každý produktivní pár zplodí měsíčně jeden pár králíků.
  • Králíci neumírají, pokud jednou začnou plodit, tak plodí pořád.

Matematicky zapsáno tedy spočítáme hodnotu n-tého čísla jako:

F(n) = F(n-1) + F(n-2), \\; \\; \\; F(0) = 0, \\;\\; F(1) = 1
    /**
     * Fibonacciho posloupnost dynamicky
     * @param index poradi cisla (pocitano od 0)
     * @return Fibonacciho cislo na danem poradi
     */
    public static int fibonacci(int index) {
        if (index == 0) {
            return 0;
        }
        if (index == 1) {
            return 1;
        }
        int result = 0; //vysledek

        int prePre = 0; //predminule cislo
        int pre = 1; //minule cislo
        for (int i = 1; i < index; i++) { //pocitame od druheho indexu
            result = prePre + pre; //vysledek je soucet dvou minulych cisel
            prePre = pre; //v dalsim kroku je minule predminulym
            pre = result; //a vysledek je minulym cislem
        }
/* Takto by to slo take zapsat, ale je to hodne neprehledne */
//      for (int i = 1, prePre = 0, pre = 1; i < index; i++, prePre = pre, pre = result) { 
//          result = prePre + pre; //vysledek je soucet dvou minulych cisel
//      }
        return result;

Break, continue

Vykonávání bloku všech zde zmíněných cyklů lze ovlivnit pomocí příkazů break a continue. Příkaz break ukončí vykonávání cyklu a program bude pokračovat za uzavírací závorkou jeho bloku. V případě continue cyklus přejde ihned do další iterace.

        //Vypise licha cisla na intervalu <1; 10>
        for (int i = 1; i <= 10; i++) {
            if(i % 2 == 0) continue;
            System.out.println(i);
        }
        
        //Vypise cisla 1 az 6
        for (int i = 1; i <= 10; i++) {
            if(i == 7) break; //zpusobi ukonceni vykonavani cyklu
            System.out.println(i);
        }

Návěští

Break a continue se vztahují vždy k bloku, ve kterém jsou obsaženy, což je velmi omezující v případě vnořených cyklů. U těchto občas chceme pokračovat (continue) v až některém z nadřazených cyklů, případně chceme tento nadřazený cyklus opustit (break).

K tomuto v Javě existují návěští, což jsou pojmenování jednotlivých cyklů. Tyto názvy deklarujeme před deklaraci daných cyklů a píšeme za ně vždy dvojtečku.

Pokud se chceme odvolat na nějaký obalující cyklus, tak v příkazech break a continue můžeme název návěští vepsat přímo za tato klíčová slova.

        //Nedela nic zajimaveho, demonstruje break a continue
        Outer:
        for (int i = 1; i <= 10; i++) {
            Inner:
            for(int j = 0; j < 20; j++){
                System.out.println(i*j);
                if(i*j == 140) continue Inner;
                else if(i*j == 150) break Outer;
            }
        }

Aktivní čekací smyčka

Ve svých budoucích programech se dostaneme do situace, ve které budeme chtít program na nějakou chvíli zastavit nebo zpomalit. Poměrně intuitivní cestou by bylo přidat smyčku, která udělá naprázdno třeba miliardu cyklů – to by určitě nějakou chvíli zabralo.

Tato myšlenka má však zásadní nedostatek v podobě optimalizace kódu. První optimalizaci může udělat již kompilátor do bytekódu, když si všimne, že je v programu kus, který nic nedělá a požírá pouze zdroje. Proto tento kus vyhodí (čímž zrychlí program, aniž by ovlivnil to, co dělá). S obdobnou logikou by postupoval při svých optimalizacích virtuální stroj a taktéž by tuto pasáž zlikvidoval.

Pokud by někoho již dnes zajímalo, jak by se tato sitace řešila, tak existuje volání Thread.sleep(long milis), které aktuální vlákno uspí na zadaný čas v milisekunách. Toto volání však vyžaduje ošetření výjimek, které jsme si zatím neuvedli.








Doporučujeme