Započnite da učite polimorfizam u Javi i kako da vršite pozivanje metoda u polimorfnim pozivima.
Autor: Rafael del Nero
Polimorfizam — ili sposobnost objekta da izvršava specijalizovane akcije na osnovu tipa — je ono što Java kôd čini fleksibilnim. Mnogi obrasci dizajna koje je kreirala Gang Of Four oslanjaju se na neki oblik polimorfizma, uključujući i obrazac Command. U ovom članku ćete naučiti osnove Java polimorfizma i kako da ga koristite u svojim programima.
Stvari koje treba znati o polimorfizmu u Javi
- Polimorfizam i Java nasleđivanje
- Zašto je polimorfizam važan
- Polimorfizam u nadjačavanju metoda
- Polimorfizam sa osnovnim Java klasama
- Polimorfni pozivi metoda i konvertovanje
- Rezervisane ključne reči i polimorfizam
- Uobičajene greške sa polimorfizmom
- Šta treba zapamtiti o polimorfizmu
Polimorfizam i Java nasleđivanje
Fokusiraćemo se na odnos između polimorfizma i Java nasleđivanja. Glavna stvar koju treba imati na umu je da polimorfizam zahteva nasleđivanje ili implementaciju interfejsa. Ovo možete videti u sledećem primeru, sa klasama Duke i Juggy:
public abstract class JavaMascot {
public abstract void executeAction();
}
public class Duke extends JavaMascot {
@Override
public void executeAction() {
System.out.println("Punch!");
}
}
public class Juggy extends JavaMascot {
@Override
public void executeAction() {
System.out.println("Fly!");
}
}
public class JavaMascotTest {
public static void main(String... args) {
JavaMascot dukeMascot = new Duke();
JavaMascot juggyMascot = new Juggy();
dukeMascot.executeAction();
juggyMascot.executeAction();
}
}
Izlaz iz ovog koda će biti:
Punch!
Fly!
Zbog njihove specifične implementacije, izvršiće se i akcija Duke i akcija Juggy .
Zašto je polimorfizam važan
Svrha korišćenja polimorfizma je da se klijentska klasa odvoji od implementacionog koda. Umesto da bude čvrsto kodirana, klasa klijenta prima implementaciju da izvrši potrebnu akciju. Na ovaj način, klasa klijenta zna dovoljno da izvrši svoje radnje, što je primer labavog povezivanja.
Da biste bolje razumeli prednosti polimorfizma, pogledajte SweetCreator
:
public abstract class SweetProducer {
public abstract void produceSweet();
}
public class CakeProducer extends SweetProducer {
@Override
public void produceSweet() {
System.out.println("Cake produced");
}
}
public class ChocolateProducer extends SweetProducer {
@Override
public void produceSweet() {
System.out.println("Chocolate produced");
}
}
public class CookieProducer extends SweetProducer {
@Override
public void produceSweet() {
System.out.println("Cookie produced");
}
}
public class SweetCreator {
private List<SweetProducer> sweetProducer;
public SweetCreator(List<SweetProducer> sweetProducer) {
this.sweetProducer = sweetProducer;
}
public void createSweets() {
sweetProducer.forEach(sweet -> sweet.produceSweet());
}
}
public class SweetCreatorTest {
public static void main(String... args) {
SweetCreator sweetCreator = new SweetCreator(Arrays.asList(new CakeProducer(),
new ChocolateProducer(), new CookieProducer()));
sweetCreator.createSweets();
}
}
U ovom primeru možete videti da klasa SweetCreator
poznaje samo klasu SweetProducer
. Ne zna implementaciju za svaki Sweet
. To razdvajanje nam daje fleksibilnost da ažuriramo i ponovo koristimo naše klase, i čini kôd mnogo lakšim za održavanje. Kada dizajnirate svoj kôd, uvek tražite načine da ga učinite što fleksibilnijim i lakšim za održvanje. Polimorfizam je veoma moćna tehnika za pisanje Java koda za višekratnu upotrebu.
Savet: anotacija @Override
obavezuje programera da koristi potpis metoda koji mora da se nadjača. Ako se metod ne nadjača, doći će do greške pri prevođenju.
Da li je preopterećivanje metoda polimorfizam?
Mnogi programeri su zbunjeni u vezi sa odnosom polimorfizma prema nadjačavanju (engl. override) metoda i preklapanje (engl. overload) metoda. Međutim, samo je nadjačavanje metode pravi polimorfizam. Preklapanje ima isti naziv metode, ali su parametri drugačiji. Polimorfizam je širok pojam, tako da će uvek biti diskusija o ovoj temi,
Polimorfizam u nadjačavanju metoda
Moguće je promeniti vraćeni tip nadjačanog metoda ako je tip kovarijantan. Kovarijantni tip je u suštini potklasa vraćenog tipa. Evo primer:
public abstract class JavaMascot {
abstract JavaMascot getMascot();
}
public class Duke extends JavaMascot {
@Override
Duke getMascot() {
return new Duke();
}
}
Pošto Duke
jeste JavaMascot
, možemo da promenimo vraćeni tip kada nadjačavamo.
Polimorfizam sa osnovnim Java klasama
Polimorfizam koristimo sve vreme u osnovnim Java klasama. Jedan veoma jednostavan primer je kada instanciramo klasu ArrayList
deklarišemo interfejs List kao tip:
List<String> list = new ArrayList<>();
Da krenemo dalje, razmotrite ovaj primer koda gde se koristi API za Java kolekcije bez polimorfizma:
public class ListActionWithoutPolymorphism {
// Example without polymorphism
void executeVectorActions(Vector<Object> vector) {/* Code repetition here*/}
void executeArrayListActions(ArrayList<Object> arrayList) {/*Code repetition here*/}
void executeLinkedListActions(LinkedList<Object> linkedList) {/* Code repetition here*/}
void executeCopyOnWriteArrayListActions(CopyOnWriteArrayList<Object> copyOnWriteArrayList)
{/* Code repetition here*/}
}
public class ListActionInvokerWithoutPolymorphism {
listAction.executeVectorActions(new Vector<>());
listAction.executeArrayListActions(new ArrayList<>());
listAction.executeLinkedListActions(new LinkedList<>());
listAction.executeCopyOnWriteArrayListActions(new CopyOnWriteArrayList<>());
}
To je ružan kôd, zar ne? Zamislite kako biste ga održavali! Sada pogledajte isti primer sa polimorfizmom:
public static void main(String … polymorphism) {
ListAction listAction = new ListAction();
listAction.executeListActions();
}
public class ListAction {
void executeListActions(List<Object> list) {
// Execute actions with different lists
}
}
public class ListActionInvoker {
public static void main(String... masterPolymorphism) {
ListAction listAction = new ListAction();
listAction.executeListActions(new Vector<>());
listAction.executeListActions(new ArrayList<>());
listAction.executeListActions(new LinkedList<>());
listAction.executeListActions(new CopyOnWriteArrayList<>());
}
}
Prednosti polimorfizma su fleksibilnost i proširivost. Umesto da kreiramo nekoliko različitih metoda, možemo deklarisati samo jedan metod koji prima generički tip List
.
Polimorfni pozivi metoda i konvertovanje
Moguće je u polimorfnom pozivu pozivati konkretne metode, ali to dolazi po cenu fleksibilnosti. Evo primera:
public abstract class MetalGearCharacter {
abstract void useWeapon(String weapon);
}
public class BigBoss extends MetalGearCharacter {
@Override
void useWeapon(String weapon) {
System.out.println("Big Boss is using a " + weapon);
}
void giveOrderToTheArmy(String orderMessage) {
System.out.println(orderMessage);
}
}
public class SolidSnake extends MetalGearCharacter {
void useWeapon(String weapon) {
System.out.println("Solid Snake is using a " + weapon);
}
}
public class UseSpecificMethod {
public static void executeActionWith(MetalGearCharacter metalGearCharacter) {
metalGearCharacter.useWeapon("SOCOM");
// The below line wouldn't work
// metalGearCharacter.giveOrderToTheArmy("Attack!");
if (metalGearCharacter instanceof BigBoss) {
((BigBoss) metalGearCharacter).giveOrderToTheArmy("Attack!");
}
}
public static void main(String... specificPolymorphismInvocation) {
executeActionWith(new SolidSnake());
executeActionWith(new BigBoss());
}
}
Tehnika koju ovde koristimo je konverzija (engl. casting), ili namerna promena tipa objekta u toku izvršavanja.
Imajte na umu da je moguće pozvati određeni metod samo kada se generički tip konvertuje u konkretan tip. Dobra analogija bi bila eksplicitno reći kompajleru: „Hej, znam šta radim ovde, pa ću konvertovati objekat u konkretan tip i koristiti konkretan metod.“
Pozivajući se na gornji primer, postoji važan razlog zašto kompajler odbija da prihvati pozivanje konkretnog metoda: klasa koja se prosleđuje može biti SolidSnake
. U ovom slučaju, ne postoji način da kompajler obezbedi da svaka podklasa MetalGearCharacter
ima deklarisan metod giveOrderToTheArmy
.
Nabavite kod: Preuzmite izvorni kôd za ovaj zadatak i pokrenite sopstvene testove.
Rezervisane ključne reči i polimorfizam
Obratite pažnju na rezervisanu reč instanceof
. Pre nego što smo pozvali konkretan metod, pitali smo da li je MetalGearCharacter
„instanceof“ od BigBoss
. Da to nije BigBoss
instanca, primili bismo sledeću poruku o izuzetku:
Exception in thread "main" java.lang.ClassCastException:
com.javaworld.javachallengers.polymorphism.specificinvocation.SolidSnake cannot be cast to com.javaworld.javachallengers.polymorphism.specificinvocation.BigBoss
A ako želimo da referenciramo atribut ili metod iz Java natklase? U tom slučaju, mogli bismo koristiti rezervisanu reč super
. Na primer:
public class JavaMascot {
void executeAction() {
System.out.println("The Java Mascot is about to execute an action!");
}
}
public class Duke extends JavaMascot {
@Override
void executeAction() {
super.executeAction();
System.out.println("Duke is going to punch!");
}
public static void main(String... superReservedWord) {
new Duke().executeAction();
}
}
Korišćenje rezervisane reči super
u Duke-ovom metodu executeAction
pozvaće metod natklase. Nakon toga izvršavamo konkretnu radnju u Duke
. Zato ćemo u sledećem izlazu videti obe poruke:
The Java Mascot is about to execute an action!
Duke is going to punch!
Uobičajene greške sa polimorfizmom
- Uobičajena je greška misliti da je moguće pozvati konkretan metod bez upotrebe konverzije.
- Još jedna greška je dilema koji metod će biti pozvan prilikom polimorfnog instanciranja klase. Zapamtite da će se pozvati metod iz kreirane instance.
- Takođe zapamtite da nadjačavanje metoda nije preklapanje metoda.
- Nemoguće je nadjačati metod ako su parametri različiti. Moguće je promeniti vraćeni tip zamenjenog metoda ako je vraćeni tip podklasa metoda natklase.
Šta treba zapamtiti o polimorfizmu
- Kreirana instanca će odrediti koji metod će biti pozvan kada se koristi polimorfizam.
- Notacija @Override obavezuje programera da koristi nadjačani metod; inače dolazi do greške kompajlera.
- Polimorfizam se može koristiti sa normalnim klasama, apstraktnim klasama i interfejsima.
- Većina obrazaca dizajna zavisi od nekog oblika polimorfizma.
- Jedini način da u polimorfnoj podklasi koristite konkretan metod je korišćenje konverzije.
- Moguće je dizajnirati moćnu strukturu u vašem kodu koristeći polimorfizam.
Prihvatite izazov Java polimorfizma!
Hajde da isprobamo šta ste naučili o polimorfizmu i nasleđivanju. U ovom izazovu, dobijate pregršt metoda iz Simpsona Meta Groeninga, a vaš izazov je da zaključite kakav će biti rezultat za svaku klasu. Za početak pažljivo analizirajte sledeći kôd:
public class PolymorphismChallenge {
static abstract class Simpson {
void talk() {
System.out.println("Simpson!");
}
protected void prank(String prank) {
System.out.println(prank);
}
}
static class Bart extends Simpson {
String prank;
Bart(String prank) { this.prank = prank; }
protected void talk() {
System.out.println("Eat my shorts!");
}
protected void prank() {
super.prank(prank);
System.out.println("Knock Homer down");
}
}
static class Lisa extends Simpson {
void talk(String toMe) {
System.out.println("I love Sax!");
}
}
public static void main(String... doYourBest) {
new Lisa().talk("Sax :)");
Simpson simpson = new Bart("D'oh");
simpson.talk();
Lisa lisa = new Lisa();
lisa.talk();
((Bart) simpson).prank();
}
}
Šta mislite? Šta će biti konačni rezultat? Ne koristite IDE da biste ovo rešili! Poenta je da poboljšate svoje veštine analize koda, pa pokušajte sami da odredite izlaz.
Odaberite svoj odgovor i moći ćete na kraju da pronađete tačan odgovor.
A)
I love Sax!
D'oh
Simpson!
D'oh
B)
Sax :)
Eat my shorts!
I love Sax!
D'oh
Knock Homer down
C)
Sax :)
D'oh
Simpson!
Knock Homer down
D)
I love Sax!
Eat my shorts!
Simpson!
D'oh
Knock Homer down
Rešavanje zadatka
Za sledeće pozivanje metoda:
new Lisa().talk("Sax :)");
izlaz će biti „I love Sax!“ To je zato što metodu prosleđujemo string, a Lisa ima taj metod.
Za sledeći poziv:
Simpson simpson = new Bart("D'oh");
simpson.talk();
Rezultat će biti „Eat my shorts!“ To je zato što tip Simpson
instanciramo sa Bart
.
Sada pokušajte ovo, što je malo teže:
Lisa lisa = new Lisa();
lisa.talk();
Ovde koristimo preklapanje metoda sa nasleđivanjem. Ne prosleđujemo ništa metodu talk, zbog čega se poziva Simpson
metod talk
. U ovom slučaju, izlaz će biti:
"Simpson!"
Evo još jedan:
((Bart) simpson).prank();
U ovom slučaju, string prank
je prosleđen kada smo klasu Bart
instancirali sa new
Bart(„D’oh“);. U ovom slučaju, najpre će se pozvati metod super.prank
, a zatim konkretan metod prank
iz klase Bart
. Izlaz će biti:
"D'oh"
"Knock Homer down"
Odgovor na ovaj Java zadatak je, dakle, D. Rezultat bi bio:
I love Sax!
Eat my shorts!
Simpson!
D'oh
Knock Homer down
Video izazov! Otklanjanje grešaka u Java polimorfizmu i nasleđivanju
Otklanjanje grešaka je jedan od najlakših načina da se u potpunosti savladaju koncepti programiranja, a da istovremeno poboljšate svoj kôd. U ovom videu, koji je na engleskom, možete pratiti kako Rafael otklanja greške i objašnjava izazov Java polimorfizma:
Izvor: InfoWorld