Szia! Ma a Java egyik fontos fogalmáról fogunk beszélni: az interfészekrõl. A szó valószínűleg ismerős számodra. Például a legtöbb számítógépes program és játék rendelkezik interfésszel. Tágabb értelemben az interfész egyfajta „távirányító”, amely két egymással együttműködő felet köt össze. Egy egyszerű példa a mindennapi élet interfészére a TV távirányítója. Két objektumot – egy személyt és egy tévét – köt össze, és különböző feladatokat hajt végre: fel- vagy lehalkítja a hangerőt, csatornát vált, és be- vagy kikapcsolja a TV-t. Az egyik félnek (a személynek) hozzá kell férnie az interfészhez (nyomjon meg egy gombot a távirányítón), hogy a másik fél végrehajtsa a műveletet. Például, hogy a TV-t a következő csatornára váltsa. Mi több, a felhasználó nem t tudnia kell, hogyan van felszerelve a TV, vagy hogyan valósul meg a csatornaváltás folyamata belülről. Az egyetlen dolog, amihez a felhasználó hozzáfér, az a felület. A fő cél a kívánt eredmény elérése. Mi köze ennek a programozáshoz és a Java-hoz? Mindent :) A felület létrehozása nagyon hasonlít egy normál osztály létrehozásához, de helyette a szót használjukosztályban a felület szót jelöljük . Nézzük meg a legegyszerűbb Java felületet, nézzük meg, hogyan működik, és miért lenne szükségünk rá:
public interface CanSwim {
public void swim();
}
Létrehoztunk egy CanSwim felületet. Kicsit olyan, mint a távirányítónk, de egyetlen 'gombbal': a swim() metódussal. De hogyan használjuk ezt a távirányítót? Ehhez meg kell valósítanunk egy módszert, vagyis a távirányító gombunkat. Az interfész használatához programunk egyes osztályainak implementálniuk kell a metódusait. Találjunk ki egy osztályt, amelynek objektumai „tudnak úszni”. Például egy kacsa osztály megfelel:
public class Duck implements CanSwim {
public void swim() {
System.out.println("Duck, swim!");
}
public static void main(String[] args) {
Duck duck = new Duck();
duck.swim();
}
}
"Mit látunk itt? A Duck osztályt az implements kulcsszó "társítja" a CanSwim felülethez . Emlékezhetsz arra, hogy hasonló mechanizmust használtunk két osztály társítására öröklődés útján, de ebben az esetben az extends szót használtuk. teljes egyértelműség, a ' public class Duck implements CanSwim ' szó szerint így fordítható : 'A nyilvános Duck osztály implementálja a CanSwim interfészt'. Ez azt jelenti, hogy egy interfészhez társított osztálynak az összes metódusát meg kell valósítania. Megjegyzés: a mi osztályunk, mint ahogy az interfész, van egy metódusa, és van benne némi logika.Ez egy kötelező követelmény.Ha csak írunkDuck
CanSwim
swim()
public class Duck implements CanSwim
metódus létrehozása nélkül swim()
az Duck
osztályban a fordító hibát jelez: A Duck nem absztrakt, és nem írja felül az absztrakt swim() metódust a CanSwim-ben Miért? Miért történik ez? Ha a TV-példával magyarázzuk el a hibát, az olyan lenne, mintha valaki kezébe adna egy TV távirányítót egy „csatornaváltás” gombbal, amely nem tud csatornát váltani. Nyomhatod a gombot, ahányszor csak akarod, de nem megy. A távirányító önmagában nem vált csatornát: csak jelet küld a TV-nek, ami a csatornaváltás összetett folyamatát valósítja meg. Így van ez a mi kacsánkkal is: tudnia kell úszni, hogy a CanSwim
felület segítségével hívható legyen. Ha nem tudja hogyan, aCanSwim
interfész nem köti össze a két felet – a személyt és a programot. A személy nem fogja tudni használni a swim()
módszert a programon belüli úszásra Duck
. Most már világosabban megérti, mire valók az interfészek. Egy interfész leírja azt a viselkedést, amellyel az interfészt megvalósító osztályoknak rendelkezniük kell. A „viselkedés” módszerek gyűjteménye. Ha több messengert szeretnénk létrehozni, akkor a legegyszerűbb egy felület létrehozása Messenger
. Mire van szüksége minden hírnöknek? Alapszinten képesnek kell lenniük üzenetek fogadására és küldésére.
public interface Messenger{
public void sendMessage();
public void getMessage();
}
Most egyszerűen létrehozhatjuk messenger osztályainkat, amelyek megvalósítják a megfelelő felületet. A fordító maga „kényszerít” bennünket, hogy implementáljuk ezeket az osztályainkban. Távirat:
public class Telegram implements Messenger {
public void sendMessage() {
System.out.println("Sending a Telegram message!");
}
public void getMessage() {
System.out.println("Receiving a Telegram message!");
}
}
WhatsApp:
public class WhatsApp implements Messenger {
public void sendMessage() {
System.out.println("Sending a WhatsApp message!");
}
public void getMessage() {
System.out.println("Reading a WhatsApp message!");
}
}
Viber:
public class Viber implements Messenger {
public void sendMessage() {
System.out.println("Sending a Viber message!");
}
public void getMessage() {
System.out.println("Receiving a Viber message!");
}
}
Milyen előnyökkel jár ez? Ezek közül a legfontosabb a laza tengelykapcsoló. Képzelje el, hogy egy olyan programot tervezünk, amely összegyűjti az ügyféladatokat. Az Client
osztálynak feltétlenül szüksége van egy mezőre, amely jelzi, hogy a kliens melyik üzenetküldőt használja. Interfészek nélkül ez furcsán nézne ki:
public class Client {
private WhatsApp whatsApp;
private Telegram telegram;
private Viber viber;
}
Három mezőt hoztunk létre, de egy kliensnek csak egy messengere lehet. Csak azt nem tudjuk, melyik. Tehát minden lehetőséget hozzá kell adnunk az osztályhoz, hogy kommunikálni tudjunk az ügyféllel. Kiderült, hogy egy vagy kettő közülük mindig is null
, teljesen szükségtelen a programnak. Inkább a mi felületünket érdemes használni:
public class Client {
private Messenger messenger;
}
Ez egy példa a laza kapcsolásra! Ahelyett, hogy egy adott messenger osztályt adnánk meg az osztályban Client
, csak azt jelezzük, hogy az ügyfélnek van messengere. Hogy pontosan melyiket, az a program futása során kerül meghatározásra. De miért kellenek ehhez interfészek? Miért adták hozzá a nyelvhez? Ez egy jó kérdés – és a helyes kérdés! Nem érhetjük el ugyanezt az eredményt közönséges örökléssel? Az Messenger
osztály mint szülő, és Viber
, Telegram
, és WhatsApp
mint gyerekek. Valóban, ez lehetséges. De van egy bökkenő. Mint már tudja, a Java-nak nincs többszörös öröklődése. De több interfész is támogatott. Egy osztály annyi interfészt implementálhat, amennyit csak akar. Képzeljük el, hogy van egy Smartphone
osztályunk, amelynek vanApp
mező, amely az okostelefonra telepített alkalmazást jelöli.
public class Smartphone {
private App app;
}
Természetesen az alkalmazás és a messenger hasonló, de mégis különböző dolgok. A messengernek lehetnek mobil és asztali verziói is, de az App kifejezetten egy mobilalkalmazást jelent. Itt van az üzlet – ha öröklést használnánk, nem tudnánk objektumot hozzáadni Telegram
az Smartphone
osztályhoz. Hiszen az Telegram
osztály nem örökölheti egyszerre App
és Messenger
! És már örököltük, Messenger
és hozzáadtuk az osztályhoz Client
. De az Telegram
osztály mindkét felületet könnyen megvalósíthatja! Client
Ennek megfelelően az osztálynak egy Telegram
objektumot adhatunk Messenger
, és megadhatjuk az Smartphone
osztálynak mintként App
. Íme, hogyan kell ezt megtenni:
public class Telegram implements Application, Messenger {
// ...methods
}
public class Client {
private Messenger messenger;
public Client() {
this.messenger = new Telegram();
}
}
public class Smartphone {
private Application application;
public Smartphone() {
this.application = new Telegram();
}
}
Most úgy használjuk az Telegram
osztályt, ahogy akarjuk. Egyes helyeken úgy működik, mint egy App
. Más helyeken úgy működik, mint a Messenger
. Biztosan észrevette már, hogy az interfész metódusok mindig „üresek”, azaz nincs implementációjuk. Ennek egyszerű az oka: a felület leírja a viselkedést, de nem valósítja meg. „Minden objektumnak, amely megvalósítja az CanSwim
interfészt, tudnia kell úszni”: ennyit mond nekünk az interfész. A halak, kacsák és lovak úszásának konkrét módja a Fish
, Duck
, és aHorse
osztályok, nem a felület. Ahogy a csatornaváltás is a tévé feladata. A távirányító egyszerűen ad egy gombot ehhez. A Java 8-ban azonban megjelent egy érdekes kiegészítés – az alapértelmezett módszerek. Például az Ön interfésze 10 metódussal rendelkezik. Közülük 9 különböző implementációval rendelkezik különböző osztályokban, de egy mindegyikre ugyanaz. Korábban, a Java 8 előtt az interfész metódusoknak nem volt implementációja: a fordító azonnal hibát adott. Most valami ilyesmit tehet:
public interface CanSwim {
public default void swim() {
System.out.println("Swim!");
}
public void eat();
public void run();
}
A default
kulcsszó használatával létrehoztunk egy interfész metódust alapértelmezett megvalósítással. Saját implementációt kell biztosítanunk két másik metódushoz – eat()
és run()
– minden olyan osztályban, amely megvalósítja CanSwim
a . A metódussal ezt nem kell megtennünk swim()
: a megvalósítás minden osztályban ugyanaz lesz. Egyébként a múltbeli feladatokban már találkoztál interfészekkel, még ha nem is vetted észre :) Itt egy szemléletes példa: Dolgoztál a List
és Set
felületekkel! Pontosabban, az implementációikkal dolgozott – ArrayList
, LinkedList
, HashSet
, stb. Ugyanez a diagram egyértelműen példát mutat arra, amikor egy osztály több interfészt valósít meg egyszerre. Például LinkedList
megvalósítja az List
ésDeque
(kétvégű sor) interfészek. Ismeri a Map
felületet, vagy inkább annak HashMap
megvalósítását. Ez a diagram egyébként egy jellemzőt szemléltet: az interfészek örökölhetnek más interfészt. Az SortedMap
interfész örökli Map
, míg Deque
örökli Queue
. Erre akkor van szükség, ha meg akarjuk mutatni az interfészek közötti kapcsolatot, ahol az egyik interfész egy másik bővített változata. Nézzünk egy példát az interfészre Queue
. Még nem vizsgáltuk átQueues
, de meglehetősen egyszerű, és úgy működik, mint egy szokásos sorban állás az üzletben. Csak a sor végére adhat hozzá elemeket, és csak az elejétől veheti át őket. Valamikor a fejlesztőknek szükségük volt a várólista továbbfejlesztett verziójára, hogy mindkét végén hozzáadhassanak és átvehessék az elemeket. Így létrehoztak egy Deque
interfészt, ami egy kétvégű sor. A közönséges sor összes metódusával rendelkezik. Végül is ez a kétvégű sor szülője, de új metódusokat is hozzáad.
GO TO FULL VERSION