Java (15) - Zanořené typy

Kdysi jsme si řekli, že každý soubor může obsahovat právě jednu veřejnou třídu/rozhraní a libovolné množství neveřejných tříd a rozhraní. Dnes pronikneme hlouběji a ukážeme si, jakým způsobem můžeme definovat třídy uvnitř jiných tříd a rozhraní, případně dokonce uvnitř jednotlivých metod.

Vnořené třídy/rozhraní

Nejjednodušší variantou zanořených typů jsou vnořené třídy/rozhraní (nested/embedded class/interface). Jsou to typy, které jsou definovány v rámci jiné třídy mezi jejími metodami a jsou uvozeny klíčovým slovem static. Vnořené třídy definované v rámci rozhraní jsou statické implicitně.

V případě vnořených tříd a rozhraní se jedná pouze o pojmenovávací konvenci, kterou dáváme najevo, že k sobě obalovaný a obalující typ nějakým způsobem patří, ale zároveň mohou existovat nezávisle na sobě.

Pro vnořené třídy (a veškeré zanořené typy obecně) platí, že mají přístup k soukromým proměnným obalujícího typu, stejně jako má obalující typ přístup k jejich proměnným.


Příklad

Příkladem může být mobilní telefon a SIM karta. Telefon může existovat bez SIM karty, stejně tak SIM karta může existovat bez mobilního telefonu. Zároveň ale cítíme, že mezi těmito objekty existuje určitá pevnější vazba.

/**
 * Trida mobilniho telefonu
 * @author Pavel Micka
 */
public class CellPhone {

    /**
     * International Mobile Equipment Identity
     *
     * Field je final - imai nelze zmenit
     */
    private final String imei;
    private SIMCard card;

    /**
     * Konstruktor, kazdy telefon ma imai
     * @param imei
     */
    public CellPhone(String imei) {
        this.imei = imei;
    }

    /**
     * @return the imai
     */
    public String getImei() {
        return imei;
    }

    /**
     * @param card the card to set
     */
    public void setCard(SIMCard card) {
        this.card = card;
    }

    @Override
    public String toString() {
        String phoneNumber = null;
        if (this.card != null) {
            phoneNumber = "Obsahuji SIM kartu s telefonnim cislem: " + card.phoneNumber + "."; //primy pristup k fieldum vnitrni tridy
        } else {
            phoneNumber = "Neobsahuji zadnou SIM kartu.";
        }
        return "Jsem mobilni telefon a mam imei: " + imei + ". " + phoneNumber;
    }

    /**
     * Vnorena trida SIM karty
     */
    public static class SIMCard {

        private String phoneNumber;

        public SIMCard(String phoneNumber) {
            this.phoneNumber = phoneNumber;
        }

        /**
         * Setter na telefonni cislo (coz snad v realnem svete jde :-])
         * @param phoneNumber
         */
        public void setPhoneNumber(String phoneNumber) {
            this.phoneNumber = phoneNumber;
        }

        public String getPhoneNumber() {
            return phoneNumber;
        }
    }
}

Třídu CellPhone.SIMCard můžeme importovat úplně stejným způsobem, jako to děláme s třídami různých balíčků. Záleží pouze na vkusu programátora, zda-li si přeje tímto způsobem zdůraznit vazbu, nebo jestli bude volat třídu přímo.

Volání

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        /**
         * Vytvorime telefon, jak jsme zvykli
         */
        CellPhone phone = new CellPhone("050277-22-222751-3");

        /**
         * Vytvorime sim kartu, je zvykem pouzit teckovaci notaci z obalujici tridy
         * Jinak zde zadny rozdil neni. Vnorenou tridou pouze rikame, ze se jedna
         * o SIMCard k mobilnimu telefonu (ne k cemukoliv jinemu, kdyby ji to nahodou
         * take melo).
         */
        CellPhone.SIMCard card = new CellPhone.SIMCard("77623456");
        phone.setCard(card);

        /**
         * Nechame vypsat objekt telefonu, pripominam, ze se zavola metoda toString
         */
        System.out.println(phone);
    }
Jsem mobilni telefon a mam imei: 050277-22-222751-3. Obsahuji SIM kartu s telefonnim cislem: 77623456.

Vnitřní třídy

Vnitřní třídy (inner class, member class) definujeme stejně jako třídy vnořené, jediný rozdíl je v tom, že neuvádíme modifikátor static. Takto definovaná třída má silnější vztah k obalující třídě než vnořená třída, protože obsahuje implicitní ukazatel na obalující instanci. Právě z tohoto důvodu nelze vytvořit vnitřní rozhraní (a kompilátor vždy doplní implicitní modifikátor static, pokud jej neuvedeme my).


Příklad

Králíci z klobouku jsou dobrým příkladem vnitřní třídy. Bob i Bobek se v klobouku vzbudí, pak celý den někde pracují a večer se do klobouku opět vrátí. Můžeme s klidem v duši říct, že Bob ani Bobek nemohou bez kouzelného klobouku existovat.

/**
 * Klobouk - v tomto pripade se vyhybam jinak velmi vhodne konvenci psat
 * nazvy trid anglicky, jelikoz se jedna o ciste ceskou
 * pohadku a tento priklad by byl v anglictine zmateny pro cechy a cizinec
 * by ho stejne nepobral :-]
 * @author Pavel Micka
 */
public class Klobouk {

    /**
     * Nazev klobouku
     */
    private String hatName;
    private Kralik[] inhabitants;

    public Klobouk(String name) {

        this.hatName = name;
        this.inhabitants = new Kralik[2]; //pole dvou kraliku
        inhabitants[0] = new Kralik("Bob");
        inhabitants[1] = new Kralik("Bobek");
    }

    /**
     * @return the name
     */
    public String getName() {
        return hatName;
    }

    /**
     * @param name the name to set
     */
    public void setName(String name) {
        this.hatName = name;
    }

    public Kralik[] getInhabitants() {
        return inhabitants;
    }

    public class Kralik {

        private String name;

        public Kralik(String name) {
            this.name = name;
        }

        /**
         * @return the name
         */
        public String getName() {
            return name;
        }

        /**
         * @param name the name to set
         */
        public void setName(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            String result = "Ahoj, ja jsem " + this.name + " a bydlim v klobouku, ktery se jmenuje " + Klobouk.this.hatName + "."; //Klobouk.this == reference na obalujici instanci
            return result;
        }
    }
}

Volání

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        Klobouk hat = new Klobouk("Pokustonuv klobouk");
        //Klobouk.Kralik bob = new Klobouk.Kralik(); //tohle by nefungovalo, musime byt v kontextu obalujici tridy

        Klobouk.Kralik[] rabbits = hat.getInhabitants();

        for(Klobouk.Kralik k : rabbits){
            System.out.println(k);
        }
    }
Ahoj, ja jsem Bob a bydlim v klobouku, ktery se jmenuje Pokustonuv klobouk.
Ahoj, ja jsem Bobek a bydlim v klobouku, ktery se jmenuje Pokustonuv klobouk.

Lokální třídy

O vnitřní třídě, kterou nadefinujeme v rámci metody řekneme, že je lokální (local inner class). Lokální interface definovat nelze (jelikož nemůže být ani vnitřní).

U lokálních tříd nesmíme používat modifikátory public, protected, private a static. Stejně tak tyto třídy nesmějí obsahovat statické proměnné a metody.


Příklad

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        String[] array = {"žluťoučký kůň", "dědek", "Máj", "májka", "chalupa", "cihla", "řízek", "Řezno"};

        /**
         * Vnitřní třída pro porovnávání českých slov
         */
        class CzechComparator implements Comparator{
            public int compare(Object o1, Object o2) {
                Collator c = Collator.getInstance(new Locale("cs", "CZ"));
                return c.compare(o1, o2);
            }
        }

        Arrays.sort(array, new CzechComparator());

        for(String s : array){
            System.out.println(s);
        }
    }
cihla
dědek
chalupa
Máj
májka
Řezno
řízek
žluťoučký kůň

Tento příklad seřadí zadaná česká slova do abecedního pořadí. Rozeberme si nyní jednotlivá použitá volání.

Jádrem programu je samotný řadicí algoritmus, jenž voláme pomocí Arrays.sort(Object[], Comparator), a který seřadí vstupní pole pomocí upraveného Merge sortu do vzestupného pořadí.

Aby Merge sort mohl rozhodnout o uspořádání jednotlivých řetězců, tak mu musíme předat třídu implementující rozhraní Comparator (dokumentace), která definuje metodu compare. Tato metoda příjímá dva argumenty a vrací -1, pokud je první argument menší než druhý argument, 0 pokud si jsou rovny a 1, je-li první argument vyšší než druhý.

Dále používáme také třídu Collator (dokumentace), která za nás provede veškeré řazení. Pro získání Collatoru musíme zavolat tovární metodu getInstance() s parametrem Locale (dokumentace). Locale je javovský objekt, který specifikuje daný geografický, kulturní nebo politický region. V našem případě vytváříme Locale pro češtinu v České republice (kód jazyka specifikuje norma ISO-639, kód státu ISO-3166).

Anonymní třídy

Anonymní třída (anonymous class) je lokální třída na jedno použití (jelikož je na daném místě rovnou použita, tak ji není zapotřebí pojmenovávat – proto anonymní).

Možnost použít danou třídu pouze jednou se může na první pohled jevit jako nešikovná, ale v reálném světě nepotkáte téměř žádnou pravověrnou lokální třídu, pouze třídy anonymní. Je to dáno především tím, že lokální třídy jsou z principu téměř vždy pouze na jedno použití – kdyby nebyly, tak bychom je buď vytvořili jako běžnou třídu, nebo alespoň jako třídu vnitřní.

Konstrukce

Samotná syntaxe konstrukce anonymní třídy je poněkud zvláštní. Nejprve uvádíme klíčové slovo new, jelikož rovnou vytváříme instanci této třídy. Následuje název abstraktní třídy, nebo rozhraní, které anonymní třída implementuje a kulaté závorky, do kterých můžeme vepsat argumenty volání případného konstruktoru (je-li anonymní třída odvozena od abstraktní třídy). Poslední položkou jsou složené závorky, jež obsahují metody abstraktní třídy nebo rozhraní, které musíme doimplementovat.

Technicky vzato lze vytvářet anonymní třídy také od neabstrakních tříd a v definici překrýt některé metody. Tuto konstrukci jsem však zatím v žádném reálném projektu nepotkal.


Příklad

Při použití anonymní třídy by předchozí příklad vypadal následovně (takto byste jej nejpravděpodobněji potkali v reálném kódu):

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        String[] array = {"žluťoučký kůň", "dědek", "Máj", "májka", "chalupa", "cihla", "řízek", "Řezno"};

        Arrays.sort(array, new Comparator(){
            public int compare(Object o1, Object o2) {
                Collator c = Collator.getInstance(new Locale("cs", "CZ"));
                return c.compare(o1, o2);
            }
        }); //strednik ukoncuje prikaz Arrays.sort()

        for(String s : array){
            System.out.println(s);
        }
    }

Literatura

  • PECINOVSKÝ, Rudolf. Návrhové vzory. 1. vyd. [s.l.] : Computer press, 2007. 527 s. ISBN 978-80-2511582-4.
  • HORTON, Ivor. Java 5. Praha : Neocortex spol s.r.o., 2005. 1443 s. ISBN 80-86330-12-5.







Doporučujeme

Internet pro vaši firmu na míru