// Informing = can describe information about itself
public interface Informing {
// this method is used for describing
String retrieveInfo();
}
Tomáš Pitner, Radek Ošlejšek, Marek Šabo
Jsou objektové jazyky, kde vůbec rozhraní nemáme.
Jsou dokonce objektové jazyky, kde nemáme ani třídy (JavaScript).
Většinou ale třídy jsou, u většiny OO jazyků: Java, C++, C#, Python, Ruby…
Přímočaré řešení je pak použít na implementaci nějaké potřebné funkčnosti třídu.
Problém je, že někdy máme takový požadavek na funkčnost, který se jednou třídou špatně implementuje, resp. evidentně potřebujeme více zcela různorodých tříd, které budou tento požadavek plnit.
Příklad: požadavkem je, aby objekty uměly o sobě sdělit informace,
tedy aby měly třeba metodu retrieveInfo()
.
Zcela jistě existuje mnoho typů objektů, které o sobě umějí informovat — od psa až po laserovou tiskárnu :-)
Tudíž ani není možné všechny třídy objektů, které o sobě umějí informovat, chápat jako podtřídy jedné třídy "UmímOSoběInformovat". V Javě navíc třída smí mít pouze jednu rodičovskou třídu/předka (=dědit jen z jedné rodičovské), tzn. bychom tím "spotřebovali" možného předka a žádného jiného předka už bychom nikde nemohli mít.
Velmi jednoduché rozhraní s jedinou metodou:
// Informing = can describe information about itself
public interface Informing {
// this method is used for describing
String retrieveInfo();
}
Rozhraní (interface
) je seznam metod (budoucích) tříd objektů.
Neobsahují vlastní kód těchto metod.
Je to tedy seznam hlaviček metod s popisem, co přesně mají metody dělat, typicky dokumentačním komentářem.
Rozhraní by nemělo příliš smyslu, kdybychom neměli třídy, které jej naplňují, realizují, přesněji implementují
Říkáme, že třída implementuje rozhraní, pokud obsahuje všechny metody, které jsou daným rozhraním předepsány.
třída implementuje rozhraní = objekt dané třídy se chová jak rozhraní předepisuje,
např. osoba nebo pes se chová jako běhající
Stejně jako třída, jedině namísto slova class
je interface
.
Všechny metody v rozhraní přebírají viditelnost z celého rozhraní:
viditelnost hlaviček metod není nutno psát;
rozhraní v našem kurzu budou pouze public
(není to vůbec velká chyba tak dělat stále).
Těla metod v deklaraci rozhraní se nepíší vůbec, ani složené závorky, jen středník za hlavičkou.
// Informing = can describe information about itself
public interface Informing {
// this method is used for describing
String retrieveInfo();
}
Třídy Person
a Donkey
implementují rozhraní Informing
:
public class Person implements Informing {
public String retrieveInfo() {
return "People are clever.";
}
}
public class Donkey implements Informing {
public String retrieveInfo() {
return "Donkeys are simple.";
}
}
Když třída implementuje rozhraní, musí implementovat všechny jeho metody! |
public void printInfo(Informing informing) {
System.out.println("Now you learn the truth!");
System.out.println(informing.retrieveInfo());
}
...
Person p = new Person();
printInfo(p);
...
Donkey d = new Donkey();
printInfo(d);
Parametr metody je typu rozhraní, můžeme tam tedy použít všechny třídy, které ho implementují.
To je velice časté a užitečné používat jako typ parametru rozhraní; je z toho pak dobře vidět, že daný objekt používáme jako instanci implementující určité rozhraní a konkrétní třída nás u použití nezajímá.
Java má podobně jako většina jiných jazyků operátor pro "přetypování" (type cast).
Píše se buďto a) (typ) hodnota
nebo b) (typ) odkaz_na_objekt
podle toho, co "přetypováváme".
Jeho fungování se zásadně liší podle toho, zda jde o a) o primitivní hodnotu nebo b) odkaz na objekt. **
U "přetypování" odkazů na objekty se ve skutečnosti nejedná o změnu obsahu objektu, nýbrž pouze o potvrzení, tzn. běhovou typovou kontrolu, že běhový typ objektu je ten požadovaný, na nějž "přetypováváme".
Jde tedy o potvrzení faktu, že pracujeme s objektem skutečného typu, jaký potřebujeme.
Sdělujeme překladači "Hele, já vím, že jde o osobu, tak s tím pracuj jako s osobou".
Když běhová kontrola případně selže (tedy nikoli v době překladu,ale až při spuštění), je vyvolána výjimka a běh programu přerušen.
Person p = new Person();
// OK, p je třídy Person a ta implementuje Informing.
Informing i = (Informing) p;
// Rovněž OK, protože jakýkoli objekt je
// (aspoň nepřímým) potomkem Object.
Object o = (Object) i;
U primitivních typů se jedná o skutečný převod hodnoty z původního typu na cílový, o typovou konverzi.
Např. long
přetypujeme na int
a ořeže se tím rozsah.
korektnost a případnou ztrátu informace za nás pohlídá v některých případech překladač, který samozřejmě zná obecné vlastnosti typů. Projeví se zejména při přetypování čísel.
Můžeme taky vytvořit proměnnou typu rozhraní:
public class Person implements Informing {
public String retrieveInfo() {
return "People are clever.";
}
public void emptyMethod() { }
}
...
Informing i = new Person();
i.retrieveInfo(); // ok
i.emptyMethod(); // cannot be done
Proměnná i
může používat pouze metody definované v rozhraní (ztrácí metody v třídě Person
).
To je opět velice časté a užitečné používat jako typ proměnné rozhraní.
máme různé tiskárny, různých značek
nechceme psát kód, který ošetří všechny značky, všechny typy
chceme použít i budoucí značky/typy
vytvoříme pro všechny jedno uniformní rozhraní:
public interface Printer {
void printDocument(File file);
File[] getPendingDocuments();
}
náš kód bude používat tohle rozhraní, každá tiskárna která ho implementuje, bude kompatibilní
budoucí tiskárny, které rozhraní implementují, ho budou moci používat také
@Override
Pro jistotu, že přepisujeme metodu předepsanou rozhraním a ne jinou, použijeme znovu anotaci @Override
:
public class Person implements Informing {
@Override
public String retrieveInfo() {
return "People are clever.";
}
}