Java interface tajemství zbavený

Je to asi rok, kdy jsem se začal učit objektově orientovanému programování. Tehdy jsem byl v zaměstnání donucen naučit se OOP principům a to hlavně v Javě. Vzpomínám si, jaké problémy mi dělalo, než jsem pochopil význam a použití rozhraní (interface) v Javě. Tento článek je tedy zamýšlen jako taková pomůcka, která by měla objasnit používání rozhraní a to hlavně začátečníkům. Pro pochopení tohoto článku byste měli zvládat minimálně základy v Javě. Nenechte se zmást, v článku budu používat slova rozhraní i interface, které zde mají stejný význam.

Co to ten interface tedy je?

Začnu z té technické stránky věci. V Javě existují klasické třídy, abstraktní třídy a rozhraní. Zde je jejich popis:

  • Normální třída: je to ta třída, která nemá žádný z níže uvedených modifikátorů. Všechny metody, které tato třída má, mají svou implementaci. To znamená, že už obsahují kód, který něco dělá.
  • Abstraktní třída: je to třída, která má modifikátor abstract. To znamená, že chování některých metod nemusí tato třída implementovat. V tom případě může být implementace těchto metod přenechána třídám, které tuto třídu rozšiřují (dědí z ní, neboli jsou jejími potomky).
  • Rozhraní: má modifikátor interface. Rozhraní neobsahuje žádnou implementaci. Definují pouze metody, které ale neobsahují žádný kód.

(jak jsem byl upozorněn v komentáři pod článkem, abstract a interface nejsou modifikátory viz tento komentář. Až na zmatení pojmu jsou ale výše uvedené tvrzení správné :) )

Proč tedy vzniklo v Javě rozhraní? Jedním z důvodů byla skutečnost, že v Javě neexistuje vícenásobná dědičnost. Třída může mít pouze jednoho předka (to znamená, že může rozšířit pouze jednu třídu). Toto omezení má své důvody (vícenásobná dědičnost může způsobovat problémy). Použití rozhraní ve třídě označujeme jako „třída implementuje rozhraní“.

Uvedu příklad:

public interface Zobrazovac {
    void ukazZpravu(String zprava);
}

Jak vidíte, rozhraní Zobrazovac má pouze jednu metodu. Tato metoda má jeden parametr, který obsahuje textový řetězec. Podle názvu si domyslíme, co tato metoda dělá. Netušíme ale, jakým způsobem to dělá a co k tomu používá. To ani vědět nechceme. Chceme jen uživateli zobrazit zprávu, víc nás nezajímá. Nyní je třeba vytvořit nějakou třídu, která bude toto rozhraní implementovat. Pro jednoduchost vytvoříme třídu TextovyZobrazovac, která zprávu zobrazí v konzoli.

public class TextovyZobrazovac implements Zobrazovac {
   // Zde je implementace metody ukazZpravu z rozhrani Zobrazovac.
    void ukazZpravu(String zprava) {
        // Vytiskneme zpravu na konzoli.
        System.out.println(zprava);
    }
}

Teď si asi říkáte, proč to dělat tak složitě. Vždyť bychom se bez toho rozhraní obešli a ušetřili bychom si čas. Teď si ale představte, že budete chtít zprávu zobrazit nejen do konzole, ale i do souboru a v některých případech zprávu zobrazit jako formátovánou HTML stránku. Kdyby nebylo vytvořeno rozhraní Zobrazovac, museli byste vytvořit několik tříd, které by se staraly o různé způsoby zobrazení zprávy. Při jejich použití v programu byste ale museli vědět, co to jsou za třídy, jaké metody pro zobrazení zprávy používají a případně znát další detaily.

V našem případě jsme si ale zavedli rozhraní Zobrazovac a tudíž můžeme ve svém kódu vytvořit třeba pole typu Zobrazovac, do kterého budeme ukládat jednotlivé zobrazovače, které mají být aktivní. V případě potřeby potom projedeme všechny prvky tohoto pole a zavoláme nad nimi metodu ukazZpravu(). Víme totiž, že všechny instance (různých) tříd v poli implementují Zobrazovac, tudíž tuto metodu mají. Nemusíme se tedy starat o to, jaké třídy vlastně máme, víme jen, že umí zobrazit nějakým způsobem zprávu.

Příklad ze života

Pokusím se princip rozhraní vysvětlit na nějakém příkladu ze života. Vemte si třeba počítačovou myš. Žádnou konkrétní, jen si představte zjednodušený model myši. Představte si myš jako rozhraní, které nám poskytuje kliknutí levým tlačítkem a kliknutí pravým tlačítkem. Dejme tomu, že toto je základní rozhraní pro všechny myši. Když se naučíte toto rozhraní, budete umět ovládat všechny myši, které mají toto rozhraní. Bude vám jedno, jestli bude myš plastová, nebo kovová. Optická nebo kuličková. Modrá nebo bílá. Kulatá nebo hranatá. Vám stačí jen vědět, že s ní můžete provést klik levým, nebo pravým tlačítkem.

Tyto dvě tlačítka jsou tedy rozhraní myši a vám je upřímně jedno, co se při stisku tlačítka děje. Neřešíte, jak to uvnitř myši funguje – jestli jsou tam nějaké mechanické části, nějaké optické čidla, nebo skřítci, kteří sledují pohyb tlačítka. Stejně je to i s rozhraním v Javě. Na úrovni rozhraní nevidíte dovnitř – nevíte jak se daná úloha provede.

Teď si představte, že se vám do ruky dostane nějaká herní myš, která bude krom hlavních dvou tlačítek obsahovat desítky dalších tlačítek a koleček. I přesto ale budete tuto myš schopni ovládat, protože pořád implementuje rozhraní klasické myši – má přeci levé a pravé tlačítko. Zbytek vás v danou chvíli nezajímá. Stejně i v našem příkladě nás nezajímá, co všechno třída TextovyZobrazovac všechno umí. Stačí jen vědět, že implementuje rozhraní Zobrazovac, díky kterému ji můžeme používat pro naše účely.

Ukažme si tedy příklad s myšmi v Java kódu:

/**
 * Rozhraní klasické myši. Jen dvě tlačítka, které lze stisknout.
 */
public interface KlasickaMys {
    void kliknutiLevymTlacitkem();
    void kliknutiPravymTlacitkem();
}
 
/**
 * Tato myš je úplně obyčejná myš, která nemá žádné funkce navíc.
 */
public class UplneObycejnaMys implements KlasickaMys {
    void kliknutiLevymTlacitkem() {
        // Zde bude napsaný kód, který se provede při stisku levého tlačítka.
        // Tato metoda je implementací metody z rozhraní KlasickaMys
    }
    void kliknutiPravymTlacitkem() {
        // Zde bude napsaný kód, který se provede při stisku pravého tlačítka.
        // Tato metoda je implementací metody z rozhraní KlasickaMys
    }
}
 
/**
 * Tato myš má v sobě zabudovanou lampičku.
 */
public class MysSLampickou implements KlasickaMys {
 
    void kliknutiLevymTlacitkem() {
        // Zde bude napsaný kód, který se provede při stisku levého tlačítka.
        // Tato metoda je implementací metody z rozhraní KlasickaMys
    }
    void kliknutiPravymTlacitkem() {
        // Zde bude napsaný kód, který se provede při stisku pravého tlačítka.
        // Tato metoda je implementací metody z rozhraní KlasickaMys
    }
    void rozsvitLampicku() {
        // Jak vidíte, toto je funkce navíc, kterou klasické myši nemají.
    }
}

Tak a teď zkusíme tyto myši použít. Vytvoříme třídu Uzivatel, která bude obsahovat pole s objekty implementujícími interface KlasickaMys. Nejdříve toto pole naplníme nějakými objekty a následně projedeme všechny prvky pole a otestujeme na nich klepnutí na levé a pravé tlačítko.

public class Uzivatel {
 
     private KlasickaMys[] poleMysi;
 
      /**
      * V konstruktoru vytvoříme instance několika myší, které má uživatel
      * k dispozici, aby je poté mohl vyzkoušet.
      */
     public Uzivatel() {
         // Vytvoření pole, do kterého umístíme dvě různé myši, které budou implementovat interface KlasickaMys
         poleMysi = new KlasickaMys[2];
 
         // Přidáme do pole s myšmi obyčejnou myš.
         KlasickaMys mys1 = new UplneObycejnaMys();
         poleMysi[0] = mys1;
 
         // Přidáme do pole s myšmi myš s lampičkou.
         KlasickaMys mys2 = new MysSLampickou();
         poleMysi[1] = mys2;
     }
 
     /**
      * Této metodě předáme v parametru jakýkoliv objekt, který implementuje interface KlasickaMys.
      * Díky tomu víme, že můžeme otestovat stisk levého a pravého tlačítka. To co se po kliknutí
      * stane už záleží na konkrétní implementaci. U myši s lampičkou zde ale nemůžeme otestovat
      * funkci rozsvitLampicku(), protože není obsažená v rozhraní KlasickaMys.
      */
     public void otestujMys(KlasickaMys mysNaOtestovani) {
         mysNaOtestovani.kliknutiLevymTlacitkem();
         mysNaOtestovani.kliknutiPravymTlacitkem();
     }
 
     /**
      * V této metodě si uživatel zkusí u každé myši uložené v poli poleMysi
      * kliknutí levým i pravým tlačítkem.
      */
    public void vyberMys() {
        for (int i = 0; i < poleMysi.lenght; i++) {
            otestujMys(poleMysi[i]);
        }
    }
 
}

Když pak vymyslíme nějakou novou, lepší myš, jednoduše vytvoříme instanci nové myši a vložíme ji do pole s myšmi. Nemusíme už tedy měnit žádné další části programu, protože každá myš, která implementuje rozhraní KlasickaMys bude umět pořád ten stejný základ – kliknutí levým a pravým tlačítkem.

Tak jako ve skutečném světě, i ve svém programu byste měli skrývat detaily o implementaci. U myši taky určitě nechcete vědět, co všechno se děje při stisku tlačítka. Nechcete zkoumat při koupi nové myši, jak se ovládá. Chcete nějaké jednotné rozhraní, které bude společné pro všechny myši. I u auta vás bude zajímat především jeho rozhraní. Když sednete do nového auta a bude mít stejné rozhraní (volant, pedály…), budete ho umět ovládat. Teď si ale představte, že byste sedli do nového auta a tam by místo volantu a pedálu byl umístěn knipl jako v letadle. Toto auto by neimplementovalo rozhraní klasického auta a proto byste ho museli používat zcela jinak než klasické auta.

Shrnutí

Jak vidíte, používání rozhraní přináší mnohé výhody. I když to zpočátku vypadá, že používat rozhraní je zbytečná práce navíc, není to tak. Pokud se budete snažit používat rozhraní co nejvíce, uvidíte, že si ušetříte spoustu času. Jakmile zjistíte na vlastní kůži ty výhody, určitě začnete rozhraní používat pravidelně.

Pokud vám přiložené příklady nestačily, na internetu najdete určitě spoustu dalších příkladu. Další možná ukázka rozhraní je při použití Observeru (Observer, Listener…to jsou slova které si zkuste vyhledat). Po přečtení tohoto článku si zkuste vymyslet nějaké další příklady, kde by bylo vhodné použít rozhraní. Zkuste si tyto příklady převést do kódu a naprogramovat to.

Pokoušel jsem se článek napsat co nejjednodušeji. Pokud se vám ale bude zdát něco nesrozumitelné, budu velmi rád, když mě na to upozorníte v komentáři pod článkem.

Další články zde na webu:


32 Responses to “Java interface tajemství zbavený”

  • David Nogol Says:

    Uff, konečně. Článek mírně upraven. Snad už nedostanu chuť to překopat :D http://tinyurl.com/crb74v

  • Luba Says:

    Výborný a srozumitelný článek napsaný lidovou řečí a praktický, perfektně vysvětlený příklad. Článek mi jakožto zelenáčovi v Javě hodně pomohl a autorovi za něj děkuji.

  • dadajax Says:

    Děkuji za pochvalu. Jsem rád, že článek splnil svůj účel a dokázal problematiku rozhraní trochu objasnit :)

  • Miky Says:

    Přidávám se k pochvale! Snad se konečně podaří napsat tu písemku :) Díky.
    Jen bych možná trochu upřesnil jednu větu:“Víme totiž, že všechny třídy v poli implementují Zobrazovac,…“ na „Víme totiž, že všechny instance (různých) tříd v poli implementují Zobrazovac,…“. Možná se ale pletu.

  • dadajax Says:

    Miky: děkuji za pochvalu a za opravení, samozřejmě že máš pravdu. Blbě jsem to napsal :) Hned to opravím

    Tak hodně štěstí u té písemky ;)

  • MK Says:

    Děkuji a zdravím ze studijního pobytu ze španělska, kde jsem interface nepochopil v angličtině a snažil se najít srozumitelné vysvětlení v českém jazyce. A našel jsem :-)

  • dadajax Says:

    MK: jsem rád že článek pomohl :) Posílám pozdrav do Španělska ;)

  • root Says:

    Skutecne velmi srozumitelne. Dik moc.

  • rudyment Says:

    Drobná korekce: interface není modifikátor, ale určení druhu datového typu. Kdyby to byl modifikátor, muselo by se psát

    public interface class

    Java prostě zavádí tři druhy datových typů: class, enum a interface. Druh class pak má navíc poddruh abstract class.

  • rudyment Says:

    Je-li tu víc takových, kteří trochu tápou s významem a používáním rozhraní, tak jsem včera odevzdal do nakladatelství CP učebnici programování, v jejímž prvním dílu se vůbec nevykládá dědičnost tříd, ale veškerý výklad je postavený na používání rozhraní.

    Rukopis četlo asi 10 lidí, kteří se přihlásili na výzvu v konferencích, a všichni si jej chválili. Tak možná pomůže i některým tápajícím.

    Tento text má svůj anglický protějšek na stránkách Sunu. Teď jej ale předělávám, tak zatím ani neříkám adresu.

  • dadajax Says:

    Rudymet: Děkuji za opravení, článek opravím.

  • MK Says:

    2 rudiment:
    chtěl jsem se zeptat, za jak dlouho myslíš, že by ta učebnice mohla vyjít? a ve španělsku asi dostupná moc nebude, co? myslíš, že by byla nějaká možnost dostat se k tomu rukopisu? jde o to, že by se mi tady hodilo něco o JAVĚ v CZ… neříkám, že články na internetu jsou špatné, ale přece jenom by to chtělo něco ucelenějšího…
    díky za odpověď…

  • Kamilos Says:

    Zdravím,

    díky za článek. Pro začátečníky je určitě vhodný, ale nemůžu si odpustit malou výtku. V příkladu se objevuje:

    UplneObycejnaMys mys1 = new UplneObycejnaMys();

    ale mělo by být
    KlasickaMys mys1 = new UplneObycejnaMys();

    jde o detail ale, začátečníci by se to měli učit už od začátku správně. Kolirát vidím u pokročilých programátorů jak píší
    HashSet s = new HashSet()
    namísto
    Set s = new HashSet()

    a je to proto, že ani kvalitní knížky pro začátečníky tohle nezdůrazňují!

  • dadajax Says:

    Kamilos: děkuji za upozornění. Opraveno ;)

  • Uf Says:

    Ahoj, jeste upresneni:
    Slovo ROZHRANI (angl. INTERFACE) ma dva vyznamy:
    1) obecny, ktery oznacuje seznam toho, co objekt umi – tedy zkracene seznam metod, public memberu. Seznam vsech metod, tedy i zdedenych od predku a implementovanych rozhrani. 2) abstraktni datovy typ (ADT) ve smyslu, v jakem je popisovan v clanku.

    Pekny clanek. Zvlastni, ze nekdo pise verejne po roce a nekdo ani po letech. Uf.

  • dadajax Says:

    Uf: Ahoj, děkuji za doplnění ;) Chtěl jsem využít toho, že mám ještě v živé paměti ty své začátky a pamatuju si na ty problémy, které jsem tehdy měl. Tomu jsem se snažil přizpůsobit i ten styl psaní a ty zvolené příklady. Čím více toho ale vím, tím více mi přijde absurdní, jak jsem něco podobného nemohl pochopit :)

  • Mato Says:

    Ja sa len chcem opytat ci
    private KlasickaMys[] poleMysi; by nemalo byt staticke:private static KlasickaMys[] poleMysi; pretoze bez toho mi nepojde metoda OtestujMys….

  • dadajax Says:

    Mato: nevidím důvod, proč by mělo bát poleMysi statické. Co to znamená, že ti bez toho nepůjde metoda otestujMys?

    Metodu otestujMys musíš volat nad nějakou instancí třídy Uzivatel. Děláš to?

  • Mato Says:

    No ano ze musim to nad niecim volat but ….

    Uzivatel Mato = new Uzivatel();
    Mato.otestujMys(poleMysi[0]);//tu mi pise error ze cannot make a static reference to the non-static field poleMysi a funguje to ked tam hodim static lenze to dost meni potom tuto triedu

  • dadajax Says:

    poleMysi je privátní atribut třídy Uzivatel. Nemůžeš jej tedy používat z vnějšku třídy Uzivatel. Pokud chceš zavolat metodu otestujMys, tak musíš ještě vytvořit krom instance třídy Uzivatel taky instanci nějaké třídy, která dědí z rozhraní KlasickaMys.

    Takže v tvém případě:
    Uzivatel Mato = new Uzivatel();
    Mato.otestujMys(new UplneObycejnaMys() );

  • mato Says:

    diki moc uz to ide …

  • David42 Says:

    Nějak se mi rozhraní nedaří pochopit stále. Připadá mi to, že je to jen proto, že když implementuji nějaké rozhraní, tak nesmím zapomenout na ty metody které jsou v rozhraní napsané. Je to tak?

  • dadajax Says:

    David42: není. Pokud ti rozhraní stále nedává smysl ani po přečtení tohoto článku, zkoušej číst dál z jiných zdrojů. Doporučuji třeba knihu Návrhové vzory.

    Každému sedne jiné vysvětlení, takže musíš jen hledat dokud nenajdeš nějakou definici nebo příklad, ze kterého ti to pak bude jasné :)

  • uf Says:

    David42: v podstate ano. Ty poskytnes funkcionalitu pro objekty, ktere maji urcite vlastnosti (rozhrani) a uzivatel tvych objektu jen doda vlastni implementaci.

    Navic to krasne oddeli implementaci sluzby (tve knihovny) a objektu, ktere ji mohou pouzivat. Knihovna se odvolava JEN na vlastnosti (metody), ktere potrebuje pro svou funkcionalitu, a ostatni umeni ji nezajima.

    Resi to i vicenasobnou dedicnost, ktera muze prinest problemy, ale je treba ji pouzit hlavne pri analyticke nutnosti.

  • Dawe Says:

    Mohl by mi prosim nekdo vysvetlit jaky je hlavni rozdil mezi Abstract a interface? Porad mi to neni uplne jasny… Musim udelat Abstraktni seznam v Jave a nejsem si jist, zda je potreba vyuzit Interface nebo zda si vystacim pouze s Abstract … Predem diky za odpoved!

  • rudyment Says:

    Interface je čistá deklarace bez jakékoliv implementace. On jenom deklaruje svoje pžadavky na třídy, které jej následně implementují.

    Třída, byť abstraktní, vždy nějakou implementaci má. Dokud se obejdeš bez implementace, jelepší používat interface. Jakmile potřebuješ, aby se něco implementovalo, musíš sáhnout po třídě.

    Při definici abstraktního seznamu vystačíš s interfacem do té doby, dokud bude stačit, že jenom deklaruješ, co musí takový seznam umět. (Takto se chová např. interface List ze standardní knihovny.) Jakmile budeš muset naprogramovat i nějaké konkrétní chování, musíš sáhnout po třídě (viz AbstractList, resp. AbstractSequentialList v Javě)-

  • Dawe Says:

    Diky za rychlou odpoved! Uz mi to je snad jasny :)

  • uf Says:

    Ahoj. Abstraktni trida uz implementuje nejake chovani. Muze jit o zakladni tridu urcenou k dedeni, pricemz jeji instance analyticky nema vyznam.
    Jde o to, ze implementujes zakladni chovani a nektere speciality – abstraktni metody – MUSI implementovat tva trida.
    Abstraktni trida muze mit i svou metodu, kde se volaji jednotlive kroky scenare. Nektere kroky jsou povinne naprogramovany (private metoda), nektere lze prekryt (protected) a nektere lze (public) nebo se MUSI (abstraktni) implementovat. Takze muzes napr. prekryt u komponenty compActivated(), ale musis prekryt compPersist() nebo buildGUI().

  • uf Says:

    Takze muzes implementovat algoritmus s povinnymi a nepovinnymi kroky

  • rudyment Says:

    V říjnu se ptal MK, kdy vyjde ona učebnice. Trochu se to protáhlo – mylslel jsem původně, že vyjde v listopadu, ale měli někde v redakci uzel, takže vyšla až na přelomu února a března. Kdyby si jí chtěl někdo pustit přes palec a podívat se, jak se dají rozhraní vysvětlovat a jak se s nimi dá pracovat, te se jmenuje

    „OOP – Naučte se myslet a programovat objektově“

  • Jozef Says:

    Chcem poďakovať za článok. dobre som pochopil čo to interface je :-) , skvele je napísaný .
    Aj ked mi unika jeho použitie, ale ako písal autor, to sa dozviem v praxi.
    Teraz pri písaní mi to došlo. ako autor písal, je to náhrada za mnohonásobnú dedičnosť :-) .

  • uf Says:

    Hlavni je urcit rozhrani (umeni tridy, deklarovat metody) tak, aby pouziti nejake funkcnosti nebylo zavisle na konkeretni implementaci.

    Je pravda, ze v podstate predepisu abstraktniho predka.

Leave a Reply