Java interfejsi — sve što vam treba na jednom mestu, jednostavno i razumljivo
„Danas počinjemo sa Java interfejsima", rekao sam Sanmej. „Za objektno orijentisano programiranje apstrakcija je izuzetno privlačna odlika. Ako programer ima slab apstraktan način razmišljanja, naići će na mnoge teškoće u programiranju i neće useti da pretoči poslovne zahteve u konkretan kod. U Javi se apstrakcija može postići na dva načina: jedan je junak prethodnog članka — apstraktna klasa, a drugi je današnji glavni lik — interfejs."
„Erge, pre nego što počnemo, čestitam ti. Videla sam na tvom Momentsu da je open-source baza znanja Put ka naprednom Javi na GitHub-u dobila prvu donaciju — iako je samo jedan juan, i ja se radujem zajedno s tobom." Na Sanmejinom licu bio je samouveren osmeh, kao da je taj novac dat njoj.

PS: U periodu od 29.04.2021. do 11.02.2023. „Ergeov put ka naprednom Javi” dobio je ukupno 58 donacija. Zaista sam veoma zahvalan svima na priznanju i podršci, nastaviću da uporno stvaram.
„Jeste, kada sam jutros ustao i video tu poruku, zaista mi je bilo drago — iako je samo jedan juan, to je prva donacija mom open-source projektu, a i prva u mom životu. Zaista sam veoma zahvalan tom čitaocu; dan vredan pamćenja." Ni sam nisam mogao da prikrijem uzbuđenje.
„Sa ovakvom podrškom, verujem da ćeš imati još više motivacije da nastaviš!" Današnje Sanmejine reči su zaista bile naročito mile.

„Jeste, jeste, hajde da počnemo!"
01. Definisanje interfejsa
„Šta je to interfejs?" Sanmej je vešto iskoristila pauzu i pitala.
Interfejs se definiše ključnom reči interface i može da sadrži konstante i metode. Pogledaj sledeći primer.
public interface Electronic {
// konstanta
String LED = "LED";
// apstraktna metoda
int getElectricityUse();
// statička metoda
static boolean isEnergyEfficient(String electtronicType) {
return electtronicType.equals(LED);
}
// podrazumevana metoda
default void printDescription() {
System.out.println("Elektronski");
}
}Pogledajmo bytecode ovog koda nakon dekompilacije.
public interface Electronic
{
public abstract int getElectricityUse();
public static boolean isEnergyEfficient(String electtronicType)
{
return electtronicType.equals("LED");
}
public void printDescription()
{
System.out.println("\u7535\u5B50");
}
public static final String LED = "LED";
}Jeste li primetili? Sve promenljive ili metode definisane u interfejsu automatski dobijaju public ispred sebe.
Sada ću redom objasniti ključne pojmove iz interfejsa Electronic.
1) Promenljive definisane u interfejsu kompajler automatski obeležava sa public static final (pogledajte dekompilovani bytecode), što znači da je promenljiva LED u gornjem primeru zapravo konstanta.
U zvaničnoj Java dokumentaciji to je ovako definisano:
Every field declaration in the body of an interface is implicitly public, static, and final.
Drugim rečima, interfejs se može koristiti kao klasa konstanti, pri čemu se public static final može i izostaviti — na prvi pogled dobar izbor, zar ne?
Međutim, taj izbor nije preporučljiv. Jer je osnovna namena interfejsa apstrakcija metoda, dok interfejs sa konstantama „zagađuje” prostor imena promenljivih u izvedenim klasama.
2) Metode koje nisu obeležene sa private, default ili static implicitno su apstraktne i kompajler im automatski dodaje public abstract. Drugim rečima, metoda getElectricityUse() u gornjem primeru je zapravo apstraktna metoda bez tela — i to je osnovna namena interfejsa.
3) Od Jave 8 interfejs sme da sadrži statičke metode, kao na primer metoda isEnergyEfficient() u gornjem primeru.
Statičku metodu ne može pozivati objekat klase koja implementira taj interfejs; može se pozvati samo preko imena interfejsa, na primer Electronic.isEnergyEfficient("LED").
Razlog uvođenja statičkih metoda u interfejse jeste pružanje jednostavnog mehanizma koji omogućava poziv metode bez kreiranja objekta, čime se povećava konkurentnost interfejsa.
4) Dozvola za definisanje default metoda u interfejsu takođe je stigla sa Javom 8, kao na primer metoda printDescription() u gornjem primeru. Ona se uvek sastoji od bloka koda koji pruža podrazumevanu implementaciju klasama koje implementiraju interfejs, a ne redefinišu tu metodu. Pošto pruža podrazumevanu implementaciju, mora imati telo metode — drugim rečima, nakon podrazumevane metode ne sme stajati samo znak ; jer će kompajler prijaviti grešku.

„A zašto uopšte definisati podrazumevane metode u interfejsu?" radoznalo upita Sanmej.
Razlozi za dozvolu podrazumevanih metoda u interfejsu su prilično čvrsti: jedan interfejs može imati više implementirajućih klasa, i sve one moraju implementirati apstraktne metode tog interfejsa, inače kompajler prijavljuje grešku. Ako bismo želeli da svim implementirajućim klasama dodamo neku konkretnu metodu, bez pomoći default metoda morali bismo ih ručno, jednu po jednu, da izmenimo.
Iz dosadašnjih primera možemo izvući sledeće zaključke:
- U interfejsu je dozvoljeno definisati promenljive
- U interfejsu je dozvoljeno definisati apstraktne metode
- U interfejsu je dozvoljeno definisati statičke metode (od Jave 8)
- U interfejsu je dozvoljeno definisati podrazumevane metode (od Jave 8)
Pored toga, treba da znamo i ovo:
1) Interfejs ne sme da se instancira direktno, inače kompajler prijavljuje grešku.

Potrebno je definisati klasu koja implementira interfejs, kao u sledećem primeru.
public class Computer implements Electronic {
public static void main(String[] args) {
new Computer();
}
@Override
public int getElectricityUse() {
return 0;
}
}A zatim instancirati.
Electronic e = new Computer();2) Interfejs može biti prazan — može ne definisati ni promenljive ni metode. Najtipičniji primer je interfejs Serializable iz paketa java.io.
public interface Serializable {
}Interfejs Serializable služi kao marker za konkretnu implementaciju serijalizacije, odnosno dovoljno je da neka klasa implementira interfejs Serializable i ona se već može serijalizovati.
3) Nemojte koristiti ključnu reč final pri definisanju interfejsa, jer će kompajler prijaviti grešku — interfejs je upravo zato da ga podklase implementiraju, a final sprečava takvo ponašanje.

4) Apstraktne metode interfejsa ne smeju biti private, protected ili final, jer u svakom od tih slučajeva kompajler prijavljuje grešku.

5) Promenljive interfejsa implicitno su public static final (konstante), pa im se vrednost ne može promeniti.
02. Uloga interfejsa
„A šta interfejs uopšte može da radi?" Sanmej je vešto ubacila pitanje u pravom trenutku.
Prvo, daje određenim klasama funkcionalnosti koje želimo — na primer, klasa koja implementira interfejs Cloneable ima mogućnost kopiranja, a klasa koja implementira Comparable ili Comparator ima mogućnost poređenja.
Cloneable je, kao i Serializable, markerski interfejs — unutra je potpuno prazan. Klasa koja implementira Cloneable može koristiti metodu Object.clone(), inače će biti bačen CloneNotSupportedException.
public class CloneableTest implements Cloneable {
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
CloneableTest c1 = new CloneableTest();
CloneableTest c2 = (CloneableTest) c1.clone();
}
}Nakon pokretanja nema greške. Sada uklonimo implements Cloneable.
public class CloneableTest {
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public static void main(String[] args) throws CloneNotSupportedException {
CloneableTest c1 = new CloneableTest();
CloneableTest c2 = (CloneableTest) c1.clone();
}
}Nakon pokretanja baca se CloneNotSupportedException:
Exception in thread "main" java.lang.CloneNotSupportedException: com.cmower.baeldung.interface1.CloneableTest
at java.base/java.lang.Object.clone(Native Method)
at com.cmower.baeldung.interface1.CloneableTest.clone(CloneableTest.java:6)
at com.cmower.baeldung.interface1.CloneableTest.main(CloneableTest.java:11)Drugo, Java principijelno podržava samo jednostruko nasleđivanje, ali putem interfejsa može se postići višestruko nasleđivanje.
Ako dve klase zajednički nasleđuju (extends) istu roditeljsku klasu, metode roditeljske klase biće redefinisane u obe podklase. Zatim, ako nova klasa nasledi obe te podklase, kompajler pri pozivu redefinisane metode ne može da odredi čiju metodu treba pozvati. Upravo je to poznati problem romba, prikazan na sledećoj slici.

Jednostavno objašnjenje: ClassC nasleđuje i ClassA i ClassB; kada objekat klase ClassC poziva metodu koju su ClassA i ClassB redefinisale, nije jasno da li pozvati metodu klase ClassA ili metodu klase ClassB.
Interfejsi nemaju takav problem. Definišimo dva interfejsa: Fly zna da leti, a Run zna da trči.
public interface Fly {
void fly();
}
public interface Run {
void run();
}Zatim neka klasa Pig istovremeno implementira oba interfejsa.
public class Pig implements Fly,Run{
@Override
public void fly() {
System.out.println("Svinja koja leti");
}
@Override
public void run() {
System.out.println("Svinja koja trči");
}
}Na određeni način, interfejs ostvaruje cilj višestrukog nasleđivanja: u stvarnom svetu svinja zaista samo trči, ali u očima Lej Džuna svinja koja stoji na mestu gde duva vetar može i da leti — potrebno joj je dati više sposobnosti, što apstraktnom klasom nije moguće, već samo interfejsom.
Treće, omogućava polimorfizam.
Šta je polimorfizam? Uprošćeno rečeno, isti događaj se na različitim objektima manifestuje kroz različite ishode: klik levim dugmetom miša na znak X u prozoru zatvara prozor, dok klik na hipervezu otvara novu web stranicu.
Polimorfizam se može ostvariti kroz odnos nasleđivanja (extends) ili kroz interfejs.
Interfejs Shape predstavlja jedan oblik.
public interface Shape {
String name();
}Klasa Circle implementira interfejs Shape i redefiniše metodu name().
public class Circle implements Shape {
@Override
public String name() {
return "Krug";
}
}I klasa Square implementira interfejs Shape i redefiniše metodu name().
public class Square implements Shape {
@Override
public String name() {
return "Kvadrat";
}
}A onda pogledajmo test klasu.
List<Shape> shapes = new ArrayList<>();
Shape circleShape = new Circle();
Shape squareShape = new Square();
shapes.add(circleShape);
shapes.add(squareShape);
for (Shape shape : shapes) {
System.out.println(shape.name());
}Time je ostvaren polimorfizam: referentni tip promenljivih circleShape i squareShape jeste Shape, ali prilikom izvršenja metode shape.name() Java virtuelna mašina zna da li da pozove metodu name() klase Circle ili metodu name() klase Square.
Da navedemo tri preduslova za polimorfizam:
- Postoji odnos nasleđivanja — na primer, Circle i Square implementiraju interfejs Shape.
- Podklasa redefiniše metodu roditeljske klase — Circle i Square redefinišu metodu
name().
- Podklasa redefiniše metodu roditeljske klase — Circle i Square redefinišu metodu
- Referenca roditeljske klase ukazuje na objekat podklase — circleShape i squareShape su tipa Shape, ali prva ukazuje na objekat klase Circle, a druga na objekat klase Square.
Zatim, pogledajmo rezultat testa:
Krug
KvadratTo znači da, iako je u for petlji tip promenljive shape Shape, pri pozivu metode name() sistem zna da objekat Circle treba da pozove metodu name() klase Circle, a objekat Square metodu name() klase Square.
03. Tri obrasca upotrebe interfejsa
U programiranju, dobar obrazac dizajna može učiniti da sa manje koda postignemo više. Pri radu sa interfejsima često se koriste tri obrasca: obrazac strategije, obrazac adaptera i obrazac fabrike.
1) Obrazac strategije
Ideja obrasca strategije jeste da se za grupu algoritama svaki algoritam smesti u implementirajuću klasu koja ima zajednički interfejs; dizajner interfejsa može izmeniti algoritam bez uticaja na pozivaoca. Primer:
// interfejs: trener
interface Coach {
// metoda: odbrana
void defend();
}
// Žoze Murinjo
class Hesai implements Coach {
@Override
public void defend() {
System.out.println("Odbrana osvaja titule");
}
}
// Pep Gvardiola
class Guatu implements Coach {
@Override
public void defend() {
System.out.println("Napad je najbolja odbrana");
}
}
public class Demo {
// parametar je interfejs
public static void defend(Coach coach) {
coach.defend();
}
public static void main(String[] args) {
// prosleđujemo različite objekte istoj metodi
defend(new Hesai());
defend(new Guatu());
}
}Metoda Demo.defend() može prihvatiti trenere različitih stilova i, u zavisnosti od prosleđenog objekta, proizvesti različito ponašanje — to se naziva „obrazac strategije”.
2) Obrazac adaptera
Ideja obrasca adaptera jeste da se prema potrebama pozivaoca preusmeri originalni interfejs. Najpoznatiji adapter iz svakodnevnog života jeste HDMI (engleski: High Definition Multimedia Interface) kabl koji istovremeno može slati audio i video signal. Primer obrasca adaptera:
interface Coach {
void defend();
void attack();
}
// apstraktna klasa implementira interfejs i prazni metode
abstract class AdapterCoach implements Coach {
public void defend() {};
public void attack() {};
}
// nova klasa nasleđuje adapter
class Hesai extends AdapterCoach {
public void defend() {
System.out.println("Odbrana osvaja titule");
}
}
public class Demo {
public static void main(String[] args) {
Coach coach = new Hesai();
coach.defend();
}
}U interfejsu Coach definisane su dve metode (defend() i attack()); ako klasa direktno implementira taj interfejs, mora implementirati obe metode.
Ako nam treba implementacija samo jedne od njih, možemo kao međusloj iskoristiti apstraktnu klasu, odnosno adapter (AdapterCoach): ta apstraktna klasa implementira interfejs i njene metode su prazne (telo metode sadrži samo par vitičastih zagrada). Tada nova klasa može zaobići interfejs i naslediti apstraktnu klasu, pa redefinisati samo metode koje su joj potrebne, umesto svih metoda iz interfejsa.
3) Obrazac fabrike
Ni obrazac fabrike nije teško razumeti: koja fabrika — takav proizvod. BWM fabrika proizvodi BMW, fablika Mercedes-a proizvodi Mercedes; A-nivo akademija školuje A-nivo trenera, C-nivo akademija školuje C-nivo trenera. Primer:
// trener
interface Coach {
void command();
}
// akademija za trenere
interface CoachFactory {
Coach createCoach();
}
// A-kategorija trener
class ACoach implements Coach {
@Override
public void command() {
System.out.println("Ja sam trener sa A licencom");
}
}
// akademija za A-kategoriju
class ACoachFactory implements CoachFactory {
@Override
public Coach createCoach() {
return new ACoach();
}
}
// C-kategorija trener
class CCoach implements Coach {
@Override
public void command() {
System.out.println("Ja sam trener sa C licencom");
}
}
// akademija za C-kategoriju
class CCoachFactory implements CoachFactory {
@Override
public Coach createCoach() {
return new CCoach();
}
}
public class Demo {
public static void create(CoachFactory factory) {
factory.createCoach().command();
}
public static void main(String[] args) {
// Za jedan tim, kakav je trener potreban, takva se akademija i traži.
// Akademija će timu prezentovati trenera odgovarajućeg nivoa.
create(new ACoachFactory());
create(new CCoachFactory());
}
}Postoje dva interfejsa: jedan je Coach (trener), koji može command() (komanduje timom); drugi je CoachFactory (akademija za trenere), koja može createCoach() (školuje odličnog trenera). Zatim klasa ACoach implementira interfejs Coach, a klasa ACoachFactory implementira interfejs CoachFactory; klasa CCoach implementira interfejs Coach, a klasa CCoachFactory implementira interfejs CoachFactory. Kada je potreban A-nivo trener, ide se kod A-nivo akademije; kada je potreban C-nivo trener, ide se kod C-nivo akademije.
Po istoj logici, možemo dodati i klasu BCoach koja implementira interfejs Coach i klasu BCoachFactory koja implementira interfejs CoachFactory, čime se stalno obogaćuje rezerva trenera.
„Kako je, Sanmej, nije lako primiti toliko znanja odjednom, zar ne?"
„Zapravo je sasvim u redu, Erge. Pošto si sve tako detaljno objasnio, sve sam zabeležila 📒. U učenju, kad si pažljiviji, rezultati su mnogo bolji."
Ovaj Sanmejin optimističan stav zaista mi daje osećaj da „trud se uvek isplati" 💪.
04. Razlika između apstraktne klase i interfejsa
Ukratko, sumirajmo razliku između apstraktne klase i interfejsa.
U Javi se klasa definisana ključnom reči abstract naziva apstraktnom klasom. Java je objektno orijentisan jezik, pa se stoga svi objekti opisuju klasama; ali obrnuto ne važi — nisu sve klase namenjene opisivanju objekata, a apstraktna klasa je jedna od njih.
Sledeći primer prikazuje jednostavnu apstraktnu klasu:
// Lično smatram da trener mora biti kako ofanzivan, tako i defanzivan
abstract class Coach {
public abstract void defend();
public abstract void attack();
}Znamo da se klasa sa apstraktnim metodama naziva apstraktnom klasom, što znači da apstraktna klasa može sadržati i metode koje nisu apstraktne. Takva klasa ne može se smatrati čistim interfejsom, iako može pružiti funkcionalnost interfejsa — apstraktna klasa je, recimo, neka zlatna sredina između obične klase i interfejsa.
Interfejs je apstraktan tip u Javi, skup apstraktnih metoda; definiše se ključnom reči interface. Razlike između interfejsa i apstraktne klase su:
- Apstraktna klasa može imati metode sa telom, dok interfejs ne može (pre Jave 8).
- Članice interfejsa implicitno su
static final, dok to ne važi za apstraktnu klasu.
- Članice interfejsa implicitno su
- Jedna klasa može implementirati više interfejsa, ali može naslediti samo jednu apstraktnu klasu.
Sledeći primer prikazuje jednostavan interfejs:
// implicitno abstract
interface Coach {
// implicitno public
void defend();
void attack();
}- Interfejs je implicitno apstraktan, pa nema potrebe za korišćenjem ključne reči
abstractpri deklaraciji; - Svaka metoda interfejsa implicitno je apstraktna, pa takođe nije potrebno koristiti
abstract; - Metode interfejsa implicitno su
public.
„Aha, razumem, brate. Neka te pitam: koja je razlika između apstraktne klase i interfejsa?"
„Oho, Sanmej, to pitanje je pravo u centar mete." Nisam mogao a da joj ne podignem palac.
1) Na nivou sintakse
- Apstraktna klasa može sadržati implementaciju konkretnih metoda; u interfejsu su metode podrazumevano
public abstract, mada od Jave 8 interfejs može sadržati i podrazumevane metode i statičke metode sa implementacijom. - Članice apstraktne klase mogu biti različitih tipova, dok članice interfejsa mogu biti samo tipa
public static final; - Interfejs ne može sadržati statičke blokove koda, dok apstraktna klasa može;
- Jedna klasa može naslediti samo jednu apstraktnu klasu, dok jedna klasa može implementirati više interfejsa.
2) Na nivou dizajna
Apstraktna klasa predstavlja apstrakciju jedne vrste entiteta, odnosno apstrakciju klase; između podklase koja nasleđuje apstraktnu klasu i same apstraktne klase postoji odnos is-a. Nasuprot tome, interfejs predstavlja apstrakciju ponašanja. Apstraktna klasa apstrahuje klasu u celini (uključujući osobine i ponašanje), dok interfejs apstrahuje samo deo klase — njeno ponašanje.
Jednostavan primer: avion i ptica su različite vrste entiteta, ali imaju zajedničko svojstvo — lete. Tada u dizajnu avion možemo oblikovati kao klasu Airplane, a pticu kao klasu Bird, ali svojstvo „letenje” ne možemo oblikovati kao klasu, jer je to samo karakteristika ponašanja, a ne apstrakcija jedne vrste entiteta.
Tada možemo „letenje” oblikovati kao interfejs Fly, koji sadrži metodu fly(), a zatim neka Airplane i Bird prema svojim potrebama implementiraju interfejs Fly. Različite vrste aviona — na primer lovci, putnički avioni — tada direktno nasleđuju Airplane; slično je i sa pticama, različite vrste ptica direktno nasleđuju klasu Bird. Otuda se vidi da je nasleđivanje odnos „da li jeste”, dok je implementacija interfejsa odnos „da li ima”. Ako klasa nasleđuje neku apstraktnu klasu, podklasa nužno pripada toj vrsti apstraktne klase, dok implementacija interfejsa izražava posedovanje neke sposobnosti — na primer, da li ptica može da leti (odnosno da li ima osobinu letenja); ako može, implementira taj interfejs, a ako ne može, ne implementira ga.
Interfejs je apstrakcija nekog ponašanja klase i između interfejsa i klase ne postoji jaka veza. Na primer, sve klase mogu implementirati Serializable interfejs i time steći sposobnost serijalizacije, ali se ne može reći da su sve klase u odnosu is-a sa Serializable.
Apstraktna klasa je roditeljska klasa mnogih podklasa i predstavlja dizajn po obrascu šablona. Interfejs je, s druge strane, specifikacija ponašanja i predstavlja dizajn po obrascu zračenja. Šta je dizajn po obrascu šablona? Najjednostavniji primer — svi smo koristili šablone u PowerPoint-u: ako je šablonom A kreiran ppt B i ppt C, ono što je njima zajedničko jeste šablon A; ako se njihov zajednički deo menja, dovoljno je izmeniti samo šablon A, bez ponovne izmene ppt-a B i ppt-a C. Kod dizajna po obrascu zračenja — na primer, ako su u nekom liftu instalirani alarmi određenog tipa, kada se alarm ažurira, svi se moraju ažurirati. Drugim rečima, kod apstraktne klase, ako je potrebno dodati novu metodu, može se direktno dodati konkretna implementacija u apstraktnu klasu, a podklase ne moraju da se menjaju; kod interfejsa to nije moguće — ako se interfejs izmeni, sve klase koje ga implementiraju moraju se prilagoditi.
