Dione
Z. KotalaP. Toman: Java
Predchozi (Obsah) Dalsi

11. Implementace OOP v Javě

11.1. Objekt

Program v Javě je staticky strukturován na třídy, jejichž instance (objekty) za běhu dynamicky programu vznikají a zanikají. Objekt je nejprve vytvořen (instanciován), následně může být používán a nakonec je zrušen.

11.1.1. Vytvoření objektu

K vytvoření objektu slouží operátor new, který má syntaxi:

   new voláníKonstruktoru
Operátor alokuje paměť pro objekt a zavolá konstruktor (viz 11.2.3.), což je speciální metoda, která provádí inicializaci objektu. Jméno konstruktoru je vždy shodné se jménem třídy objektu.

Priklad 11.1.
new String("ahoj");

vytvoří objekt třídy String a inicializuje ho konstruktorem s parametrem "ahoj".

Operátor new vrací referenci, odkazující na vytvořený objekt. Tato reference se při vytvoření objektu obvykle zároveň ukládá (1) do referenční proměnné (viz 7.2.):

Priklad 11.2.
String retezec;                 //  deklarace proměnné
retezec = new String("ahoj");   //  přiřazení reference

nebo zkráceně:

String retezec = new String("ahoj");

11.1.2. Používání objektu

Používání objektu spočívá ve volání metod a přímé manipulaci se členskými proměnnými. Pro přístup k proměnné nebo metodě se používá tečková notace:

   reference.jménoProměnné
   reference.voláníMetody

Priklad 11.3.
String retezec = new String("ahoj");    // vytvoření objektu
retezec = retezec.concat("!");          // volání metody
Metoda concat() vrací nový řetězec, který vznikne připojením parametru na konec řetězce. Výsledek se přiřadí do proměnné retezec.

Je možné použít i vícenásobnou přístupovou konstrukci:

Priklad 11.4.
int delka = retezec.trim().length();

Metoda trim() vrací referenci na nový řetězec (instanci třídy String), který vznikl z původního oříznutím prázdných znaků. Metoda length() vrací délku řetězce (typ int). Proměnné delka se tedy přiřadí délka řetězce retezec po oříznutí metodou trim().

Předchozí zápis lze přepsat jako:

String orezanyRetezec = retezec.trim();
int delka = orezanyRetezec.length();

11.1.3. Zrušení objektu

Java neumožňuje přímé rušení (dealokaci) objektu. Objekt je zrušen automaticky až tehdy, neexistuje-li na něj reference. Jsou například rušeny všechny objekty lokálních proměnných při ukončení metody. "Ruční" zneplatnění reference se provede přiřazením hodnoty null příslušné referenční proměnné.

Priklad 11.5.
String retezec = new String("ahoj");
retezec = null;   // zneplatnění reference -- objekt bude zrušen 

Tento automatický mechanismus rušení objektů je znám pod názvem garbage collection ("úklid smetí"). Jeho výhoda spočívá v nemožnosti dealokovat již dealokovaný prostor nebo do něj zapisovat, což bývá v jiných jazycích zdrojem mnoha nepříjemných chyb.

Úklid se provádí buďto synchronně, tzn. až při nedostatku paměti pro další alokace, nebo asynchronně na pozadí, pokud to daná platforma umožňuje (Unix, Windows95/NT). V programu lze o spuštění úklidu požádat voláním metody System.gc(), například v době, kdy program neprovádí výpočty.

Ještě před vlastním zrušením objektu se zavolá metoda finalize() (2) (je-li definována), kde může programátor uložit obsah proměnných do souboru apod. Deklarace této metody musí vypadat následovně:

   protected void finalize() throws Throwable

Pozn.: Při rušení objektu nedochází ke zřetězení volání metod finalize() jako je tomu u konstruktorů při vytváření objektu (viz 11.2.3.).

11.2. Třída

Pro každý objekt musí být před jeho vytvořením deklarována třída, ať už navržená v programu nebo knihovní. Knihovní třídy jsou uloženy v balících (viz kap. 12.).

11.2.1. Deklarace třídy

Nejjednodušší deklarace třídy má syntaxi:

   class JménoTřídy {
      // tělo třídy
   }

Identifikátor JménoTřídy slouží jako objektový typ, například v deklaraci referenční proměnné (viz 7.2.). V těle třídy mezi složenými závorkami mohou být deklarace členských proměnných (viz 11.2.4.) a/nebo deklarace a definice metod (viz 11.2.2.) (3) - obvykle v tomto pořadí, ale není to vyžadováno. Všimněte si, že za uzavírající závorkou deklarace třídy není středník.

Speciálním případem je třída, která neobsahuje metody. Ta pak slouží jako klasická datová struktura (record v Turbo Pascalu, struct v C).

Obecná deklarace třídy vypadá takto:

   [ modifikátory ] class jménoTřídy
   [extends jménoRodičovskéTřídy ]
   [implements seznamRozhraní ] {
      // tělo třídy
   }

  • modifikátory mohou být následující:

    • public - označuje veřejnou třídu. Veřejná třída je přístupná i mimo balík (viz kap. 12.), kde je deklarována. Není-li třída veřejná, je přístupná pouze ve svém balíku.

    • abstract - třída deklarovaná s tímto modifikátorem je abstraktní a nesmí být nikdy instanciována.

      Priklad 11.6.
      Třída Number v balíku java.lang definuje metody pro vzájemnou konverzi číselných typů (int na float atd.). Jejími potomky jsou mj. Float a Integer.

      Je zřejmé, že smysl mají pouze instance tříd Integer a Float - reálná a celá čísla - nikoliv třídy Number (obecné "číslo" neexistuje). Třída Number je proto deklarována jako abstraktní.

    • final - označuje koncovou třídu. Koncová třída nemůže být třídou rodičovskou (nemůže mít potomky).

    Nejsou-li uvedeny příslušné modifikátory, je třída implicitně pokládána za neveřejnou, neabstraktní a ne-koncovou. Zároveň nelze použít modifikátory abstract a final.

  • Za jménem třídy může být specifikována rodičovská třída pomocí klíčového slova extends (angl. rozšiřuje). Třída dědí proměnné a metody třídy rodičovské. Není-li tato část uvedena, je rodičovskou třídou automaticky java.lang.Object (viz 11.5.).

  • Za klíčovým slovem implements následuje jedno nebo více jmen rozhraní (viz 11.6.), které třída implementuje. To mimo jiné znamená, že třída musí obsahovat definice metod uvedené v příslušném rozhraní. Je-li jmen rozhraní více, jsou oddělena čárkami.

Priklad 11.7.
Deklarace veřejné třídy Appletik, jejíž rodičovskou je třída java.applet.Applet, a která implementuje rozhraní Cloneable a Runnable (z balíku java.lang) vypadá takto:

public class Appletik extends java.applet.Applet
implements Cloneable, Runnable {
   public void run() {  // ...
   }
   // ...
}

Rozhraní Runnable obsahuje metodu run, a proto musí třída Appletik její definici obsahovat. Rozhraní Cloneable je prázdné a slouží jen k identifikaci třídy (její instance je možné klonovat - viz 11.5.).

11.2.2. Metody

Nejjednodušší definice metody má tvar:

   NávratovýTyp jménoMetody ( parametry ) {
      // tělo metody
   }

  • NávratovýTyp musí být některý datový typ z kap. 7.. Návrat z metody a případné předání návratové hodnoty způsobí příkaz return (viz 9.13.). Je-li návratovým typem void, metoda nevrací hodnotu.

  • jménoMetody musí být platný identifikátor. Pokud mají v jedné třídě dvě metody shodná jména, musí mít odlišný počet a/nebo typ parametrů - tzv. přetěžování metod (overloading). Při volání přetížené metody překladač na základě argumentů rozhodne, kterou volat. Díky přetěžování nemusí existovat různě pojmenované metody, jejichž funkce je prakticky shodná:

    Priklad 11.8.
    Argumentem metody System.out.println(), která provádí výpis na standardní výstup, může být proměnná kteréhokoliv základního datového typu nebo nic.

    System.out.println();         // odřádkuje
    System.out.println("ahoj");   // vypíše ahoj (1)
    System.out.println(12345);    // vypíše 12345 (2)
    

    Ve všech případech se jedná o volání jiné přetížené metody - překladač se rozhoduje na základě argumentů - první případ se liší počtem argumentů, druhé dva jejich typem: (1) předává řetězec, (2) celé číslo.

  • Formální parametry metody:

    • Nejsou-li parametry uvedeny, jde o metodu bez parametrů. (4)

    • Každý parametr má tvar:

        [final] typ jménoParametru
      
      Modifikátor final, který lze použít až od JDK1.1, značí konstantní hodnotu (nelze měnit hodnotu argumentu v metodě).

    • V těle metody nelze deklarovat lokální proměnnou (viz 9.4.) pojmenovanou stejně jako parametr.

    • Je-li parametrů více, jsou odděleny čárkami:

      Priklad 11.9.
      void metoda(int x, int y) {
         // ...
      }
      

  • Je-li jméno parametru stejné jako jméno členské proměnné, dochází k jejímuzastínění (hiding) parametrem. K explicitnímu přístupu ke členské proměnné pak slouží operátor this (5) (viz též 11.8.), který vrací referenci objektu na sebe:

    Priklad 11.10.
    class Trida {
       int x;
    
       Trida (int x) {   // zastínění členské proměnné x parametrem
          this.x = x;    // přiřazení hodnoty parametru členské proměnné x
          // ...
       }
    }
    

Obecná deklarace metody vypadá takto:

   [ práva ] [static] [abstract] [final] [native]
   [synchronized] NávratovýTyp jménoMetody ( parametry )
   [throws seznamVýjimek ]

  • práva - tato položka určuje přístupnost metody vně třídy a pravidla pro dědičnost. Metoda může být buď veřejná (public), chráněná (protected), soukromá (private) nebo nemusí mít práva specifikována vůbec (default access). Více viz 11.3. a 11.4..

  • static - označuje statickou metodu. Statické metody je možné narozdíl od nestatických volat i tehdy, neexistuje-li žádná instance dané třídy. Mohou však přímo manipulovat pouze se statickými (a svými lokálními) proměnnými.

    Volání statické metody má tvar:

       [ JménoTřídy.  ] jménoMetody ( parametry );
    
    JménoTřídy se nemusí specifikovat uvnitř třídy, která statickou metodu definuje.

    V jedné třídě nemůže být definována shodně pojmenovaná statická i nestatická metoda se stejným počtem a typem parametrů - jinak dojde k chybě při překladu.

  • abstract - metody deklarované jako abstraktní nemají v dané třídě tělo a musí být definovány až potomky třídy. Třída, která obsahuje abstraktní metody, nemůže být instanciována a musí být rovněž deklarována jako abstraktní (viz 11.2.1.).

    Priklad 11.11.
    abstract class A {
       abstract void metoda();   // deklarace abstraktní metody
    }
    

  • synchronized - viz 16.5.2..

  • final - označuje koncovou metodu. Koncová metoda nesmí být potomky překryta.

  • native - nativní metody umožňují sloučit programy psané v Javě a jiných jazycích. Například většina nízkoúrovňových (6) metod z Java Core API je nativních. V běžných aplikacích se nativní metody nepoužívají kvůli jejich nepřenositelosti. V appletech se používat nesmí.

  • Klíčové slovo throws uvozuje jedno nebo více jmen tříd výjimek (viz kap. 13.), které může metoda vyvolat. Je-li výjimek více jsou odděleny čárkami.

    Priklad 11.12.
    Následující metoda deklaruje, že může vyvolat výjimky třídy ArithmeticException a SecurityException:

    void metoda() throws ArithmeticException, SecurityException {
       // ...
    }
    

Zárověň nelze použít dvojic static abstract a final abstract.

Argumenty metod se vždy předávají hodnotou - hodnotu argumentu sice měnit lze, ale navenek se to neprojeví:

Priklad 11.13.
Metoda prohod() má zaměnit hodnoty svých dvou argumentů:

void prohod(int x, int y) {
   int tmp = x; x = y; y = tmp;
}

void hop() {
   int a = 1, b = 2;
   prohod(a,b);  //  tohle nefunguje! (1)
   //  nadále  a = 1,  b = 2
}

Po zavolání metody prohod (1) se hodnoty argumentů nezamění. Pro správnou funkci je třeba provést tuto úpravu: proměnné x, y se umístí do jedné instance třídy (2), na kterou se metodě prohod předá reference (3):

class XY {
   public int x, y;  // (2)
}

void prohod(XY xy) {
   int tmp = xy.x;
   xy.x = xy.y;
   xy.y = tmp;
}

void hop() {
   XY xy = new XY();  // objekt xy namísto a, b

   xy.x = 1;
   xy.y = 2;
   prohod(xy);        // (3)
   // nyní:  xy.x = 2,  xy.y = 1
}

11.2.3. Konstruktory

Konstruktor je speciální metoda, která se volá pouze při vytváření objektu (viz 11.1.1.) a slouží k jeho inicializaci. Jméno konstruktoru musí být shodné se jménem třídy, v níž je definován. Návratový typ konstruktoru se neuvádí.

Pro konstruktory platí pravidla uvedená pro metody (viz 11.2.2.). Jejich deklarace však nesmí obsahovat modifikátory abstract, final, native, static a synchronized a nedochází k dědění konstruktorů.

Priklad 11.14.
class Bod {
   Bod() {  // konstrukor třídy Bod
      // ...
   }
}

Každá třída má alespoň jeden konstruktor. Pokud ve třídě konstruktor není uveden, vytvoří překladač automaticky implicitní konstruktor bez parametrů (default constructor), který nic neprovádí.

Konstruktor každé třídy musí na začátku obsahovat volání konstruktoru rodičovské třídy, aby se zajistila řádná inicializace zděděných proměnných (tzv. řetězení konstruktorů). K volání rodičovského konstruktoru slouží klíčové slovo super (viz též 11.8.):

   super( parametryKonstruktoru );

Volání rodičovského konstruktoru lze vynechat pouze v případě, že rodičovská třída má pouze jeden konstruktor bez parametrů nebo konstruktor implicitní - pak toto volání překladač na začátek konstruktoru sám doplní.

Volání konstruktoru téže třídy se provádí pomocí operátoru this (viz též str. 11):

Priklad 11.15.
class Trida {
   public Trida() {  
      this(0);             // volání konstruktoru (*)
   }
   
   public Trida(int i) {   // (*)
      // ...
   }
}

Pozn.: Pokud je potřeba zabránit instanciování dané třídy, stačí nadefinovat všechny její konstruktory jako soukromé.

11.2.4. Členské proměnné

Členské proměnné se deklarují v těle třídy - mimo těla metod (jinak by se jednalo o lokální proměnné). Podle konvence se deklarace členských proměnných uvádějí před deklaracemi metod.

Pro členské proměnné platí podobná pravidla jako pro lokální proměnné (viz 9.4.). Jejich deklarace může navíc obsahovat následující modifikátory:

   [ práva ] [static] [transient] [final] [volatile]
   typ jménoProměnné

  • práva - tato položka určuje přístupnost proměnné vně třídy a pravidla pro dědičnost. Proměnná může být buď veřejná (public), chráněná (protected) nebo soukromá (private) nebo nemusí mít práva specifikována vůbec (default access). Více viz 11.3. a 11.4..

  • static - statická proměnná existuje jen v jednom exempláři, který všechny objekty dané třídy (včetně potomků) sdílí, tj. při vytváření instance třídy není pro statickou proměnnou znovu alokována paměť. Statické proměnné jsou přístupné (v souladu s přístupovými právy) i tehdy, neexistuje-li (ještě) instance třídy. Ke statické proměnné se přistupuje touto konstrukcí:
    
       JménoTřídy.jménoProměnné
    

    Priklad 11.16.
    Ve třídě Integer (z balíku java.lang) je deklarována konstanta MAX_VALUE obsahující největší hodnotu čísla typu int. Protože je statická, není třeba nejprve vytvářet instanci této třídy, a může být ihned použita:

    int a = Integer.MAX_VALUE;
    

    Jméno třídy (Integer) musí být použito, jinak by překladač hledal definici konstanty v aktuální třídě.

  • transient - informuje, že proměnná není součástí perzistentního stavu objektu - systém nebude její hodnotu uchovávat při zasílání objektu po síti apod.

  • volatile - zaručí, že obsah proměnné bude aktualizován při každém jejím použití (čtení, přiřazení) - používá se tehdy, přistupuje-li se v programu k proměnné asynchronně (viz kap. 16.) a při každém čtení proměnné je třeba získat aktuální hodnotu. Java zaručuje, že hodnota libovolné proměnné je konzistentní s výjimkou typů typů long a double, které by měly být vždy deklarovány s modifikátorem volatile, je-li k nim prováděn asynchronní přístup. (7)

  • final - označuje konstantu. Její obsah je nutno inicializovat při deklaraci a nesmí se v programu měnit (překladač ohlásí chybu).

    Od verze JDK 1.1 je možné hodnotu v deklaraci nespecifikovat - konstanta nabude hodnoty po první (a jediné!) inicializaci.

Priklad 11.17.
Deklarace veřejné celočíselné statické konstanty MAX_VALUE, která je inicializována číslem 0x7fffffff (maximální hodnota pro typ int) vypadá takto:

public static final int MAX_VALUE = 0x7fffffff;

11.3. Dědičnost

Třída-potomek se od rodičovské třídy odvodí v deklaraci pomocí klíčového slova extends (viz 11.2.1.). Potomek od přímého rodiče dědí, tj. přejímá jako by byly znovu definovány, všechny členské proměnné a metody, které: (8)

  • jsou deklarovány jako veřejné (public) nebo chráněné (protected),

  • nemají explicitně určena přístupová práva a zároveň se rodičovská třída nachází ve stejném balíku jako potomek.

Potomek nedědí členské proměnné a metody, které:

  • jsou deklarovány jako soukromé (private) (9) ,

  • jsou deklarovány v jiném balíku a nejsou veřejné (public),

  • mají v potomkovi stejné jméno (a parametry - u metod) jako v rodičovské třídě. Tyto členské proměnné (metody) jsou předefinovány - tzv. překrytí proměnných a metod (overriding).

Při překrývání metod je možné přístupová práva pouze rozšířit (na public nebo protected - v případě implicitních práv). Naopak seznam (resp. typy) výjimek deklarovaných pomocí throws lze pouze zúžit. Na změny modifikátorů a členské proměnné omezení kladena nejsou.

K překrytým metodám a proměnným rodiče se přistupuje pomocí operátoru super, který vrací referenci na rodičovskou třídu:

   super. JménoČlenskéProměnné
   super. JménoMetody

Priklad 11.18.
class A {
   void a() {
      // ...
   }
}

class B extends A {
   void a() {
      super.a();  // volá metodu rodiče: A.a
      a();        // volá sama sebe
      // ...
   }
}

11.4. Přístupová práva

Členské proměnné a metody mají specifikována přístupová práva (viz kap. 11.2.2. a 11.2.4.) určující okruh tříd, které k nim mají přístup. Přístupem se rozumí možnost přímé manipulace se členskými proměnnými a volání metod.

Třída má přístup pouze k metodám a členským proměnným, které:

  • sama deklaruje,

  • dědí (viz 11.3.),

  • jsou deklarovány v jiných třídách a jsou veřejné (public),

  • jsou umístěny ve třídě stejného balíku (viz kap. 12.) a zároveň mají přístup nespecifikován nebo nastaven jako public či protected.

Třída nemá přístup ke členským proměnným a metodám z jiných balíků, které jsou soukromé, chráněné nebo mají nespecifikován přístup.

Priklad 11.19.
V příkladu 11.16. je konstanta MAX_VALUE deklarována jako veřejná a je tedy přímo přístupná všem ostatním třídám (i mimo svůj balík).

11.5. Třída Object

Třída Object z balíku java.lang je kořenovou třídou ve stromu tříd, tj. všechny třídy, ať už knihovní nebo navržené programátorem, mají společného (nepřímého) rodiče třídu Object.

Object definuje základní metody, které musí mít každý objekt v Javě - většinu z nich používá runtime systém. Jedná se například o metody:

  • protected native clone() - vytvoří identický objekt (ale nevolá konstruktor) a přiřadí stejné hodnoty všem členským proměnným. (10) Funguje pouze u tříd, které implementují rozhraní Cloneable.

  • public boolean equals(Object obj) - porovnává objekt s objektem obj.

  • public final Class getClass() - vrací objekt reprezentující třídu v runtime systému.

  • public String toString() - vrací identifikační řetězec objektu,

  • wait(), notify(), notifyAll() - viz 16.5.3..

11.6. Rozhraní (interface)

Rozhraní (interface) je syntaktická struktura obsahující deklarace konstant a metod (bez implementací). Deklarace rozhraní je podobná deklaraci třídy:

   [public] interface jménoRozhraní
   [ extends seznamRozhraní ] {
      // tělo rozhraní
   }

  • public - označuje veřejné rozhraní. Veřejné rozhraní je přístupné i mimo balík (viz kap. 12.), kde je deklarováno. Není-li rozhraní veřejné, je přístupné pouze ve svém balíku.

  • V nepovinné části za klíčovým slovem extends (angl. rozšiřuje) následuje jedno nebo více jmen rozhraní, jejichž konstanty a metody toto rozhraní dědí.

    Kromě hierarchického stromu tříd tedy v Javě existuje ještě strom rozhraní. Oproti třídě může mít rozhraní více rodičů - jména rodičovských rozhraní jsou oddělena čárkami:

    Priklad 11.20.
    Deklarace rozhraní Slucujici, které má dvě rodičovská rozhraní: Runnable a Cloneable vypadá takto:

    interface Slucujici extends Runnable, Cloneable {}
    

Tělo rozhraní smí obsahovat pouze deklarace konstant a metod. Při tom platí následující pravidla:

  • Všechny metody a konstanty uvedené v deklaraci rozhraní jsou automaticky veřejné (public) a nesmí mít specifikována přístupová práva protected ani private.

  • Všechny metody jsou navíc automaticky abstraktní.

  • I když u proměnné není modifikátor final, jedná se vždy o konstantu.

  • V rozhraní nesmí být použity modifikátory: transient, volatile a synchronized.

Priklad 11.21.
public interface NoveRozhraní {
   int MAX_DELKA_RETEZCE = 20;   // automaticky konstanta
   void vypis(String retezec);   // automaticky abstraktní metoda
}

Rozhraní se používají k zachycení společných prvků tříd, které spolu nemusí souviset ve stromu dědičnosti - společnými prvky jsou konstanty a metody deklarované rozhraním. Třída, která implementuje dané rozhraní (viz 11.2.), musí obsahovat definice všech(!) metod tohoto rozhraní. Rozhraní pak lze použít jako objektový typ třídy:

Priklad 11.22.
Program bude obsahovat schránku (clipboard), přes kterou lze kopírovat, a vkládat objekty (text, obrázky, zvuky). Všechny objekty, které lze do schránky umístit, musí kopírování samy umožňovat - musí obsahovat metodu copy().

Z hlediska návrhu není vhodné, aby třídy takto rozdílných objektů měly společného rodiče - třídu, deklarující metodu copy(), kterou všechny povinně zdědí. Výhodnější je deklarovat rozhraní (Clip) obsahující metodu copy(), které budou jednotlivé třídy implementovat - i v tomto případě bude zaručena podpora kopírování ze strany objektů.

public interface Clip {
   byte[] copy();
}

Metoda copy() bude vracet pole bytů reprezentující objekt ve schránce. Každá třída, jejíž instance lze kopírovat přes schránku, musí implementovat rozhraní Clip. Její deklarace bude:

class Obrazek implements Clip {
   byte[] copy() {
      // ...
   }
}

Schránka pak může obsahovat libovolný objekt typu Clip:

class Schranka {
   byte[] schranka;

   // metoda pro vložení (kopírovatelného) objektu
   public void vlozit(Clip objekt) {   // typ parametru je rozhraní
      schranka = objekt.copy();        // vložení objektu do schránky
   }
   // ...
}

Rozhraní může být prázdné. Třídu implementující rozhraní lze identifikovat operátorem instanceof, viz 8.2..

Pozn.: Rozhraní neumožňují vícenásobnou dědičnost, neboť třídy prostřednictvím rozhraní nedědí definice (kód) metod. Hierarchie rozhraní je nezávislá na hierarchii tříd.

11.7. Inicializace tříd a rozhraní

K inicializaci třídy nebo rozhraní dochází při prvním aktivním použití, které nastane, je-li splněna aspoň jedna z podmínek (11) :

  • je vyvolána metoda nebo konstruktor deklarovaný danou třídou.

  • je vytvořeno pole s prvky typu dané třídy,

  • je proveden přístup k nekonstantní členské proměnné třídy nebo konstantě rozhraní,

  • je proveden přístup ke členské proměnné třídy s modifikátorem final nebo static, která je inicializována hodnotou vypočítanou za běhu programu.

Inicializace třídy se skládá z inicializace statických členských proměnných a vykonání statických inicializátorů (viz dále), přičemž před tím musí být inicializována nejprve její rodičovská třída.

Inicializace rozhraní spočívá pouze v inicializaci v něm definovaných konstant, nedochází automaticky k inicializaci rodičovského rozhraní.

11.7.1. Inicalizátory

Od JDK 1.1 lze k inicializaci třídy (resp. instance) použít statický (resp. nestatický) inicializátor. Jeho syntaxe je:

 deklaraceTřídy {
   [static] {
      // inicializace
   }
}

Jedná se o blok, který obsahuje inicializace členských proměnných. Statický inicializátor vyvolává pouze třída. Nestatický inicializátor vyvolávají všechny konstruktory třídy. Nestatický inicializátor smí vyvolávat pouze výjimky, které deklarují všechny konstruktory třídy. (12) .

Priklad 11.23.
class A {
   static {
      String ahoj = "ahoj";
   }
   // ...
}

11.8. Vnořené třídy

Od verze JDK 1.1 je možné třídy do sebe vnořovat (13) a Java rozeznává dva druhy tříd:

  • Třídy nejvyšší úrovně (top-level classes) - jsou "normální" třídy a statické třídy (14) umístěné v jiné třídě (na úrovni vnoření nezáleží). Do této skupiny se také řadí také vnořená rozhraní.

    Priklad 11.24.
    Obě následující třídy TopLevel1 a TopLevel2 jsou třídy nejvyšší úrovně.

       class TopLevel1 {
          static class TopLevel2 {
             // ...
          }
    
          interface Cool {
             // i rozhraní může být vnořené
          }
       }
    

    Třídy nejvyšší úrovně mohou být zpřístupněny (závisí na přístupových právech) udáním úplného jména třídy. Podobně jako u balíků (viz kap. 12.) se používá tečková notace: TopLevel1.TopLevel2 je úplné jméno vnořené třídy TopLevel2.

    Třídy nejvyšší úrovně umožňují vytvářet hierarchickou strukturu na úrovni tříd, nikoliv pouze na úrovni balíků.

  • Vnitřní třídy (inner classes) - jsou nestatické vnořené třídy a lze je umístit i do těla metody nebo do bloku. Vnitřní třídy definované v bloku nebo metodě nejsou "zvenku" přístupné a nemohou mít modifikátory public, protected, private a static. Vnitřní třídy nesmí obsahovat statické proměnné a metody, statické inicializátory, ani deklarace rozhraní.

    Vnitřní třídy jsou obecnějším nástrojem pro případy, kdy jiné jazyky používají ukazatele na funkce.

    Zvláštním druhem vnitřní třídy je nepojmenovaná, anonymní třída (anonymous class). Deklarace anonymní třídy je součástí rozšířené syntaxe operátoru new:

       new Typ ( parametry ) {
          // tělo anonymní třídy
       }
    

    Typ představuje:

    • jméno konstruktoru rodičovské(!) třídy, od které je anonymní třída odvozena (následují jeho parametry), nebo

    • jméno rozhraní - anonymní třída jako jediná může přímo instanciovat rozhraní (zde se parametry neuvádí). (15)

    Priklad 11.25.
    Vytvoření členské proměnné r jako instance rozhraní Runnable (viz též 16.2.) vypadá následovně:

    class NejakaTrida {
       Runnable r = new Runnable() {
          public void run() {
             // ...
          }
       }
    }
    

Vnitřní třídy mají přímý přístup k soukromým statickým proměnným a metodám vnější třídy a nemusí používat jejich úplná jména. Třídy nejvyšší úrovně mohou manipulovat pouze se statickými proměnnými a statickými metodami vnějších tříd.

Pokud vnitřní třída ve svých metodách používá lokální proměnné nebo parametry, které sama nedeklaruje, musí tyto být deklarovány s modifikátorem final.

Protože po zavedení vnitřních tříd v JDK 1.1 může mít daná třída více instancí, je v některých případech použití operátorů new a super nutno explicitně uvést referenci na instanci vnější třídy: (16)


   referenceNaVnějšíInstanci. new
   referenceNaVnějšíInstanci. super

Podobně byla rozšířena syntaxe operátoru this, kde je možné specifikovat vnější třídu:


   JménoVnějšíTřídy. this

Priklad 11.26.
class Vnejsi {
   static int i = 10;                    // (1)
   int x;

   class Vnitrni {
      Vnitrni() {
         int i = 0;                      // (2)
         int j = Vnejsi.this.i;          // (3)
         // ...
      }
   }

   static Vnitrni delej(Vnejsi obj) {
      // ...
      return obj.new Vnitrni();          // (4)
   }
}

Na řádce (3) je proměnné j přiřazen obsah statické proměnné i (1) - v případě neuvedení třídy by se přiřadila hodnota proměnné i (2).

Na řádce (4) je nutné u operátoru new uvést referenci na instanci obj, neboť metoda delej() je statická a jako taková nemůže (automaticky) předat konstruktoru referenci na instanci vnější třídy.


  • (1) Objekt, na nějž neodkazuje žádná referenční proměnná, je automaticky zrušen (viz 11.1.3.).
  • (2) V jiných jazycích má podobnou funkci tzv. destruktor.
  • (3) Od JDK 1.1 může třída obsahovat další třídy, viz 11.8..
  • (4) Narozdíl od jazyka C, kde u bezparametrické funkce ANSI norma vyžaduje uvedení typu void v místě parametru.
  • (5) V Turbo Pascalu má ekvivalent self.
  • (6) Jedná se převážně o metody, které jsou závislé na hardwaru nebo na operačním systému (např. metody pro čtení ze souboru).
  • (7) Důvodem je, že 64-bitové operace jsou v některých implementacích prováděny po 32 bitech.
  • (8) Statické proměnné a metody potomek s rodičovskou třídou sdílí.
  • (9) Přístup ze zděděných metod ovšem zůstává zachován.
  • (10) Pro členské proměnné typu objekt clone() neprovádí! Proto je třeba dát pozor u vícerozměrných polí - nekopírují se vyšší indexy, ale pouze se přiřadí.
  • (11) Podrobný popis inicializací i s ohledem na synchronizaci obsahuje [GJS96].
  • (12) To neplatí pouze pro anonymní třídy, viz 11.8..
  • (13) Toto rozšíření je však binárně kompatibilní s JDK 1.0 - překladač provádí transformace na úrovni zdrojového textu.
  • (14) U těchto tříd lze na rozdíl od doposud známých úrovně použít modifikátor static.
  • (15) Ve skutečnosti bude tato třída potomkem třídy java.lang.Object, který příslušné rozhraní implementuje.
  • (16) Důvodem je, že k instanci vnější třídy může existovat zároveň několik instancí tříd vnitřních a vnitřní třídy mají přístup ke členským proměnným třídy vnější.

Predchozi
Converted by Selathco v0.9 on 25.09.1999 19:46
Dalsi