4. seminář - Dědičnost, rozsahy platnosti

TEACHINGJJ1

Zapouzdření

  • Vnitřní a vnější rozhraní objektu
  • Viditelnost dané entity ovlivněna modifikátory přístupu:
    • private - k této entitě má přístup jen objekt samotný
    • protected - k této entitě má přístup cokoliv v rámci stejného balíčku a případní potomci mimo balíček
    • public - k této entitě má přístup kdokoliv (veřejná API balíčku)
    • default - k této entitě má přístup cokoliv v rámci stejného balíčku. Výchozí modifikátor, nepíše se.
  • Speciální metody pro kontrolovaný přístup k atributům:
    • čtení - getter
    • zápis - setter
    • může jich být více (polymorfismus) anebo taky nemusí být žádný
  • Ukázka: Máme třídu Car odkaz, která pomocí zapouzdření zajistí aby:
    • Rychlost šla nastavovat jen v rozsahu min-max rychlosti auta.
    • Nabírání a vysazování posádky bylo možné, jen když auto stojí.
    • SPZ nešla po vytvoření změnit vůbec.
    • Všimněte si, že v kódu třídy Car můžu s danou instancí stále dělat, co chci - je má zodpovědnost, aby to dávalo smysl. Ale uživatel této třídy už může používat jen mnou nadefinované veřejné rozhraní, a tedy nemůže například vysadit posádku ve 100 Km/h.

Statické metody a atributy

Dosud jsme všechny metody a atributy definovali jako instační. Atributy a metody byli asociované s instancí dané třídy. Každá instance měla jiné hodnoty atributů se kterými pak prováděli svůj kód metody. V Javě ale můžeme definovat i atributy a metody sdílené všemi instancemi. Atrobut tedy náleží třídě místo instance. Aby metoda (atribut) byla sdílená všemi instacemi, tak k tomu použijeme klíčové slovo static. Na tyto metody (atributy) se odkazujeme pomocí jména třídy.

Příklad


    public static class Person {

        // Vnorena trida
        // Ukazeme si hezci zpusob.
        public static class Status {
            public static final String SENIOR = "Senior";
            public static final String JUNIOR = "Junior";
            public static final String ADULT = "Adult";
        }

        private static final int JUNIOR_AGE_THRESHOLD = 18;
        private static final int SENIOR_AGE_THRESHOLD = 65;

        private int age;

        public Person(int age) {
            this.age = age;
        }

        public int getAge() {
            return age;
        }

        public boolean setAge(int age) {
            if (age < 0) { return false; }
            this.age = age;
            return true;
        }

        public String getStatus() {
            if (this.age <= JUNIOR_AGE_THRESHOLD) {
                return Status.JUNIOR;
            } else if (this.age <= SENIOR_AGE_THRESHOLD) {
                return Status.SENIOR;
            }
            return Status.ADULT;
        }
        
        public static int[] getAgeThresholds() {
            return new int[] {JUNIOR_AGE_THRESHOLD, SENIOR_AGE_THRESHOLD};
        }
    }

    public static void main(String[] args) {
        // Prace s vnorenou tridou

        Person person = new Person(33);

        if (person.getStatus().equals(Person.Status.SENIOR)) {
            System.out.println("Osoba je senior");
        } else if (person.getStatus().equals(Person.Status.JUNIOR)) {
            System.out.println("Osoba je junior");
        } else {
            System.out.println("Osoba je dospely");
        }
        
        // Prace se statickou metodou
        int[] ageThresholds = Person.getAgeThresholds();
    }

Poznamka k porovnavani retezcu

Pokud chceme porovnávat adresy (mista kde jsou objekty uloženy), tak použijeme operátor ==. V případě, že potřebujeme porovnávat obsah objektu, tak musíme použít metodu equals. Řetězce v příkladu níže jsou uloženy následovně:

  • Řetězce s1 a s2 jsou uloženy v tzv Java String Pool a obě proměnné odkazují na stejnou hodnotu v paměti prot je výsledek operace == vždy true.
  • Hodnota s3 je ale alokována na haldě a jedná se tedy o jinou instanci.


    String s1 = "HELLO";
    String s2 = "HELLO";
    String s3 =  new String("HELLO");

    System.out.println(s1 == s2); // true 
    System.out.println(s1 == s3); // false
    System.out.println(s1.equals(s2)); // true
    System.out.println(s1.equals(s3)); // true

Dědičnost

  • Pravidlo … is a … dědičnost
  • Pravidlo … has a … kompozice
  • Příklad: Co kdybychom do našeho dopravního systému chtěli přidat i motorky, kola, vlaky, …?
    • Třídy Motorbike, Bicycle, Train, Bus, Tram, …
    • Mají toho mnoho společného, abstrahujme tedy společné do třídy Vehicle - dopravní prostředek. Všechny třídy výše pak budou dědit z Vehicle a jen doplní svá specifika.
  • V kódu potomka se dá přistupovat k metodám a (přístupným) atributům předka přes klíčové slovo super.
  • Třída může překrýt implementaci metody předka. Pokud jde o instančí metodu, pak je přepsána a k metodě předka už se přes daný objekt (mimo kód potomka a super) nedostaneme. Přepis (čehokoliv z předka) se označuje anotací @Override.
  • Dědičnost se dá omezit
    • Označíme-li třídu klíčovým slovem final, tak z ní nepůjde dále dědit.
    • Označíme-li metodu klíčovým slovem final, tak nepůjde překrýt.
    • Od Java 17 máme sealed třídy. Sofistikovanější mechanizmus pro omezení dědičnosti (lze omezit, kdo může dědit z naší třídy).
  • Každá třída může dědit z nejvýše jedné třídy. Klíčové slovo extends.

Abstraktní třídy

  • Jsou i situace, kdy spostu funkcionality je společné a chceme ji tedy definovat na jednom místě v nějaké nadřazené třídě, ale přesto nechceme/nemůžeme dovolit vytváření instancí přímo této nadřazeneé třídy. Třeba protože nějakou funkcionalitu vyžadujeme, ale její konkrétní implememtaci můžeme dodat, až po vybrání konkrétního typu odvozeného od této nadřazené třídy.
  • Podobně se může stát, že část funcionality jsme schopni udělat už rovnou obecně, ale část funkcinality, kterou vyžadujeme, musí dodat až potomci. Tedy chceme zajistit, aby každý potomek měl nějakou metodu, ale nevíme, jakým způsobem to jendotlivý potomci udělají. Jinými slovy známe hlavičku této metody, ale neznme jeí tělo. Metodu tedy přidáme jako abstraktní - přidáme klíčové slovo abstract a nevytvoříme ji žádné tělo (místo těla napíšeme jen středník). Pokud je ve třídě abstraktní metoda, tak i třída musí být abstraktní.
  • Příklad: Naše základní třída Vehicle. Nechceme, aby se nám v systému vyskytovaly dopravní prostředky neznámého typu, ale zároveň spoustu společné funkcionality můžeme implementovat už zde. Třídu Vehicle tedy označíme za abstraktní klíčovým slovem abstract. Tím zamezíme vytváření přímo jejich instancí - všechny instance budou instance nějakého jejího konkrétního potomka.
  • Programátor vždy rozhoduje sám, jestli je podle něj lepší použít abstraktní třídu nebo interface.
  • Abstraktní třídy nemohou být final.
  • instanceof
    • Jak zjistit, jestli daný objekt je instancí dané třídy (zahrnuje i, že je instancí potomka) nebo jestli implementuje dané rozhraní? Klíčové slovo instanceof.
  • Záznamy (Record)

    • Popis v release notes.
    • Slouží k jednoduché agregaci souvisejících dat.
    • Je to vlastně omezená třída s jednodušším zápisem a konstantním obsahem.
    • Dodáme název a seznam atributů, zbytek (gettery, konstruktor, toString(), equals(…), hasCode(…)) doděla překladač.
    • Vše dodělané překladačem můžeme přepsat (konstruktor i ve zkrácené formě), ale pak za to ručíme sami.
    • Můžeme přidat další libovolný static obsah a instanční metody (avšak ne instanční atributy).
    • Všechny třídní atributy budou vždy final, tedy nemá smysl uvažovat settery.
    • Každý record je implicitně final class, tedy z nich nelze dále dědit.
    • Další vlastnosti zatím mimo naše možnosti - pro zájemce odkaz výše.

    Kompletni příklad na dědičnost a rozhraní si můžete stáhnout zde.

    Úkoly

    Implementujte třídu Point představující bod v rovině určený dvěma souřadnicemi.

    V tříde Point implementujte metodu double distance(Point p) vracející vzdálenost od daného bodu.

    Implentujte třídu Line představující úsečku. Implementujte metody: double getLength() a double distance(Point p). Vzdálenost bodu od úsečky berte v tomto případě jako nejmenší vzdálenost mezi bodem a všemi body ležícími na úsečce.

    Implementujte třídu Rectangle představující obdelník, který má strany vodorovné s osami X a Y. Třída by měla mít dva konstruktory, jeden vytvářející obdelník pomocí dvou bodů, daląí vytvářející obdelník na základě bodu, výąky a ąířky.

    Implementujte metodu double getArea() vracející obsah daného obrazce.

    Implementujte metodu double distance(Point p) vracející vzdálenost od daného bodu. Vzdálenost bodu od obdelníku berte v tomto případě jako nejmenší vzdálenost mezi bodem a všemi body ležícími na stranách obdelníku.

    Implementujte třídu Square představující čtverec. Třída by měla mít konstruktor vytvářející objekt na zákládě souřadnice a délky strany.

    Implementujte metodu double getArea().

    Implementujte metodu double distance(Point p) vracející vzdálenost od daného bodu.

    Implementujte třídu Circle představující kruľnici. Třída by měla mít konstruktor vytvářející objekt na zákládě souřadnice a poloměru.

    Implementujte metodu double getArea().

    Implementujte metodu double distance(Point p) vracející vzdálenost od daného bodu. Vzdálenost bodu od kružnice berte v tomto případě jako nejmenší vzdálenost mezi bodem a všemi body ležícími na kružnici. Vzdálenost nemůže mít zápornou hodnotu!

    Třídy z předchozích příkladů upravte tak, aby se díky kompozici a dědičnosti omezila redundance kódu. Pokud to má opodstatnění, použijte rozhraní.