10 Generické implementace Main Entry: 1ge·ner·ic Pronunciation: j&-'ner-ik Function: adjective Etymology: French générique, from Latin gener-, genus birth, kind, class 1 a : relating to or characteristic of a whole group or class : GENERAL b : being or having a nonproprietary name c : having no particularly distinctive quality or application 2 : relating to or having the rank of a biological genus - ge·ner·i·cal·ly /-i-k(&-)lE/ adverb - ge·ner·ic·ness noun Meriam-Webster Online Dictionary
Entry Word: generic Function: adjective Text: Synonyms GENERAL 2, common, universal Antonyms specific Meriam-Webster Online Thesaurus
In metaphysics, epistemology, and logic, a general category, such as a property or relation, considered as distinct from the particular things that instantiate or exemplify it. The problem of universals concerns the question of what sort of being should be ascribed to such categories (e.g., is there any such thing as redness apart from particular red things?). The debate over the status of universals stems from Plato's theory of forms. Whereas Plato held that forms (universals) exist independently of particulars, Aristotle argued that forms exist only in the particulars in which they are exemplified. See also realism. "Universal." Britannica Concise Encyclopedia. 2004. Encyclopædia Britannica Premium Service. 10 May 2004
.
Jak vytvořit generickou implementaci zásobníku nebo řazení? Obecný způsob vytvoření více specializované třídy je vytvoření podtřídy nějaké třídy, kterou potom nazveme nadtřídou (subclass, superclass). Také hovoříme o základní a odvozené třídě nebo o rodičovské třídě a třídě potomek. Tomuto procesu se říká dědičnost. Podtřída, odvozená třída, potomek dědí vlastnosti nadtřídy, základní třídy, rodiče. Tato myšlenka byla realizována již v jazyku Simula 67. V Javě deklarujeme podtřídu B třídy A pomocí klíčového slova extends. class B extends A { ... } Více tříd může být deklarováno jako podtřídy nějaké třídy. class C extends A { ... } Vztah rodič potomek může dále pokračovat vytvořením podtřídy D například třídy C. class D extends C { ... } Vzniká tak hierarchie tříd. Třída D je ovšem také podtřídou třídy A. Podtřída - získává všechny metody a proměnné nadtřídy - může přidat nové metody a proměnné třídy - může předefinovat metody a proměnné rodiče
Považujme za generickou třídu (základní, rodičovskou) třídu Auto z první přednášky, kde můžeme namítat, že spotřeba se u osobních aut mění s obsazením a u nákladních aut s nákladem. class Auto { final float kapacitaNadrze; float obsahNadrze; float spotreba;
}
Auto(float kapacitaNadrze, float spotreba) { this.kapacitaNadrze = kapacitaNadrze; this.spotreba = spotreba; } Auto(Auto a) { kapacitaNadrze = a.kapacitaNadrze; spotreba = a.spotreba; } Auto() { kapacitaNadrze = 24.0F; spotreba = 8.0F; } float doplnNadrz() { float doplneni; doplneni = kapacitaNadrze - obsahNadrze; obsahNadrze = kapacitaNadrze; return doplneni; } float dojezd() { float dojede = obsahNadrze / spotreba * 100.0F; return dojede; } void ujelo(float vzdalenost) { obsahNadrze = obsahNadrze - vzdalenost / 100.0F * spotreba; }
Je zřejmé, že třídy OsobniAuto a NakladniAuto jsou podtřídy třídy Auto, mající kromě základní spotřeby ve třídě Auto, aktuální spotřebu v závislosti na obsazení auta nebo nákladu. class OsobniAuto extends Auto { int maxOsob; int osoby; float aktualniSpotreba;
}
OsobniAuto(float kapacitaNadrze, float spotreba, int maxOsob) { super(kapacitaNadrze,spotreba); this.maxOsob = maxOsob; aktualniSpotreba = spotreba; } private void zmenaSpotreby() { aktualniSpotreba = spotreba * (1 + 0.02F * osoby); } void nastoupili(int n) { osoby += n; zmenaSpotreby(); } void vystoupili(int n) { osoby -= n; zmenaSpotreby(); } float dojezd() { float dojede = obsahNadrze / aktualniSpotreba * 100.0F; return dojede; } void ujelo(float vzdalenost) { obsahNadrze = obsahNadrze - vzdalenost / 100.0F * aktualniSpotreba; }
Proměnné třídy Auto jsme rozšířili o maximální počet osob pro dané auto, skutečné obsazení a aktuální spotřebu, která zvyšuje základní spotřebu v závislosti od počtu osob v autě. Protože rodičovská třída má konstruktor s parametry, potomek má konstruktor, ve kterém je konstruktor rodiče volán pomocí super(). Pomocí super můžeme zpřístupnit v potomkovi proměnné i metody rodiče. Přidali jsme metody realizující nástup a výstup osob z auta. Protože nyní dojezd auta, jakož i jeho spotřeba po ujetí zadaného počtu kilometrů závisí na aktuální a ne na základní spotřebě, nutno předefinovat metody dojezd()a ujelo(). Jelikož jsme použili stejného jména jako v rodičovské třídě a také stejné formální parametry, došlo k překrytí těchto metod. Nyní můžeme s objekty třídy OsobniAuto pracovat obvyklým způsobem. OsobniAuto a = new OsobniAuto(55.0F, 5.0F, 5); System.out.println("Nacerpali jsme " + a.doplnNadrz() + " l"); System.out.println("Spotreba je "+ a.spotreba +" l/100km"); System.out.println("Dojezd je " + a.dojezd() + " km"); a.nastoupili(5); System.out.println("Aktualni spotreba je " + a.aktualniSpotreba + " l/100km"); System.out.println("Dojezd je " + a.dojezd() + "km");
Podobně můžeme od třídy Auto odvodit třídu NakladniAuto.
class NakladniAuto extends Auto { float maxNaklad; float naklad; float aktualniSpotreba;
}
NakladniAuto(float kapacitaNadrze, float spotreba, float maxNaklad) { super(kapacitaNadrze, spotreba); this.maxNaklad = maxNaklad; aktualniSpotreba = spotreba; } private void zmenaSpotreby() { aktualniSpotreba = spotreba * (1 + naklad/maxNaklad); } void naloz(float n) { naklad += n; zmenaSpotreby(); } void vyloz(float n) { naklad -= n; zmenaSpotreby(); } float dojezd() { float dojede = obsahNadrze / aktualniSpotreba * 100.0F; return dojede; } void ujelo(float vzdalenost) { obsahNadrze = obsahNadrze - vzdalenost / 100.0F * aktualniSpotreba; }
Objekt třídy OsobniAuto nebo NakladniAuto je i objektem třídy Auto, tedy referenční proměnná nadtřídy může obsahovat i odkazy na objekty podtřídy. Tedy je-li OsobniAuto a = new OsobniAuto(55.0F, 5.0F, 5); potom můžeme také psát Auto x = a; Nyní ovšem x mohlo také odkazovat na objekt třídy NakladniAuto. Jestli x odkazuje na objekt třídy OsobniAuto můžeme zjistit pomocí operátoru instanceof a je-li tomu tak, můžeme s x pracovat pomocí přetypování jako s objektem třídy OsobniAuto. (OsobniAuto)x
Mějme pole auta[] pro osobní i nákladní auta. Potom informace o nich můžeme vytisknout následovně:
Auto [] auta; ... for (int i=0; i<pocetAut; i++) { if (auta[i] instanceof OsobniAuto) { System.out.println("Osobni - max osob: " + ((OsobniAuto)auta[i]).maxOsob); } if (auta[i] instanceof NakladniAuto) { System.out.println("Nakladni - max naklad:" + ((NakladniAuto)auta[i]).maxNaklad); } }
Obecně má v Javě každá třída jedinou rodičovskou třídu tvoříce tak hierarchii tříd. Jediná třída, která nemá rodičovskou třídu a současně je tak nadtřídou všech tříd je třída Object. To nám umožní vytvořit například generický zásobník obsahující jakékoliv prvky.
class Zasobnik { private Object[] z; private int vrchol; final int maxN=10; Zasobnik() { z = new Object[maxN]; vrchol = 0; } boolean jePrazdny() { return (vrchol == 0); } void push(Object klic) { z[vrchol++] = klic; }
}
Object pop() { Object t = z[--vrchol]; z[vrchol] = null; return t; }
Nyní můžeme vkládat do zásobníku objekty různých tříd a při jejich výběru je přetypovat.
Zasobnik zasobnik = new Zasobnik(); A a = new A(); B b = new B(); zasobnik.push(a); zasobnik.push(b); ... b = (B)zasobnik.pop(); a = (A)zasobnik.pop();
Musíme si ovšem být vědomi, že porušíme-li správnost přetypování, což v případě složitějších případů může záviset na konkrétním vykonání programu, toto bude zjištěno až v průběhu vykonávání programu. zasobnik.push(a); zasobnik.push(b); ... a = (A)zasobnik.pop(); b = (B)zasobnik.pop(); Chceme-li takový zásobník použít pro základní datové typy, například int, musíme použít obalující třídy, které jsou k dispozici pro každý základní typ, což je pro int třída Integer.
Potom pro vložení int x do zásobníku musíme psát zasobnik.push(new Integer(x)) a pro výběr ((Integer) zasobnik.pop()).intValue() Uvedený způsob použití generického zásobníku odporuje definici ADT pomocí definovaného rozhraní. Řešením je použít generickou třídu pro implementaci speciální třídy, kterou nazýváme adaptér. Pro rozhraní definované pro IntZasobnik tak máme následující adaptér pro třídu Zasobnik class IntZasobnik { private Zasobnik z; IntZasobnik() { z = new Zasobnik(); }
}
boolean jePrazdny() { return z.jePrazdny(); } void push(int klic) { z.push(new Integer(klic)); } int pop() { return ((Integer)z.pop()).intValue(); }
Java poskytuje na specializaci, kromě dědičnosti, další prostředek – rozhraní. Rozhraní definuje soubor metod, bez jejich implementace. Třída, která implementuje toto rozhraní (tj. jakoby jej zdědí), musí implementovat (tj. jakoby překrýt) všechny metody rozhraní, tedy je implementovat. Deklarace rozhraní je podobna deklaraci třídy interface jmeno { // hlavičky metod } Každá třída, která implementuje toto rozhraní, což se zapíše pomocí klíčového slova implements, musí obsahovat deklarace všech metod rozhraní. Za předpokladu, že rozhraní je implementováno nějakou třídou, lze k instancím takovéto třídy přistupovat pomocí referenční proměnné typu rozhraní obdobně, jako lze přistupovat k instancím podtřídy pomocí referenčních proměnných nadtřídy. Pro generické řazení pomocí porovnávání, musí řazené prvky mít metodu pro jejich porovnání. Jinými slovy musí implementovat následující rozhraní interface Porovnatelny { boolean mensi(Porovnatelny v); }
Generické řazení vkládáním pro prvky třídy implementující toto rozhraní potom můžeme napsat následovně: class Razeni { static void razeniVkladanim( Porovnatelny [] a) { for(int i = 1; i < a.length; i++) { int j = i; Porovnatelny v = a[j];
}
}
}
for( ; j > 0 && v.mensi(a[j - 1]); j--) a[j] = a[j - 1]; a[j] = v;
Pro řazení celočíselných klíčů potom pro ně definujeme třídu IntPrvek implementující rozhraní Porovnatelny. class IntPrvek implements Porovnatelny { int hodnota; IntPrvek(int x) { hodnota = x; } int getInt () { return hodnota; } public boolean mensi(Porovnatelny v) { return hodnota < ((IntPrvek) v).hodnota; } }