Java (5) - Objekty

Minule jsme si ukázali třídu Math, která agreguje matematické operace, které nejsou soušástí jazyka jako takového. Tentokrát si ukážeme třídu pro práci s řetězci String a zjistíme, že objekty nejsou jen operace, ale také data. Dále si řekneme, co to jsou balíčky a také se naučíme vytvářet vlastní třídy (a jejich instance).

Třída String

S řetězci jsme již pracovali v druhém dílu tohoto seriálu (První program). Naučili jsme se, že řetězce se dají vytvořit tak, že literál uzavřeme z obou stran do horních uvozovek. Co jsme si ale neřekli je, že se nad jednotlivými řetězci dají volat operace naprosto stejným způsobem, jako jsme tak činili v dílu minulém nad třídou Math.

        String s = "Skakal pes pres oves"; //retezec
        s = s.substring(0, 6); //prvnich sest znaku
        System.out.println(s); //"Skakal"
        String concat = s + " pes"; //zretezeni vice retezcu
        System.out.println(concat); //"Skakal pes"

Tento příklad obsahuje jeden drobný chyták. Jednotlivé řetězce jsou immutable, což znamená, že jejich obsah nemůžeme nijak měnit (pro zkušenější mohu prozradit, že řetězec je implementován jako pole znaků). Tudíž všechny modifikující operace vytvářejí vždy nový řetězec a musíme proto odkaz na tento modifikovaný řetězec uložit znovu do proměnné (na druhé řádce).

Třídy a instance

Co si ale vlastně pod pojmem třída představit? Třída je forma, jež definuje jasně ohraničený soubor (typů) dat a operací nad nimi. Pomocí této formy pak vytváříme instance, jednotlivé objekty, které obsahují samotná data (nad kterými jsou volány příslušné operace).


Příklad

Mějme třídu Člověk, tato třída definuje, že člověk má jméno a věk a obsahuje operaci představSe(), která vypíše zadané jméno a věk. Pomocí této šablony – třídy – pak vytváříme jednotlivé instance, v reálném životě bychom řekli lidi.

Každé nově vytvořené instanci v programu přiřadíme data (jméno, věk). Když později zavoláme operaci představSe() nad tímto objektem (instancí), tak nám vypíše hlášku specifickou pro daného člověka.

K čemu je to dobré?

Z tohoto příkladu není úplně patrná obrovská síla, kterou objektové programování nabízí. Tato síla se nám bude odkrývat postupně společně s konstrukcemi, jak si je budeme představovat v dalších dílech.

Pro lepší představu uvedu, že se v dají například konkretizovat podtypy jednotlivých tříd (Zvíře → Pes, Kočka) a vytvářet celé hierarchie tříd. Pomocí objektů lze také efektivně skrývat konkrétní implementaci před jejími uživateli, což nám umožní dělat změny v knihovnách, aniž by došlo k narušení závislých programů.

Úkrok stranou – balíčky

Vzhledem k tomu, že v každém programu jsou minimálně desítky tříd a navíc používáme při programování knihovny, tak se velmi brzo dostaneme do situace, kdy pojmenujeme třídu duplicitním názvem. Abychom mohli tyto třídy vzájemně odlišit, tak Java používá balíčky.

Balíčky jsou logickým seskupením několika tříd, které se fyzicky projevuje jako adresář (každé veřejné třídě odpovídá jeden soubor). V rámci zdrojového souboru vždy deklarujeme balíček a volitelně vyjmenováváme třídy z ostatních balíčků, které používáme (import).

Pokud bychom třídy nevyjmenovali, tak k nim musíme vždy přistupovat pomocí plně kvalifikovaného jména, což je cesta k dané třídě v tečkové notaci (místo lomítek jsou použity tečky, pro import obsahu balíčku lze použít divokou kartu *, což se ale příliš nedoporučuje).

Java implicitně provádí import java.lang.*. V tomto balíčku je například jak třída Math, tak třída String, proto jsme je doposud nemuseli nijak uvádět.

Samozřejmě jsme ale mohli v kódu k daným třídám přistupovat pomocí plně kvalifikovaného jména (což je ale poněkud rozvláčné).

        java.lang.String s2 = "plne kvalifikovane";
        double i = java.lang.Math.sqrt(4); //2.0

Deklarace třídy

Pro deklaraci třídy používáme klíčové slovo class, před kterým je specifikátor přístupu, a za kterým následuje název třídy. Samotné tělo třídy je uzavřeno v bloku (složené závorky).

Pravidla a konvence

Při vytváření tříd platí následující pravidla a konvence. Název třídy by měl začínat velkým písmenem a pokračovat camelCasem. Název by měl být výstižný, popisující a měl by být podstatným jménem. Dále pokud se rozhodneme použít specifikátor přístupu public, tak jméno souboru (*.java) musí být totožné s názvem třídy (z toho plyne, že daný soubor může obsahovat pouze jednu veřejnou třídu).

Vypadá to sice složitě, ale ve skutečnosti to zcela odpovídá logice toho, co si přejeme udělat, takže si na to není problém zvyknout.

Specifikátory přístupu

Pro přístup k jednotlivým třídám, jejich operacím a proměnným používáme specifikátory přístupu. Jejich význam je především v zakrývání implementačních detailů, které nemá (nesmí) uživatel našich kódů vidět a případně je použít. Uvažujme třeba, že pracujeme pro Pentagon. Samozřejmě, že naše procesy obsahují to nejlepší zabezpečení, ale stejně by asi nebylo moudré, aby kdokoliv, kdo používá naší knihovnu, mohl zavolat operaci NuclearSilo.fire().

SpecifikátorPovolený přístup
publicZ libovolné třídy.
privatePouze zevnitř dané třídy, žádný přístup z vnějšku.
protectedZ kterékoliv třídy téhož balíku, případně z potomka třídy kdekoliv.
žádný (package friendly)Z kterékoliv třídy téhož balíku.

Definice třídy

Nyní již máme dostatek znalostí k tomu, abychom mohli vytvořit třídu Animal, která bude sloužit jako schránka na data týkající se nějakého zvířátka. Tuto třídu v dalším dílu tohoto seriálu obohatíme o operace.

V balíčku beings (pro jména balíčků obvykle používáme pouze malá písmena a číslovky) vytvoříme veřejnou třídu Animal (soubor Animal.java). Zvířátko bude obsahovat proměnnou sound, jež bude obsahovat řetězec popisující zvuk, který dané zvíře vydává a proměnnou kind popisující druh zvířete (také řetězec).

package beings; //oznaceni balicku

import java.lang.String; //toto se deje implicitne - je to tu pro ilustraci importu

/**
 * Toto je zviratko, aktualne je pomerne dost hloupe, ale priste jiz bude
 * o mnoho chytrejsi a nebezpecnejsi
 * @author Pavel Micka
 */
public class Animal {
    /**
     * Druh zviratka
     */
    public String kind;
    /**
     * Zvuk, ktery vydava
     */
    public String sound;
}

Použití třídy

Pokud chceme tuto třídu použít, tak musíme nejprve vytvořit její instanci. Toto se dělá pomocí klíčového slova new, které vrací ukazatel (referenci) na paměťové místo, ve kterém se instance nachází. Na práci s proměnnou ani se samotnou instancí toto nemá vliv – pracujeme s ní stejně jako s instancí řetězce – tato znalost je pouze důležitá pro pochopení základních principů jazyka.

Dokud referenci nepřiřadíme instanci, tak ukazuje do prázdna (ukazatel null). Pokud bychom se pokusili nad nezinicializovanou proměnnou zavolat nějakou operaci, tak kód ani nepůjde zkompilovat. Pokud bychom se pokusili zavolat operaci nad proměnnou ukazující z jakéhokoliv důvodu na null, tak se dočkáme havárie v podobě výjimky NullPointerException.

package objects; //jsme v balicku objects
import beings.Animal; //abychom mohli zviratko pouzit primo, tak jej importujeme

/**
 * Trida neni verejna, nemusi se jmenovat stejne jako soubor
 *
 * Tvorba trid pojmenovanych jinak nez soubor neni dobrou praktikou (v tomto
 * pripade se soubor jmenuje Main.java). Tento priklad slouzi spise pro
 * ilustraci moznosti
 * 
 * @author Pavel Micka
 */
class Main2 {

    /**
     * Vstupni mod aplikace
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        Animal alik = new Animal(); //deklarace s inicializaci
        alik.kind = "pes";
        alik.sound = "haf haf";

        //Nyni vytvorime dalsi zviratko - kocku
        Animal micina = new Animal(); //vytvorime novou instanci zvirete
        micina.kind = "kocka";
        micina.sound = "mnaaaauuu";

        System.out.println("Ja jsem " + alik.kind + " a delam " + alik.sound);
        System.out.println("Ja jsem " + micina.kind + " a delam " + micina.sound);


        /* Ukazka NullPointerException, odkomentujte */
        //alik = null; //inicializace do prazdna
        //alik.kind.substring(2);
    }
}

Kód tohoto projektu si můžete stáhnout zde.

Literatura

  • HORTON, Ivor. Java 5. Praha : Neocortex spol s.r.o., 2005. 1443 s. ISBN 80-86330-12-5.







Doporučujeme