11 KTE / ZPE Informační technologie Ing. Petr Kropík, Ph.D. email: pkropik@kte.zcu.cz tel.: +420 377 63 4639, +420 377 63 4606 (odd. informatiky) Katedra teoretické elektrotechniky FEL ZČU Plzeň
Metoda equals() - porovnání shodnosti objektů - nezapomeňte, že == vrátí shodu jen pro dvě proměnné odkazující na stejný objekt - sám si programuji, které položky se mají rovnat, podobný princip jako u metody clone - volání porovnávám instance p1 a p2: p1.equals(p2) - pokud metodu nepřekryji chová implicitně jako == a = new Obdelnik(10,43, "Prvni"); b = (Obdelnik) a.clone(); // klon s hlubokou kopií a.tisk(); b.tisk(); System.out.println(a.equals(b)); a.popisek.reverse(); a.tisk(); b.tisk(); System.out.println(a.equals(b)); - equals bude v obou případech false, nepřekryli jsme -> chová se tedy jako == (máme po klonování dvě různé instance) Překryji equals např. takto, je na mojí vůli, co porovnám, tj. co bude kritériem rovnosti instancí: public boolean equals(obdelnik tendruhej) { if ((this.sirka == tendruhej.sirka) && (this.vyska == tendruhej.vyska)) { return true; else return false;
V praxi jednodušeji: public boolean equals(obdelnik tendruhej) { return ((this.sirka == tendruhej.sirka) && (this.vyska == tendruhej.vyska)); Potom použití: System.out.println(a.equals(b)); - při úpravě metody equals, je vhodné, upravit i metodu následující hashcode Příklad na clone a equals: package klonovani; public class Obdelnik implements Cloneable { private int sirka, vyska; public int getsirka() { return sirka; public void setsirka(int sirka) { this.sirka = sirka; public int getvyska() { return vyska;
public void setvyska(int vyska) { this.vyska = vyska; public StringBuffer popisek; // pozor vložený objekt public Obdelnik(int sirka, int vyska, String popisek) { this.popisek = new StringBuffer(popisek); this.sirka = sirka; this.vyska = vyska; public Object clone() { Obdelnik dolly = null; try { dolly = (Obdelnik) super.clone(); // aby kopie byla hluboká dolly.popisek = new StringBuffer(popisek); // nelze: // dolly.popisek = (StringBuffer) popisek.clone(); catch (CloneNotSupportedException e) { e.printstacktrace(); return dolly; public void tisk() { System.out.println(popisek); public boolean equals(obdelnik tendruhej) {
return ((sirka == tendruhej.getsirka()) && (vyska == tendruhej.getvyska())); /** * @param args */ public static void main(string[] args) { Obdelnik a, bklon, c; a = new Obdelnik(10,43, "Prvni"); bklon = (Obdelnik) a.clone(); c = a; a.tisk(); bklon.tisk(); System.out.println(a.equals(bKlon)); System.out.println(a.equals(c)); a.popisek.reverse(); // obrátí pořadí znaků v řetězci a.tisk(); bklon.tisk(); System.out.println(a.equals(bKlon)); bklon.setsirka(122); System.out.println(a.equals(bKlon)); Metoda hashcode()
- vrací id číslo objektu pro různé objekty (různé z hlediska metody equals) vrací různá čísla proto je vhodné ji upravit, pokud měním metodu equals Metoda getclass() - vrací objekt třídy Class obsahuje informace o třídě - pozor jde o finální metodu Např.: p.getclass().getname(); Class info_a = a.getclass(); System.out.println("a.getClass hlásí: " + info_a); System.out.println(a.getClass().getName()); Tiskne: a.getclass hlásí: class geometrie.obdelnik geometrie.obdelnik Balíky - nejblíže mají ke knihovnám tříd - na disku se projeví jak vnořené adresáře: package zcu.fel.kte.moje.strileckagame; na disku: /zcu/fel/kte/moje/strileckagame - slouží pro vymezení přístupových práv souvisí s použití kvalifikátorů private, protected, nic, public. - import balíku (import všech tříd balíku proto ta *): import zcu.fel.kte.moje.strileckagame.*; - import jedné třídy z balíku: import zcu.fel.kte.moje.strileckagame.mojetrida;
import zcu.fel.kte.moje.strileckagame.jinatrida; - toto nelze: import zcu.fel.kte.moje.strileckagame.m*; Proč import? Bez importu: zcu.fel.kte.moje.strileckagame.mojetrida.mojemetoda(blaf); r = zcu.fel.kte.moje.strileckagame.mojetrida.mojemetoda(blaf) + zcu.fel.kte.moje.strileckagame.mojetrida.mojemetoda(blaf) / zcu.fel.kte.moje.strileckagame.mojetrida.mojemetoda(blaf); S importem: MojeTrida.mojeMetoda(blaf); r = MojeTrida.mojeMetoda(blaf) + MojeTrida.mojeMetoda(blaf) / MojeTrida.mojeMetoda(blaf);
Přístupová práva - omezení přístupu k celým třídám nebo k jednotlivým datovým prvkům a metodám - u metod a proměnných - nástroj pro řízení zapouzdření, řízení přístupu k prvkům instance (třídy) private nic není uveden občas přátelský spec. (package friendly) protected public V porovnání s třídou, která daný datový prvek (před nímž je některý ze specifikátorů) obsahuje: stejná třída třída ze stejného balíku dceřinná třída ze stejného balíku dceřinná třída z jiného balíku třída z jiného balíku private přístupný není příst. není příst. není příst. není příst. nic přístupný přístupný přístupný není příst. není příst. protected přístupný přístupný přístupný přístupný není příst. public přístupný přístupný přístupný přístupný přístupný - pozor: pokud neuvedeme ve třídě balík, do kterého patří, spadne do balíku default nebezpečí pro datové prvky a metody se specifikátor protected balík default je vždy automaticky importován - v rámci dědičnosti není možné oslabit (zpřísnit) přístupová práva k prvkům třídy, tj. pokud původní třída dala něco více k dispozici, nemohu to v potomkovi "schovat" Např.: v Object je metoda: protected Object clone() a viz minule my jsem překryli metodou: public Object clone()
- tj. povolit větší přístup lze, tj. mohu skryté prvky "odkrýt". Co lze měnit (tj. směr od přísnějšího k volnějšímu): private -> nic -> protected -> public - týká se dat. prvků a metod - jinak je to u celých tříd, může být rodič public class a potomek jen class (bez ozn., nic) - u tříd: - public class jen jedna v souboru, podle ní se soubor jmenuje, je využitelná, popř. má-li main i spustitelná (z vnějšku) - class v podstatě pomocné třídy pro hlavní public class v souboru (nenabízejí se ven) - třídy nabízené balíkem (tj. např. mojí aplikací, knihovnou atp.) musí být public class, třídy pro interní potřebu balíku stačí class
Rozhraní (interface) - JAVA nepodporuje vícenásobnou dědičnost (viz zač. semestru dceřinná třída nemůže mít dva rodiče) - kdy použijeme: - chceme, aby třída musela implementovat (naprogramovat) určité metody - dvě třídy jsou si podobné (obsahem atp.), ale není vhodné (z logiky věci předek by byl umělou konstrukcí) vyrábět jim společného předka (popř. to ani nejde, když jde o potomky knihovních tříd) stručně: chci zkombinovat funkčnost více tříd do jedné a něco přeprogramovat. Zmínka o přeprogramování je tu proto, že jinak by stačila kompozice objektů. Pokud tuto kombinaci potřebuji => rozhraní. Vlastnosti a deklarace rozhraní: public interface MojeRozhrani { public void whoami(); public int jinametoda(double parametr); - nesmí obsahovat proměnné - obsahuje hlavičky metod (tzv. signatury metod) platí do Java 7 - mohou obsahovat konstanty - třída může implementovat několik rozhraní
- jsou mimo hierarchii tříd (ale mezi rozhraními lze dědit, funguje i vícenás. dědičnost) - rozhraní může být implementováno několika třídami - od Java 8 lze vytvářet tzv. default metody tj. mohu v interface připravit tělo metody, takže neudělám-li ve třídě nové, použije se výchozí (default) tělo z interface: - je to důležité např. pokud přidáme novou metodu do rozhraní pokud má default tělo, nemusíme nutně hned předělávat implementující třídy Př. https://docs.oracle.com/javase/tutorial/java/iandi/defaultmethods.html Rozhraní pokračování - to, že třída implementuje rozhraní, znamená, že naprogramuje těla metod uvedených v rozhraní - a hlavička třídy vypadá takto: public class Makac extends NejakyRodic implements NejakeRozhrani, NejakeDalsiRozhrani, AjesteJedno { // tělo třídy - rozhraní často v knihovnách (metody bylo třeba seskupit pro uživatele jinak, šikovněji, než se hodilo z hlediska naprogramování těchto tříd) často u grafických knihoven - lze udělat jako proměnnou typu rozhraní (a velmi často se to dělá) kde se vezmou datové prvky? Rozhraní si v podstatě udělá instanci tříd, které jej implementují vyrobím tedy instanci třídy, která implementuje rozhraní. Dělá se tedy instance třídy, která rozhraní implementuje. Viz příklad níže. - viz dále, v podstatě jde o obdobu vynucení implementace metody jako u abstraktní metod a tříd, ovšem právě bez nutnosti společného předka - třída může být potomkem jiné třídy a navíc implementovat rozhraní: public class Makac extends NejakaTrida implements NejakeRozhrani, NejakeJineRoz {
// tělo třídy - pokud rodičovská třída implementuje rozhraní, dědí se do potomka (tj. pokud rodič implementuje rozhraní, implementuje jej i potomek) - nelze se přes proměnnou typu rozhraní dostat na metody implementujících tříd, které nejsou uvedeny v deklaraci rozhraní Příklad: public interface VstupVyst { public void cti(); public void zapisuj(); public class InpOut implements VstupVyst { // musím povinně naprogr. jinak překladač hlásí chybu public void cti() { // nějaké příkazy // musím povinně naprogr. jinak překladač hlásí chybu public void zapisuj() { // nějaké příkazy public void tiskni() { // nějaké příkazy
public class IOoperace implements VstupVyst { // musím povinně naprogr. jinak překladač hlásí chybu public void cti() { // nějaké příkazy // musím povinně naprogr. jinak překladač hlásí chybu public void zapisuj() { // nějaké příkazy public void kresli() { // nějaké příkazy Potom v nějakém mainu: VstupVyst prom, test; // všimněte si podobnosti s obdobným // postupem u abstr. tříd pole Zvirat prom = new InpOut(); test = new IOoperace(); nebo: VstupVyst[] pole; // všimněte si podobnosti s obdobným
// postupem u abstr. tříd pole zvirat pole = new VstupVyst[2]; pole[0] = new InpOut(); pole[1] = new IOoperace(); pole[0].cti(); // o.k. pole[1].cti(); // o.k. pole[0].zapisuj(); // o.k. pole[1].zapisuj(); // o.k. pole[0].tiskni(); // chyba, rozhraní tuto metodu nezná pole[1].kresli(); // chyba, rozhraní tuto metodu nezná - obejít to lze pouze přetypováním: ((InpOut) pole[0]).tiskni(); ((IOoperace) pole[1]).kresli(); - operátor instanceof umí zjistit, instance jakého typu byla vyrobena do např. položky toho pole for (int i = 0; i < pole.length; i++) { if (pole[i] instanceof InpOut) // je pole[0] instancí třídy InpOut? ((InpOut) pole[i]).tiskni(); if (pole[i] instanceof IOoperace) // je pole[0] instancí třídy IOoperace? ((IOoperace) pole[i]).tiskni();
Pozn. rozhraní s konstantou: public interface VstupVyst { public static final int KONSTANTA = 5; public void cti(); public void zapisuj(); Výčtový typ - enum http://www.algoritmy.net/article/30320/enum-19 /** * Vyctovy typ pro mesice v roce * @author Pavel Micka */ public enum Month { JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER; /** * Vrati nahodny mesic * @return nahodny mesic */ public static Month getrandom(){ Random r = new Random(); // pseudonahodny generator cisel int pocetpolozekenumu = Month.values().length; return Month.values()[r.nextInt(pocetPolozekEnumu)]; // konec enum Instance jednotlivých měsíců nyní získáme následovně: Month m1 = Month.JANUARY; // Ziskejme instanci odpovidajici lednu
Month m2 = Month.getRandom(); // Ziskejme nahodnou instanci Viz také příkad enum Planet: https://docs.oracle.com/javase/tutorial/java/javaoo/enum.html public enum Planet { MERCURY (3.303e+23, 2.4397e6), VENUS (4.869e+24, 6.0518e6), EARTH (5.976e+24, 6.37814e6), MARS (6.421e+23, 3.3972e6), JUPITER (1.9e+27, 7.1492e7), SATURN (5.688e+26, 6.0268e7), URANUS (8.686e+25, 2.5559e7), NEPTUNE (1.024e+26, 2.4746e7); private final double mass; // in kilograms private final double radius; // in meters Planet(double mass, double radius) { this.mass = mass; this.radius = radius; private double mass() { return mass; private double radius() { return radius; // universal gravitational constant (m3 kg-1 s-2) public static final double G = 6.67300E-11; double surfacegravity() { return G * mass / (radius * radius);
double surfaceweight(double othermass) { return othermass * surfacegravity(); public static void main(string[] args) { if (args.length!= 1) { System.err.println("Usage: java Planet <earth_weight>"); System.exit(-1); double earthweight = Double.parseDouble(args[0]); double mass = earthweight/earth.surfacegravity(); for (Planet p : Planet.values()) System.out.printf("Your weight on %s is %f%n", p, p.surfaceweight(mass)); If you run Planet.class from the command line with an argument of 175, you get this output: $ java Planet 175 Your weight on MERCURY is 66.107583 Your weight on VENUS is 158.374842 Your weight on EARTH is 175.000000 Your weight on MARS is 66.279007 Your weight on JUPITER is 442.847567 Your weight on SATURN is 186.552719
Your weight on URANUS is 158.397260 Your weight on NEPTUNE is 199.207413
Vnořené třídy - třída deklarovaná uvnitř jiné třídy - použijeme ji obvykle v případě, pokud chceme třídu pro potřeby pouze jedné jiné třídy První možnost: public class Vnejsi { class VnitrniVnorena { // nějaké tělo // pokračuje tělo vnější třídy VnitrniVnorena a; Potom lze také: Vnejsi.VnitrniVnorena x; Po překladu vzniknou class soubory: Vnejsi.class Vnejsi$VnitrniVnorena.class - použiji pokud v rámci vnější třídy existuje skupina dat a operací nad nimi, vhodných k zapouzdření - vnější třída Bankomat, vnitřní vnořená Fronta požadavků - vnitřní může implementovat rozhraní, může být potomkem jiné třídy atp. Druhá možnost vnitřní třída je s public třídou v jednom souboru, ale není uvnitř jiné třídy: v jednom souboru Vnejsi.java jsou spolu: class Vnitrni { // nějaké tělo public class Vnejsi { // pokračuje tělo vnější třídy
Anonymní vnořená třída package geometrie; public interface Info { void kdojsem(); public class Ctverec extends Obdelnik { class VnitrniVnorena implements Info { int i = 5; // nějaké tělo public void kdojsem() { System.out.println(toString()); // anonymni vnorena public Info informace() { return new Info() { public void kdojsem() { System.out.println(toString()); ; // je konec těla anonymní vnořené třídy // konec metody informace // konec třídy Ctverec Po překladu vzniknou class soubory: Ctverec.class Tělo anonymní vnořené třídy, nemá jméno, rovnou dělám její instanci pomocí new.
Ctverec$VnitrniVnorena.class Ctverec$1.class - ve třídě Ctverec vytváříme metodu informace(), která vrací instanci nějaké třídy, která implementuje rozhranní Info (viz polymorfismus a rozhranní výše). - POZOR: příkaz return new Info() { značí: vyrob instanci třídy, u které neuvádím jméno, ale říkám, že implementuje rozhranní Info a rovnou píšu tělo této anonymní vložené třídy do složených závorek. - Potom v mainu: Ctverec ctv = new Ctverec(2,4,3); Info inf; inf = ctv.informace(); inf.kdojsem(); nebo rovnou: Ctverec ctv = new Ctverec(2,4,3); ctv.informace().kdojsem(); - detailní použití opět viz projekt "geometrie" V grafických aplikacích často, tělo anonymní vnořené, která zde neimplementuje rozhranní, ale je potomkem třídy MouseAdapter a překrývá její metody mouseup() a mousedown():
Čtení a zápis do/ze souboru - je nutno vždy otevřít vst./výst. proud (stream) - proudy - proudy znaků - proudy bytů Znakové proudy: třídy typu: Reader metody.read Writer metody.write Bytové proudy: třídy typu: InputStream metody.read OutputStream metody.write - v těchto třídách jsou další metody např..close() - mohou vyvolat výjimku IOException (viz začátek semestru, System.in.read()) Děděním jsou odvozeny další třídy, pomocné, rozšiřují se a specializují vlastnosti. Tzv. třídy vlastností viz níže. Třídy pracující se zařízeními: soubory - znakové: FileReader, FileWriter - bytové: FileInputStream, FileOutputStream paměť - znakové: CharArrayReader, CharArrayWriter, StringReader, StringWriter
- bytové: ByteArrayInputStream, ByteArrayOutputStream, StringBufferInputStream roura - znakové: PipedReader, PipedWriter - bytové: PipedInputStream, PipedOutputStream