Obsah

Třídy a objekty

třída 5.A

Třídu deklarujeme pomocí klíčového slova class. Za ním následuje jméno třídy. Jméno třídy obvykle začíná velkým písmenem. Pokud se skládá z více slov, oddělujeme slova tak, že první písmeno každého slova je velké (např. BarevnyObrazek). Není zvykem používat podtržítka. Třída může obsahovat deklarace proměnných a metod. Proměnné i metody mohou být buď třídní (statické) nebo instanční. Instančním proměnným říkáme instanční atributy nebo zkráceně atributy (angl. instance attributes nebo fields). Třídní (statické) metody již známe. Deklarují se pomocí klíčového slova static. Dále si ukážeme, jak deklarovat a používat instanční metody (angl. instance methods). Instanční metody se deklarují podobně jako statické. Na rozdíl od statických metod však jejich hlavičky neobsahují klíčové slovo static. Instanční metody často pracují s instančními atributy.

  class Moje {
     int x;  // instanční atribut
     void vypisX() {  // instanční metoda
        System.out.println( x );
     }
  } 
reference a instance Od dané třídy můžeme vytvářet instance (říkáme jim též objekty, angl. instances či objects). Třída je šablona, která říká, jak budou objekty vypadat, tj. jaké budou mít atributy a metody. V našem případě bude mít každá instance třídy Moje jednu proměnnou typu int. Deklarací proměnné typu Moje zavedeme proměnnou, do níž můžeme uložit referenci (odkaz) na instanci třídy Moje.
  Moje p; 
Protože proměnné typu třída odkazují na objekty, nazývají se referenční proměnné. Každá referenční proměnná zabírá v paměti stejný prostor: 32 bitů v 32-bitové JVM a obvykle 64 bitů v 64-bitové JVM. Naproti tomu objekty různých typů mají zpravidla různou velikost. Velikost objektu je dána jeho atributy. Objekty vytváříme pomocí klíčového slova new:
  p = new Moje(); 
Po provedení tohoto příkazu bude proměnná p obsahovat odkaz na instanci třídy Moje na haldě. K atributům a metodám přistupujeme pomocí tečky. Volání metody zapisujeme pomocí jména metody a kulatých závorek:
  p.x = 1;
  p.vypisX(); 
reference a instance Od jedné třídy můžeme vytvořit libovolné množství instancí. Tyto instance jsou na sobě nezávislé.
  Moje p2 = new Moje();
  p2.x = 2;
  p2.vypisX(); 
Instanční metody slouží k provádění operací nad objekty daného typu. Např. ve třídě Moje můžeme deklarovat metodu, která zvýší hodnotu atributu x o 1:
  class Moje {
     int x;
     void zvysX() {
        x++;
     }
  } 
Instanční metody, stejně jako metody třídní, mohou mít parametry a vracet hodnotu. Parametry a návratovou hodnotu stanovíme v deklaraci metody.
  class Moje {
     int x;
     // pričte dx k x a vrátí novou hodnotu x
     int posunX( int dx ) {
        x += dx;
        return x;
     }
  } 
Třída může obsahovat tzv. konstruktor (angl. constructor). Konstruktor vypadá jako metoda, která nemá návratový typ a má jméno shodné se jménem třídy.
  class Box {
     double d;
     Box() {  // konstruktor
        d = Math.random() * 100;
     }
  } 
Konstruktor se volá při vytváření instance a obvykle provádí inicializaci objektu. Volání konstruktoru zapisujeme za klíčové slovo new. Konstruktor může mít parametry.
  class Box {
     double d;
     Box( double dd ) {  // konstruktor s parametrem
        d = dd;
     }
  } 
Má-li konstruktor parametry, musíme při vytváření objektu zadat jejich hodnoty.
  Box p = new Box( 3.5 ); 
K inicializaci atributu lze použít tzv. inicializátor (angl. initializator).
  class Cislo {
     int x = 1;  // inicializátor
  } 
Inicializátor používáme pro jednoduchou inicializaci (např. výrazem, jehož hodnota je známa při překladu). Složitější inicializace provádíme v konstruktoru. Pokud atribut neinicializujeme inicializátorem ani v konstruktoru, bude mít nulovou hodnotu. Tj. hodnotu, jejíž vnitřní reprezentace je 0. Pro číselné typy je to 0, pro typ boolean je to false, pro typ char znak na pozici 0 v tabulce Unicode a pro referenční typy hodnota null. Hodnota null znamená, že reference neodkazuje na žádný objekt.

přetížení

V jedné třídě je možné deklarovat více konstruktorů, pokud se liší seznamem parametrů. Má-li třída alespoň dva konstruktory, říkáme o konstruktoru, že je přetížený (angl. overloaded).

  class Cislo {
     int x;
     Cislo() {
        x = 1;
     }
     Cislo( int px ) {
        x = px;
     }
  } 
Při vytváření objektu se zavolá pouze jeden konstruktor. Výběr konstruktoru provede překladač podle skutečných parametrů.
  Cislo p1 = new Cislo();  // volání konstruktoru bez parametrů
  Cislo p2 = new Cislo( 97 );  // volání konstruktoru s parametrem 
vytvoření objektu Vytvoření objektu zahrnuje několik kroků:
  1. přidělení (alokace) paměti
  2. inicializace na nulu
  3. provedení inicializátorů
  4. zavolání konstruktoru

Při provádění konstruktoru tedy atributy mají definovanou hodnotu. Buď mají nulové hodnoty nebo hodnoty, které jsme jim přiřadili pomocí inicializátorů.

Dále si ukážeme třídu Bod, která popisuje bod v rovině:

  class Bod {
     int x, y;
     // konstruktory
     Bod( int px, int py ) {
        x = px;
        y = py;
     }
     Bod( Bod b ) {
        x = b.x;
        y = b.y;
     }
     // posune bod o dx a dy
     void posun( int dx, int dy ) {
        x += dx;
        y += dy;
     }
     // vytiskne souřadnice bodu
     void vytiskni() {
        System.out.printf( "[%d,%d]", x, y );
     }
     // spočte vzdálenost bodu od počátku
     double spoctiVzdalenostOdPocatku() {
        return Math.sqrt( x * x + y * y );
     }
  } 
Proměnná typu Bod obsahuje odkaz na instanci třídy Bod. Obsah této proměnné můžeme přiřadit do jiné proměnné stejného typu. Přitom dojde ke zkopírování hodnoty odkazu. Odkazovaný objekt se nezmění. přiřazení reference
  Bod b1 = new Bod( 1, 2 );
  Bod b2 = b1;  // odkaz uložený v b1 se zkopíruje do b2
  b2.vytiskni();  // vytiskne [1,2]
  b1.posun( 1, 1 );  // posune bod na [2,3]
  b2.vytiskni();  // vytiskne [2,3] 
Proměnné b1 a b2 v tomto příkladě odkazují na stejný objekt (mají stejnou hodnotu). Volání b2.vytiskni() je tedy totéž jako b1.vytiskni().

Referenční typy lze použít i pro parametry metod. Při volání metody se pak předává hodnota reference, tj. odkaz na objekt. Stejně jako u parametrů primitivního typu jde o předávání hodnotou. předávání parametrů

  static void m1( Bod b ) {
     b.posun( 1, 1 );
  }
  public static void main( String[] args ) {
     Bod p = new Bod( 1, 2 );
     m1( p );
     p.vytiskni();  // vytiskne [2,3]
  } 
Při volání metody m1() se jako parametr předá hodnota proměnné p. Jde o předávání hodnotou, tj. reference z proměnné p se zkopíruje do proměnné b. Z toho plyne, že metoda m1() nemůže změnit hodnotu skutečného parametru, tj. proměnné p. Na začátku metody bude proměnná b odkazovat na stejný objekt jako proměnná p. Voláním metody posun() dojde ke změně stavu tohoto objektu (změní se hodnoty jeho atributů). Metoda m1() tedy nemůže změnit hodnotu proměnné p, ale může změnit objekt, na který proměnná p odkazuje. Parametry jsou vždy lokální v dané metodě, tj. proměnná b je lokální v metodě m1(). Změna hodnoty parametru v metodě tedy neovlivní hodnotu skutečného parametru, v našem případě proměnné p.

Pokud v deklaraci třídy neuvedeme konstruktor, překladač do třídy automaticky vloží tzv. implicitní konstruktor (angl. default constructor). Implicitní konstruktor nemá žádné parametry a má prázdné tělo.

  class Bod {
     int x, y;
     // pokud nedeklarujeme konstruktor, překladač vloží
     // implicitní konstruktor:
     // Bod() { }
  } 
Implicitní konstruktor umožňuje vytvářet instance od tříd, které neobsahují deklaraci konstruktoru. Jeho vkládáním nám překladač ulehčuje práci. Pokud konstruktor nepotřebujeme, nemusíme jej deklarovat. Překladač vloží implicitní konstruktor pouze tehdy, pokud třída žádný konstruktor neobsahuje. Má-li třída např. konstruktor se dvěma parametry, implicitní konstruktor se nevkládá.

Úloha 1

Doplňte metodu spoctiObsah(), která vrátí obsah obdélníka.

Úloha 2

Doplňte konstruktor, který bude mít tři parametry. Parametry použijte pro inicializaci atributů.

Úloha 3

Do třídy Datum doplňte deklaraci atributů den, mesic a rok a deklaraci konstruktoru se třemi parametry.

Úloha 4

Do třídy RacionalniCislo doplňte dva konstruktory. První konstruktor bude mít dva parametry typu int a druhý konstruktor bude mít jeden parametr typu RacionalniCislo.

Úloha 5

Určete, co bude výstupem následujícího kódu.

Úloha 6

Doplňte věty.

Úloha 7

Napište ve správném pořadí.

Otázky a odpovědi

Studentka: Mistře, v jakém pořadí mám zapisovat deklarace atributů, konstruktorů a metod ve třídě? Je toto pořadí významné?
Java guru: Většinou není. Atributy, konstruktory a metody lze deklarovat v libovolném pořadí. Inicializátory se vykonají před konstruktorem i tehdy, jsou-li uvedeny až za ním.
  class Bod {
     int x = 1;  // toto se provede první
     Bod() {
        x = y = 3;  // toto třetí
     }
     int y = 2;  // toto druhé
  } 
Po vytvoření instance třídy Bod tedy budou mít atributy x a y hodnotu tři. Na pořadí inicializátorů záleží, pokud jsou na sobě závislé.
  class Souradnice {
     double x = Math.random() * 100;
     double y = x;
  } 
Takové situace se ovšem vyskytují poměrně zřídka. Složitější inicializaci totiž většinou provádíme v konstruktoru. Navíc nás překladač na nesprávné pořadí inicializátorů upozorní.
  class Souradnice {
     double x, y;
     Souradnice() {
        x = y = Math.random() * 100;
     }
  } 
Pokud jde o vzájemné pořadí deklarací atributů, konstruktorů a metod, zapisujeme je obvykle v tomto pořadí: atributy, konstruktory, metody.
Studentka: Vím, že přetížené konstruktory se mohou lišit jen pořadím parametrů. Např.:
  class Box {
     Box( int x, double y ) {
        System.out.println( "Box( int x, double y )" );
     }
     Box( double y, int x ) {
        System.out.println( "Box( double y, int x )" );
     }
  } 
Při vytváření objektu se zavolá konstruktor, jehož parametry nejlépe odpovídají skutečným parametrům. Výběr konstruktoru provede překladač podle typu skutečných parametrů.
  Box b1 = new Box( 1, 2.5 );  // parametry int a double
  Box b2 = new Box( 2.5, 1 );  // parametry double a int 
Který konstruktor se ovšem zavolá, pokud budou oba parametry typu int? Žádný konstruktor se dvěma parametry typu int ve třídě Box není. Existuje však implicitní typová konverze z typu int na typ double, takže je možné jeden z parametrů převést na double.
Java guru: Máš pravdu, v tomto případě přicházejí v úvahu dvě možnosti: buď se převede první parametr na double a zavolá se druhý konstruktor, nebo se převede druhý parametr a zavolá se první konstruktor. Protože překladač nedokáže rozhodnout, kterou možnost jsi měla na mysli, ohlásí chybu. Výběr konstruktoru tak přenechá tobě.
  Box b1 = new Box( 1, 2 );    // toto se nepřeloží
  Box b2 = new Box( 1, 2.0 );  // takto vybereme první konstruktor
  Box b3 = new Box( 1.0, 2 );  // a takto druhý 
Při překladu jiných konstrukcí se překladač chová obdobně. Vždy, když je něco nejednoznačné, ohlásí chybu a nechá programátora, aby nejednoznačnost odstranil.