Poznámky k UJJ1.

Kapitola/strana Poznámka
3.3/34 Uvádíte, že:

Nesplňuje-li metoda main() zmíněné požadavky, program nebude možné přeložit.

To není přesné, protože program samozřejmě přeložit jde. Pouze není daná třída spustitelná a metoda main() se tak stává "obyčejnou" statickou metodou.


Navrhovaná oprava:
Nesplňuje-li main() zmíněné požadavky, půjde sice zdrojový soubor přeložit, ale nebude možné jej spustit.
P.Herout

4.2/69 Inicializace třídy:
  • Škoda, že není popsána také možnost, že lze pro inicializaci využít nestatický inicializační blok.
    Tato konstrukce je popsána v Java Language Specification, kapitola 8.6.

    Následující příklad ukazuje jeho použití:

    public class Initializer {
    
      private Object field1 = new Object() {
        {
          System.out.println("Field 1 init");
        }
      };
    
      {
        /* Instance initializer */
        System.out.println("Nestaticky inicializator");
      }
    
      private Object field2 = new Object() {
        {
          System.out.println("Field 2 init");
        }
      };
    
      public Initializer() {
        System.out.println(getClass() + ": prazdny konstruktor");
      }
    
      public Initializer(int param) {
        System.out.println(getClass() + ": konstruktor s parametrem");
      }
    
      public static void main(String args[]) {
        new Initializer();
        new Initializer(1);
      }
    }
    
    Výstupem programu bude:
    Field 1 init
    Nestaticky inicializator
    Field 2 init
    class Initializer: prazdny konstruktor
    Field 1 init
    Nestaticky inicializator
    Field 2 init
    class Initializer: konstruktor s parametrem
    
    Z výstupu je dobře patrné pořadí jednotlivých kroků inicializace. Z programu je také vidět, že nestatický inicializační blok je jedinou možností, jak provést dodatečnou inicializaci v anonymních vnořených třídách.

    Inicializačních bloků může být i více, pořadí jejich volání závisí na umístění ve zdrojovém kódu.

  • Dle mého názoru by bylo vhodné uvést přesné pořadí operací v jakém konstrukce objektu probíhá.
    To znamená:
    1. Konstruktor nadtřídy.
    2. Atributy třídy deklarované s inicializační výrazem + nestatické inicializační bloky.
    3. Konstruktor třídy.

  • Chtěl bych upozornit na jedno úskalí při použití statických dat v třídě. Jedná se o to, že za jistých okolností může být statická inicializace provedena vícekrát. To je způsobeno tím, že při prvním odkazu na třídu je tato pomocí class loaderu načtena do JVM, přičemž proběhne statická inicializace. Následně program může vytvářet instance této třídy a pracovat s nimi. Problém nastává, jakmile není v programu odkaz na žádnou instanci této třídy. Tím pádem se třída stává "adeptem pro odklizení" pomocí garbage collectoru. Pokud je třída opravdu vyjmuta z JVM, tak při dalším odkazu na tuto třídu se třída načte a statická inicializace se provede podruhé.

    V mnoha případech toto chování nevadí, ale programátor by si měl uvědomit tuto zvlášnost Javy a vyvarovat se provádění operací ve statických inicializátorech, které lze provést pouze jednou.
    Jako řešení tohoto problému lze použít spustění JVM s parametrem -noclassgc -- potom ale nejsou z paměti odstraňovány žádné nepoužívané třídy.

9.5/152 V knize je uveden kód podobný následujícímu:

int i = 123;
String s = String.valueOf(i);
System.out.println("i: " + s);

Tento způsob mi přijde zavádějící. Otázka zní proč nebylo využito standardního operátoru '+' pro spojování řetězců. Kód může vypadat následovně:

int i = 123;
System.out.println("i: " + i);

Přitom přeložená třída je stejná jako předešlá, pouze je zápis mnohem přehlednější.

Podobný problém je vidět v poznámce pod čarou v 9.7/154 -- zde je nečitelnost kódu ještě markantnější:
  String s = "obr".concat(String.valueOf(i)).concat(".jpg")
místo
  String s = "obr" + i + ".jpg"

Zarážející na tom je, že operátor '+' běžně používáte a doporučujete při volání metod System.out.println(). Čtenář tak nabyde dojmu, že metoda System.out.println() je "magická", ale přitom se jedná o obyčejnou metodu, která je pouze přetížena pro všechny základní typy.

13.8/221 Zde píšete, že se u atributů rozhraní neuvádí explicitně jejich modifikátory. V Java language specification se skutečně píše, že se by se tyto modifikátory neměly uvádět (protože atributy rozhraní mají implicitní modifikátory public static final a tyto nelze dále měnit).

Praxe je však úplně jiná. Pokud nahlédnete do zdrojových kódů Java Core API, tak zjistíte, že se stalo nepsaným standardem tyto modifikátory uvádět jak u atributů, tak u metod rozhraní.

14/224 Zde by bylo, dle mého názoru, zajímavé uvést poznámku pro programátory v C++, že privátní, statické a final metody nevyužívají pozdní vazbu a vysvětlit důvody.

Jelikož metody s modifikátorem final není možné dále předefinovávat, nepotřebuje JVM při jejich volání využívat pozdní vazby. Pro třídu a její potomky je díky této vlastnoti již v době překladu známa metoda, která se má zavolat. To samé platí pro private metody, díky tomu, že každá private metoda je automaticky také final.

20.6, 20.7
  • Není zde uvedeno a není vysvětlen důvod, proč metody wait(), notify() a notifyAll() musí vlastnit "zámek" na objekt na kterém jsou volány, jinak nastane výjimka IllegalMonitorStateException.

    Tyto metody manipulují se zámkem objektu.

    • metoda wait() se vzdává vlastnictví zámku ve prospěch jiných vláken
    • metody notify() a notifyAll() po skončení synchronizované sekce ve které byly zavolány, probudí vlákna čekající na daný zámek
    Z toho vyplývá, že pokud chtějí tyto metody se zámkem manipulovat, musí jej nejprve vlastnit.

  • Bylo by zajímavé uvést, že stejně jako synchronizované nestatické metody používají zámek instance (tzn., že synchronized metoda používá stejný zámek jako blok synchronized(this)), tak statické metody používají zámek objektu Class příslušné třídy (tzn., že synchronized static metoda používá stejný zámek jako blok synchronized(getClass())).