Duboko razumevanje tri glavne osobine Jave: enkapsulacija, nasleđivanje i polimorfizam
Kada govorimo o Java objektnoj orijentaciji, moramo pomenuti njene tri glavne osobine: enkapsulacija, nasleđivanje i polimorfizam. Tri osobine su uskozo povezane, ali se međusobno razlikuju; razumno korišćenje nasleđivanja može znatno da smanji ponavljajući kod i poveća iskoristivost koda.
1) Enkapsulacija
"Sanmej, jesi li spremna? Da pričamo o Java enkapsulaciji, to je jedna od tri glavne osobine Jave; kad je raščistiš, pomooći će ti u kasnijem programiranju", rekao sam Sanmej.
"Dobro, brate, spremna sam", odgovorila je Sanmej. Dok me je slušala, brzo je otvorila XMind — izgleda da je ovaj efikasan način učenja, paralelno učenje i sažimanje misaonih mapa, čvrsto zapamtila.
Enkapsulacija se, shvaćena doslovno, odnosi na pakovanje; stručnije rečeno, to je skrivanje informacija — znači korišćenje apstrakcije da se podaci i operacije nad tim podacima zamotaju zajedno, tako da čine nedeljiv, samostalan entitet.
Podaci su zaštićeni unutar klase, pri čemu se što više kriju unutrašnji detalji implementacije, a ostavljaju se samo određeni spoljašnji interfejsi radi povezivanja sa spoljnim svetom.
Drugi objekti mogu komunicirati s ovako enkapsuliranim objektom isključivo putem ovlašćenih operacija. Drugim rečima, korisnik ne mora da zna detalje unutar objekta (a zapravo i ne može da ih sazna), ali može pristupiti tom objektu preko interfejsa koje on nudi.
Korišćenje enkapsulacije donosi četiri glavne koristi:
- Dobra enkapsulacija može da smanji spregu (coupling).
- Unutrašnja struktura klase se može slobodno menjati.
- Omogućava precizniju kontrolu nad članovima.
- Sakriva informacije i detalje implementacije.
Najpre da pogledamo dve klase.
Husband.java
public class Husband {
/*
* Enkapsulacija svojstava
* Ime, pol, starost i supruga osobe njena su privatna svojstva
*/
private String name ;
private String sex ;
private int age ;
private Wife wife;
/*
* setter() i getter() su interfejsi koje ovaj objekat izlaže spoljašnjem svetu
*/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void setWife(Wife wife) {
this.wife = wife;
}
}Wife.java
public class Wife {
private String name;
private int age;
private String sex;
private Husband husband;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public void setAge(int age) {
this.age = age;
}
public void setHusband(Husband husband) {
this.husband = husband;
}
public Husband getHusband() {
return husband;
}
}Vidi se da svojstvo wife unutar klase Husband nema getter(), a isto tako svojstvo age klase Wife nema metodu getter(). Razlog za to, Sanmej, valjda već i sama naslućuješ.
Nijedna žena ne želi da drugi znaju njene godine.
Dakle, enkapsulacija privatizuje svojstva jednog objekta i istovremeno pruža metode kojima spoljašnji svet može pristupiti tim svojstvima; ako ne želimo da spoljašnji svet pristupa nečemu, prosto ne moramo da pružimo metodu za taj pristup.
Ali ako klasa spoljašnjem svetu ne pruži nikakvu metodu za pristup, onda takva klasa i nema nekog smisla.
Ako, recimo, posmatramo kuću kao objekat — u njoj ima lepih ukrasa, kao što su sofa, televizor, klima, čajni sto i slično, sve to su privatna svojstva te kuće. Ali ako nemamo one zidove koji zaklanjaju, ne bi li drugi sve videli na prvi pogled? Ne bi ostalo ni trunka privatnosti!
Zahvaljujući tim zaklanjajućim zidovima, možemo imati svoju privatnost, a možemo i slobodno menjati unutrašnji raspored bez uticaja na ljude napolju.
Međutim, bez vrata i prozora, šta znači jedan potpuno zatvoren crni sanduk? Zato i kroz vrata i prozore drugi mogu videti unutrašnji pejzaž. Prema tome, vrata i prozori su interfejsi koje objekat kuće ostavlja spoljašnjem svetu za pristup.
Time još uvek ne možemo stvarno da osetimo korist od enkapsulacije. Sada da iz ugla programa analiziramo šta nam ona donosi. Ako ne koristimo enkapsulaciju, taj objekat nema setter() i getter(), pa bi klasa Husband izgledala ovako:
public class Husband {
public String name ;
public String sex ;
public int age ;
public Wife wife;
}Koristili bismo je ovako:
Husband husband = new Husband();
husband.age = 30;
husband.name = "Marko";
husband.sex = "Muški"; // deluje pomalo suvišnoAli šta ako jednog dana treba da izmenimo Husband — na primer, age pretvorimo u tip String? Ako si tu klasu koristio/la samo na jednom mestu, još i prolazi; ali ako imaš desetine ili čak stotine takvih mesta, zar te ne bi uhvatila panika od prepravljanja? Uz enkapsulaciju uopšte ne moramo ništa da menjamo — dovoljno je da malo izmenimo metodu setAge() klase Husband.
public class Husband {
/*
* Enkapsulacija svojstava
* Ime, pol, starost i supruga osobe njena su privatna svojstva
*/
private String name ;
private String sex ;
private String age ; /* izmenjeno u tip String */
private Wife wife;
public String getAge() {
return age;
}
public void setAge(int age) {
// jednostavno konvertuj
this.age = String.valueOf(age);
}
/** izostavljeni setter i getter za ostala svojstva **/
}Sva ostala mesta i dalje pozivaju ( husband.setAge(22) ) i ostaju nepromenjena.
Do ovde zaista možemo videti da enkapsulacija zaista olakšava izmenu unutrašnje implementacije klase, bez potrebe za izmenom koda koji je koristi.
Pogledajmo još jednu prednost: enkapsulacija omogućava precizniju kontrolu nad članskim promenljivama.
Opet onaj Husband — uglavnom ne grešimo kad ga koristimo, ali ponekad si u zabuni pa napišeš ovako nešto:
Husband husband = new Husband();
husband.age = 300;Možda si iz nepažnje to napisao/la; ako primetiš — još polovi, ali ako ne primetiš, biće velike nevolje — ko je ikada video stogodišnjeg čudovišta od 300 godina!
Ali uz enkapsulaciju možemo izbeći taj problem; učinimo određenu kontrolu nad pristupnim ulazom za age (setter), na primer:
public class Husband {
/*
* Enkapsulacija svojstava
* Ime, pol, starost i supruga osobe njena su privatna svojstva
*/
private String name ;
private String sex ;
private int age ; /* izmenjeno u tip String */
private Wife wife;
public int getAge() {
return age;
}
public void setAge(int age) {
if(age > 120){
System.out.println("ERROR:error age input...."); // prikazuje poruku o grešci
}else{
this.age = age;
}
}
/** izostavljeni setter i getter za ostala svojstva **/
}Gore su primeri kontrole nad setter metodom, ali pomoću enkapsulacije možemo dobro kontrolisati i ono što objekat "izlazi" napolje. Na primer, pol se u bazi podataka obično čuva u obliku 1 ili 0, ali na prednjoj strani ne možemo prikazivati 1 ili 0; ovde je dovoljno da u metodi getter() izvršimo određenu konverziju.
public String getSexName() {
if("0".equals(sex)){
sexName = "Ženski";
}
else if("1".equals(sex)){
sexName = "Muški";
}
return sexName;
}Pri upotrebi je dovoljno koristiti sexName i ostvariti ispravan prikaz pola. Istim postupkom možemo i različito reagovati na različita stanja.
public String getCzHTML(){
if("1".equals(zt)){
czHTML = "<a href='javascript:void(0)' onclick='qy("+id+")'>Omogući</a>";
}
else{
czHTML = "<a href='javascript:void(0)' onclick='jy("+id+")'>Onemogući</a>";
}
return czHTML;
}"Dobro, toliko za sada o enkapsulaciji", rekao sam Sanmej nakon što sam otpio gutljaj Pu-er čaja.
"Dobro, brate, jasno mi je."
Referenca: https://www.cnblogs.com/chenssy/p/3351835.html, priredio: Chenmo Wang Er
2) Nasleđivanje
01. Šta je nasleđivanje
Nasleđivanje (engl. inheritance) pojam je iz softverske tehnologije objektno-orijentisanog programiranja. Ono veoma olakšava iskorišćavanje prethodnog koda.
Java je tipičan objektno-orijentisani jezik; u Javi nasleđivanje znači da podklasa nasleđuje polja i metode nadklase, čime objekat (instanca) podklase dobija polja i metode nadklase, odnosno podklasa od nadklase nasleđuje metode tako da ima iste metode kao nadklasa.
Navedimo primer: životinje ima mnogo vrsta i predstavljaju širi pojam. Među vrstama životinja dobro su nam poznate mačka (Cat) i pas (Dog) — sve imaju opšte osobine životinja (na primer mogu da jedu, mogu da se čuju), ali se u detaljima razlikuju (različite životinje jedu različito i drugačije zvuče).
Pri implementaciji klasa poput Cat i Dog u Javi potrebno je naslediti klasu Animal. Nakon nasleđivanja, konkretne klase životinja poput Cat i Dog postaju podklase, a klasa Animal nadklasa.

02. Zašto nam treba nasleđivanje
Sanmej, možda se pitaš zašto nam treba nasleđivanje?
Ako imamo samo dve-tri klase, sasvim ograničenih svojstava i metoda, zaista nema potrebe za nasleđivanjem. Ali tako nije — u jednom sistemu obično postoji mnogo klasa sa mnogo sličnosti; na primer, mačka i pas su obe životinje, ili student i nastavnik su obe osobe. Svaka klasa može imati mnogo istih svojstava i metoda, pa ako se svaka klasa piše iznova, kod ne samo da deluje neuredno, već je i obim posla veliki.
Ovde dolazi do izražaja prednost nasleđivanja: mogu se direktno koristiti svojstva i metode nadklase, a mogu se dodati i nova svojstva i metode za proširenje; metod nadklase, ako zatreba, može se i redefinisati. Tako nasleđivanje ne samo da znatno smanjuje količinu koda, već i strukturu koda čini jasnijom.

Zato sa gledišta koda kompletnu klasu Animal projektujemo ovako:
class Animal
{
public int id;
public String name;
public int age;
public int weight;
public Animal(int id, String name, int age, int weight) {
this.id = id;
this.name = name;
this.age = age;
this.weight = weight;
}
// ovde su izostavljeni get i set metode
public void sayHello()
{
System.out.println("hello");
}
public void eat()
{
System.out.println("I'm eating");
}
public void sing()
{
System.out.println("sing");
}
}A klase Dog, Cat, Chicken mogu se projektovati ovako:
class Dog extends Animal// nasleđuje Animal
{
public Dog(int id, String name, int age, int weight) {
super(id, name, age, weight);// poziva konstruktor nadklase
}
}
class Cat extends Animal{
public Cat(int id, String name, int age, int weight) {
super(id, name, age, weight);// poziva konstruktor nadklase
}
}
class Chicken extends Animal{
public Chicken(int id, String name, int age, int weight) {
super(id, name, age, weight);// poziva konstruktor nadklase
}
// koka nosi jaja
public void layEggs()
{
System.out.println("Ja sam stara koka, snela sam jaje! Kotkodak! Kotkodak!");
}
}Nakon što svaka od klasa nasledi Animal, može direktno koristiti svojstva i metode klase Animal bez ponovnog pisanja; ako neka klasa ima svoj metod, lako ga može dodati.
03. Podela nasleđivanja
Nasleđivanje se deli na jednostruko i višestruko; Java podržava samo jednostruko nasleđivanje klasa, ali se implementacijom interfejsa može postići cilj višestrukog nasleđivanja.
Jednostruko nasleđivanje

Jednostruko nasleđivanje — jedna podklasa ima samo jednu nadklasu, kao što smo gore prikazali na klasama Animal i njenim podklasama. Jednostruko nasleđivanje daje pregledniju hijerarhiju klasa, ali mu nedostatak je što bogatstvo strukture ponekad ne zadovoljava potrebe.
Višestruko nasleđivanje

Višestruko nasleđivanje — jedna podklasa ima više neposrednih nadklasa. Prednost je u tome što podklasa poseduje osobine svih nadklasa, pa je bogatstvo podklase veliko, ali nedostatak je što lako dovodi do zabune. Sledeća slika prikazuje jedan zbunjujući primer.

Iako Java ne podržava višestruko nasleđivanje, ima tri načina da ostvari njegov efekat — to su unutrašnja klasa, višeslojno nasleđivanje i implementacija interfejsa.
Unutrašnja klasa može naslediti klasu koja nije povezana sa spoljnom klasom, čime se čuva njena samostalnost; upravo na toj osnovi može se postići efekat višestrukog nasleđivanja.
Višeslojno nasleđivanje: podklasa nasleđuje nadklasu, a ako nadklasa nasleđuje drugu klasu, to se naziva višeslojno nasleđivanje. Tako podklasa dobija svojstva i metode svih nasleđenih klasa.

Implementacija interfejsa nesumnjivo je najbolji način da se zadovolje potrebe višestrukog nasleđivanja; jedna klasa može implementirati više interfejsa i time zadovoljiti svoje potrebe u bogatstvu i složenim okruženjima.
U poređenju sa klasom, klasa je entitet — ima svojstva i metode, dok interfejs više liči na skup metoda. Uzmimo za primer Tang Sana iz "Douluo Dalu": njegov odnos nasleđivanja mogao bi izgledati ovako:

04. Kako implementirati nasleđivanje
Ključna reč extends
U Javi je nasleđivanje klasa jednostruko, odnosno jedna podklasa može imati samo jednu nadklasu, pa ključnom rečju extends može se naslediti samo jedna klasa. Njena sintaksa je:
class ImePodklase extends ImeNadklase{}Na primer, klasa Dog nasleđuje klasu Animal ovako:
class Animal{} // definiše klasu Animal
class Dog extends Animal{} // klasa Dog nasleđuje klasu AnimalNakon što podklasa nasledi nadklasu, dobija neprivatna svojstva i metode nadklase. Ako ti nije jasno, pogledaj ovaj slučaj: u IDEA-i napravi projekat, kreiraj klasu test za probe, pa kreiraj klase Animal i Dog; neka Animal kao nadklasa sadrži metod sayHello(), a klasa Dog nakon što nasledi Animal može pozivati sayHello(). Konkretan kod je:
class Animal {
public void sayHello()// metod nadklase
{
System.out.println("hello,everybody");
}
}
class Dog extends Animal// nasleđuje Animal
{ }
public class test {
public static void main(String[] args) {
Dog dog=new Dog();
dog.sayHello();
}
}Kada se klikne "Run", podklasa Dog može direktno koristiti metod nadklase Animal.

Ključna reč implements
Ključna reč implements može, takoreći, dati Javi svojstvo višestrukog nasleđivanja; koristi se kada klasa implementira interfejs, a jedna klasa može implementirati više interfejsa (interfejsi se razdvajaju zarezima).
Pogledajmo slučaj: kreiraj klasu test2 za probe, zatim kreiraj interfejse doA i doB; interfejs doA deklariše metod sayHello(), a interfejs doB deklariše metod eat(). Zatim kreiraj klasu Cat2 koja implementira interfejse doA i doB, pri čemu u klasi mora redefinisati metode sayHello() i eat(). Konkretan kod je:
interface doA{
void sayHello();
}
interface doB{
void eat();
// sledeće će izazvati grešku: metode u interfejsu ne mogu imati konkretnu definiciju, samo deklaraciju
//public void eat(){System.out.println("eating");}
}
class Cat2 implements doA,doB{
@Override// obavezno redefinisati metode iz interfejsa
public void sayHello() {
System.out.println("hello!");
}
@Override
public void eat() {
System.out.println("I'm eating");
}
}
public class test2 {
public static void main(String[] args) {
Cat2 cat=new Cat2();
cat.sayHello();
cat.eat();
}
}Kada klasa Cat implementira interfejse doA i doB, mora implementirati njihove deklarisane metode. Klikni na "Run" i rezultat je prikazan ispod — to je jednostavan primer implementacije interfejsa klasom:

05. Osobine nasleđivanja
Glavni sadržaj nasleđivanja jeste da podklasa nasleđuje nadklasu i redefiniše njene metode. Pri korišćenju svojstava ili metoda podklase prvo treba kreirati objekat, a objekat se kreira putem konstruktora; u konstruktoru možemo pozivati određena svojstva i metode podklase i nadklase, pa zato treba unapred savladati ključne reči this i super.
Nakon što se objekat kreira, poziva se metod nadklase koji je redefinisan, pri čemu obrati pažnju na razliku između redefinisanja i preopterećenja.
Ključne reči this i super
Detaljno će biti obrađene kasnije; ovde samo ukratko da se upoznamo s njima.
Ključne reči this i super veoma su važna znanja u nasleđivanju; one redom predstavljaju referencu na trenutni objekat i referencu na objekat nadklase; mnogo toga im je slično, ali i sa izvesnim razlikama.
this označava trenutni objekat, to je referenca koja ukazuje na samog sebe.
this.polje // poziva člansku promenljivu, razlikuje člansku od lokalne promenljive
this.() // poziva neku metodu ove klase
this() // označava poziv konstruktora ove klasesuper označava objekat nadklase, to je referenca koja ukazuje na nadklasu.
super.polje // označava člansku promenljivu objekta nadklase
super.metod() // označava metod definisan u objektu nadklase
super() // označava poziv konstruktora nadklaseKonstruktor
Konstruktor je specijalna vrsta metode, to je metod koji ima isto ime kao klasa. U nasleđivanju konstruktor je prilično posebna metoda (na primer ne može se naslediti), pa treba savladati pravila i zahteve konstruktora u okviru nasleđivanja.
Pri nasleđivanju, konstruktor zahteva pažnju na sledeće:
Konstruktor nadklase ne može se naslediti:
Pošto je sintaksa konstruktora da ima isto ime kao klasa, a nasleđivanje ne menja ime metoda, ako podklasa nasledi konstruktor nadklase, to bi očito dovelo do konflikta sa sintaksom konstruktora. Na primer, konstruktor klase Father zove se Father(); ako klasa Son nasledi konstruktor Father() klase Father, to bi došlo u sukob s definicijom da konstruktor ima isto ime kao klasa. Zato podklasa ne može naslediti konstruktor nadklase, ali će podklasa pozivati konstruktor nadklase.
Proces konstrukcije podklase mora pozvati konstruktor svoje nadklase:
Java virtuelna mašina pre konstrukcije objekta podklase prvo konstruiše objekat nadklase, a tek po njegovom završetku konstruiše svojstva svojstvena podklasi — to se naziva memorijsko preklapanje. A Java virtuelna mašina pri konstrukciji objekta nadklase izvršava konstruktor nadklase, pa konstruktor podklase mora pozvati super(), odnosno konstruktor nadklase. Tako bi, na primer, jednostavan slučaj nasleđivanja trebalo ovako da se napiše:
class A{
public String name;
public A() {// konstruktor bez argumenata
}
public A (String name){// konstruktor s argumentom
}
}
class B extends A{
public B() {// konstruktor bez argumenata
super();
}
public B(String name) {// konstruktor s argumentom
//super();
super(name);
}
}Ako konstruktor podklase ne poziva eksplicitno konstruktor nadklase, sistem podrazumevano poziva konstruktor nadklase bez argumenata.
Ponekad pri pisanju nasleđivanja podklasa ne koristi super(), a program ipak radi bez problema; zapravo, to je radi uštede koda — sistem pri izvršavanju automatski dodaje poziv konstruktoru nadklase bez argumenata. Ako ne veruješ, malo izmenimo gornje klase i izvršimo:

Redefinisanje metode (Override)
Redefinisanje metode znači da se u podklasi pojavi metod potpuno isti kao u nadklasi (uključujući tip povratne vrednosti, ime metode i listu parametara); on je zasnovan na nasleđivanju. Možeš ga shvatiti tako da spoljašnost metoda ostaje nepromenjena, ali se suština ponovo zapisuje.
Evo jednog jednostavnog i razumljivog primera redefinisanja metode:
class E1{
public void doA(int a){
System.out.println("Ovo je metod nadklase");
}
}
class E2 extends E1{
@Override
public void doA(int a) {
System.out.println("Redefinišem metod nadklase, ovo je metod podklase");
}
}Anotacija @Override izričito proglašava da je reč o redefinisanoj metodi; ona ti može pomoći da proveriš ispravnost sintakse redefinisanja. Naravno, može i bez nje, ali se preporučuje da se stavi.
Preopterećenje metode (Overload)
Ako dva metoda imaju isto ime, ali različite parametre, možemo reći da je jedan metod preopterećenje drugog.
Preopterećenje se obično može shvatiti tako da metodi koji obavljaju istu stvar imaju isto ime, ali različite liste parametara, a mogu se razlikovati i po drugim uslovima. Jednostavan primer preopterećenja: metod add() u klasi E3 jeste preopterećen metod.
class E3{
public int add(int a,int b){
return a+b;
}
public double add(double a,double b) {
return a+b;
}
public int add(int a,int b,int c) {
return a+b+c;
}
}06. Nasleđivanje i modifikatori
Uloga Java modifikatora jeste da modifikuju ili ograniče klasu ili njene članove; svaki modifikator ima svoju ulogu, a u nasleđivanju neki specijalni modifikatori mogu dovesti do toga da modifikovano svojstvo ili metod ne mogu biti nasleđeni, ili nasleđivanje zahteva izvesne dodatne uslove.
Java nudi mnogo modifikatora; oni se koriste za definisanje klasa, metoda ili promenljivih i obično se nalaze na samom početku iskaza. Glavno se dele u dve kategorije:
- Modifikatori prava pristupa, odnosno
public,private,protecteditd. - Modifikatori koji nisu modifikatori pristupa, odnosno
static,final,abstractitd.
Modifikatori pristupa
Kada Java podklasa redefiniše nasleđeni metod, ne sme mu umanjiti pravo pristupa, odnosno opseg važenja modifikatora pristupa koji podklasa nasledi ne sme biti uži od onog u nadklasi — dakle mora biti otvoreniji. Ako je u nadklasi metod protected, on u podklasi može biti samo protected ili public, a nikako default (podrazumevani opseg pristupa) ili private. Zato metod koji se u nasleđivanju redefiniše ne može se dekorisati modifikatorom private.
Ako ti i dalje nije jasno, pogledaj nekoliko malih primera i lako ćeš shvatiti: napiši klasu A1 s četiri metoda ostvarena pomoću četiri modifikatora, pa neka podklasa A2 nasledi A1; pri redefinisanju metoda klase A1 primetićeš da se privatni metod nadklase ne može redefinisati, a da modifikator pristupa kod redefinisanja neprivasnog metoda ne sme biti užeg opsega (veći ili jednak).

Pravilan primer bi trebalo da bude:
class A1 {
private void doA(){ }
void doB(){}//default
protected void doC(){}
public void doD(){}
}
class A2 extends A1{
@Override
public void doB() { }// opseg modifikatora pristupa redefinisanog metoda u podklasi može se proširiti
@Override
protected void doC() { }// opseg modifikatora pristupa redefinisanog metoda u podklasi može biti isti kao u nadklasi
@Override
public void doD() { }// ne može se dekorisati sa protected ili default
}Takođe treba obratiti pažnju na to da u nasleđivanju izuzeci koje podklasa baca moraju biti jednaki izuzetku nadklase ili podizuzetku izuzetka nadklase. Sledeći primer s četiri metoda pokazuje da opseg izuzetka metoda podklase ne sme biti veći od opsega izuzetka odgovarajućeg metoda nadklase.

Pravilan primer bi trebalo da bude:
class B1{
public void doA() throws Exception{}
public void doB() throws Exception{}
public void doC() throws IOException{}
public void doD() throws IOException{}
}
class B2 extends B1{
// opseg izuzetka može biti isti kao u nadklasi
@Override
public void doA() throws Exception { }
// opseg izuzetka može biti manji nego u nadklasi
@Override
public void doB() throws IOException { }
// opseg izuzetka ne može biti veći nego u nadklasi
@Override
public void doC() throws IOException { }// ne može se baciti Exception niti bilo šta veće od IOException
@Override
public void doD() throws IOException { }
}Modifikatori koji nisu modifikatori pristupa
Modifikatori pristupa kontrolišu pravo pristupa, dok svaki od ostalih modifikatora ima svoju posebnu ulogu; u nastavku su obrađeni static, final i abstract.
static, prevedeno, znači "statički"; može se koristiti uz promenljive, metode i klase — nazivamo ih statičkim promenljivama i statičkim metodima (koje se nazivaju i klasne promenljive, odnosno klasni metodi). Ako se u okviru klase static koristi uz promenljivu ili metod, njima se može pristupiti direktno preko klase, bez kreiranja objekta klase.
Pri projektovanju klase možemo koristiti statičke metode; mnoge klasa alatki, poput Math, Arrays i sličnih, sadrže brojne statičke metode.
Pogledaj sledeći primer kojim se potvrđuje gornje pravilo:

Izvorni kod je:
class C1{
public int a;
public C1(){}
// public static C1(){}// konstruktor ne sme biti deklarisan kao static
public static void doA() {}
public static void doB() {}
}
class C2 extends C1{
public static void doC()// u statičkoj metodi ne postoji trenutni objekat, pa zato ne mogu se koristiti this i super.
{
//System.out.println(super.a);
}
public static void doA(){}// statička metoda može biti redefinisana statičkom metodom
// public void doB(){}// statička metoda ne može biti redefinisana nestatičkom metodom
}final promenljive:
finaloznačava "krajnji, konačni", jednom kada se promenljivoj dodeli vrednost, ne može se ponovo dodeliti. Instanca promenljiva modifikovana safinalmora imati eksplicitno zadanu početnu vrednost (odnosno ne može se samo deklarisati). Modifikatorfinalse obično koristi zajedno sastaticradi kreiranja klasnih konstanti.
final metodi:
finalmetod nadklase može biti nasleđen podklasom, ali ne može biti redefinisan od strane podklase. Glavni cilj proglašavanja metodafinaljeste sprečavanje izmene njegovog sadržaja.
final klase:
finalklasa ne može biti nasleđena; nijedna klasa ne može naslediti bilo koju osobinufinalklase.
Dakle, bilo da je reč o promenljivoj, metodu ili klasi, nakon što se dekoriraju sa final, sve nose značenje "konačno, krajnje" — sadržaj ne može biti izmenjen.
abstract, engleski "apstraktan", uglavnom se koristi za dekorisanje klasa i metoda; nazivamo ih apstraktnim klasama i apstraktnim metodima.
Apstraktan metod: metodi mnogo različitih klasa slični su, ali im se konkretan sadržaj razlikuje, pa možemo izvući samo njegovu deklaraciju, bez konkretnog tela metode; apstraktan metod može izraziti pojam, ali ne i konkretnu implementaciju.
Apstraktna klasa: klasa koja ima apstraktne metode mora biti apstraktna klasa; apstraktna klasa može izraziti pojam, ali je klasa koja ne može konstruisati entitet.

Na primer, možemo projektovati apstraktnu klasu People i jedan apstraktni metod, a u podklasama ih konkretno ostvariti:
abstract class People{
public abstract void sayHello();// apstraktan metod
}
class Chinese extends People{
@Override
public void sayHello() {// implementira apstraktni metod
System.out.println("Zdravo");
}
}
class Japanese extends People{
@Override
public void sayHello() {// implementira apstraktni metod
System.out.println("Koničiwa");
}
}
class American extends People{
@Override
public void sayHello() {// implementira apstraktni metod
System.out.println("hello");
}
}07. Klasa Object i konverzija tipova
Kada govorimo o Java nasleđivanju, moramo pomenuti i korensku klasu svih klasa: klasu Object (java.lang.Object). Ako klasa ne deklariše eksplicitno svoju nadklasu (odnosno ne napiše extends xx), onda je podrazumevana nadklasa te klase upravo Object. Svaka klasa može koristiti metode klase Object, a kreirana klasa može se sa Object konvertovati i naviše i naniže, pa je klasa Object obavezno znanje za savladavanje i razumevanje nasleđivanja.
Java konverzija naviše i naniže se često koristi u Javi, a i ona je zasnovana na nasleđivanju, pa je i Java konverzija obavezno znanje za savladavanje i razumevanje nasleđivanja.
Pregled klase Object
Objectje korenska klasa hijerarhije klasa; sve klase implicitno nasleđuju klasuObject.- U Javi svi objekti imaju podrazumevane metode klase
Object. - Klasa
Objectima jedan konstruktor, i to konstruktor bez argumenata.
Object je nadklasa svih Java klasa, na samom vrhu celokupne strukture nasleđivanja klasa, a ujedno i najapstraktnija klasa.
Metode poput toString(), equals(), hashCode(), wait(), notify(), getClass() i druge sve su metodi klase Object. Kasnije ćeš se s njima često sretati, a među njima ćeš najviše puta sretati toString() i equals(), koje ćemo često morati redefinisati da bismo zadovoljili svoje potrebe.
Metod toString() vraća znakovnu reprezentaciju objekta; pošto objekte konstruišemo na različite načine, moramo ga redefinisati; ako ga ne redefinišemo, podrazumevano vraća format imeKlase@hashCode.
Ako redefinišemo metod toString(), njegovim direktnim pozivom možemo dobiti sadržaj koji smo sami odredeli pri konverziji u String, bez potrebe da svaki put ručno spajamo stringove, što uveliko pojednostavljuje ispis.
Metod equals() uglavnom poredi da li su dva objekta jednaka; jednakost objekata ne mora nužno da znači strogo podudaranje adresa dva objekta — nekada smatramo da su jednaki ako im se podudara sadržaj. Klasa String, na primer, redefinisala je metod equals() tako da poredi jednakost po sadržaju stringa.

Konverzija naviše
Konverzija naviše: instanciranjem objekta podklase (užeg opsega) dobija se objekat nadklase (šireg opsega) — to spada u automatsku konverziju. Jedna slika dobro prikazuje logiku konverzije naviše:

Nakon što referentna promenljiva nadklase ukazuje na objekat podklase, mogu se koristiti samo metode koje je nadklasa deklarisala; ali ako je metod redefinisan, izvršiće se metod podklase, a ako nije, izvršiće se metod nadklase.
Konverzija naniže
Konverzija naniže: instanciranjem objekta nadklase (šireg opsega) dobija se objekat podklase (užeg opsega); u kodu referenca nadklase mora se u zagradama () eksplicitno prevesti u tip podklase. Ali da bi konverzija uspela, referenca nadklase zapravo mora ukazivati na objekat podklase. I ovde jedna slika dobro prikazuje logiku konverzije naniže:

Kada referentna promenljiva podklase ukazuje na objekat na koji ukazuje referentna promenljiva nadklase (jedan Son() objekat), konverzija naniže je završena i mogu se pozivati neki metodi svojstveni podklasi, a koje nadklasa nema.
Evo primera konverzije naviše i naniže:
Object object=new Integer(666);// konverzija naviše
Integer i=(Integer)object;// konverzija naniže Object->Integer, object zapravo i dalje ukazuje na Integer
String str=(String)object;// pogrešna konverzija naniže; iako kompajler ne prijavljuje grešku, u toku rada će je prijaviti08. Redosled inicijalizacije podklase i nadklase
U Java nasleđivanju, redosled inicijalizacije nadklase i podklase je:
- Statičke članske promenljive i statički blokovi koda nadklase
- Statičke članske promenljive i statički blokovi koda podklase
- Obične članske promenljive i blokovi kode nadklase, te konstruktor nadklase
- Obične članske promenljive i blokovi kode podklase, te konstruktor podklase
Ukratko, važi: statičko pre nestatičkog, nadklasa pre podklase, nekonstruktor pre konstruktora. Unutar iste kategorije (na primer obične promenljive i obični blokovi) izvršavanje ide redom od napred ka nazad, pa treba obratiti pažnju na logiku.
Ni ovo nije teško razumeti: statičke promenljive, koje se nazivaju i klasne promenljive, mogu se posmatrati kao globalne promenljive; statičke članske promenljive i statički blokovi koda inicijalizuju se prilikom učitavanja klase, dok se nestatične promenljive i blokovi inicijalizuju prilikom kreiranja objekta. Zato je statičko brže od nestatičnog.
Pri kreiranju objekta podklase potrebno je prvo kreirati objekat nadklase, pa je nadklasa u prednosti pred podklasom.
A prilikom poziva konstruktora vrše se određene inicijalizacije članskih promenljivih, pa obične članske promenljive i blokovi koda imaju prednost pred konstruktorom.
Što se tiče dubljeg objašnjenja zašto je baš taj redosled, treba dublje proučiti tok izvršavanja JVM-a. Jedan probni kod je:
class Father{
public Father() {
System.out.println(++b1+"konstruktor nadklase");
}// konstruktor nadklase, četvrti
static int a1=0;// static nadklase, prvi; obrati pažnju na redosled
static {
System.out.println(++a1+"static nadklase");
}
int b1=a1;// članske promenljive i blokovi nadklase, treći
{
System.out.println(++b1+"blok nadklase");
}
}
class Son extends Father{
public Son() {
System.out.println(++b2+"konstruktor podklase");
}// konstruktor podklase, šesti
static {// static podklase, drugi korak
System.out.println(++a1+"static podklase");
}
int b2=b1;// članske promenljive i blokovi podklase, peti
{
System.out.println(++b2 + "blok podklase");
}
}
public class test9 {
public static void main(String[] args) {
Son son=new Son();
}
}Rezultat izvršavanja:

3) Polimorfizam
Java polimorfizam u objektno-orijentisanom programiranju znači da objekti iste klase u različitim situacijama ispoljavaju različita ponašanja i stanja.
- Podklasa može naslediti polja i metode nadklase, a objekat podklase može direktno koristiti metode i polja nadklase (osim privatnih).
- Podklasa može redefinisati metod nasleđen od nadklase, tako da objekat podklase pri pozivu tog metoda ispoljava drugačije ponašanje.
- Objekat podklase može se dodeliti referenci tipa nadklase, čime se preko reference tipa nadklase poziva metod redefinisan u podklasi i ostvaruje polimorfizam.
Cilj polimorfizma jeste da poveća fleksibilnost i proširivost koda, učini ga lakšim za održavanje i proširenje.
Na primer, dozvoljavanjem podklasi da nasledi metod nadklase i da ga redefiniše, povećava se iskoristivost koda.
Ili, polimorfizam može ostvariti dinamičko povezivanje, što znači da program u toku rada može i tad da utvrdi poziv metoda objekta.
"Suva je ova teorija, hajde da je analiziramo konkretno preko koda."
01. Šta je polimorfizam
U mom sećanju, onaj sjajan okršaj između Sun Wukonga i Erlang Shena iz "Putovanja na zapad" savršeno objašnjava reč "polimorfizam": jedan Sun Wukong može u sedamdeset dva oblika; jedan Erlang Shen takođe može u sedamdeset dva oblika; obojica se mogu pretvoriti u različite oblike, a dovoljno je da tiho izgovore "promena".
Šta je Java polimorfizam? To je zapravo sposobnost — isto ponašanje ima različite oblike ispoljavanja; drugim rečima, pri izvršavanju koda Java u toku rada, na osnovu tipa objekta, proizvodi različite rezultate. Isto kao što Sun Wukong i Erlang Shen samo treba da izgovore "promena" i već se pretvore — i to svaki put u nešto drugo; jedan te isti princip.
Tri su uslova za polimorfizam:
- Podklasa nasleđuje nadklasu
- Podklasa redefiniše metod nadklase
- Referenca nadklase ukazuje na objekat podklase
Jednostavna primena polimorfizma, pogledajmo spisak programa 1-1:
// podklasa nasleđuje nadklasu
public class Wangxiaoer extends Wanger {
public void write() { // podklasa redefiniše metod nadklase
System.out.println("Zapamtimo mržnju, pokazimo odlučnost da se osnažimo");
}
public static void main(String[] args) {
// referenca nadklase ukazuje na objekat podklase
Wanger[] wangers = { new Wanger(), new Wangxiaoer() };
for (Wanger wanger : wangers) {
// kada je objekat Wanger, ispisuje: Ne zaboravimo sramotu domovine
// kada je objekat Wangxiaoer, ispisuje: Zapamtimo mržnju, pokazimo odlučnost da se osnažimo
wanger.write();
}
}
}
class Wanger {
public void write() {
System.out.println("Ne zaboravimo sramotu domovine");
}
}02. Polimorfizam i kasno povezivanje
Sada da razmislimo o jednom pitanju: kada se u spisku programa 1-1 izvrši wanger.write(), pošto kompajler ima samo jednu referencu tipa Wanger, kako zna da li da pozove metod write() nadklase Wanger ili metod write() podklase Wangxiaoer?
Odgovor je — u toku rada, na osnovu tipa objekta, vrši se kasno povezivanje; u fazi prevođenja kompajler ne zna tip objekta, ali mehanizam poziva metoda u Javi pronalazi ispravno telo metode, zatim ga izvršava i daje tačan rezultat.
Jedna važna prednost koju pruža mehanizam polimorfizma jeste dobra proširivost programa. Pogledajmo spisak programa 2-1:
// podklasa nasleđuje nadklasu
public class Wangxiaoer extends Wanger {
public void write() { // podklasa redefiniše metod nadklase
System.out.println("Zapamtimo mržnju, pokazimo odlučnost da se osnažimo");
}
public void eat() {
System.out.println("Ne volim da čitam, volim samo da jedem");
}
public static void main(String[] args) {
// referenca nadklase ukazuje na objekat podklase
Wanger[] wangers = { new Wanger(), new Wangxiaoer() };
for (Wanger wanger : wangers) {
// kada je objekat Wanger, ispisuje: Ne zaboravimo sramotu domovine
// kada je objekat Wangxiaoer, ispisuje: Zapamtimo mržnju, pokazimo odlučnost da se osnažimo
wanger.write();
}
}
}
class Wanger {
public void write() {
System.out.println("Ne zaboravimo sramotu domovine");
}
public void read() {
System.out.println("Svake nedelje pročitaj jednu dobru knjigu");
}
}U spisku programa 2-1 dodali smo metod read() u klasu Wanger i metod eat() u klasu Wangxiaoer, ali to nimalo ne utiče na poziv metode write().
Metod write() zanemaruje promene u okolnom kodu i i dalje radi normalno. To me podseća na stihove iz "Devet yang prave pisme" u romanu Džin Junga "Nebeski mač i zmajeva sablja": "Neka je snažan koliko hoće, hladan vetar pošumi po brdu; neka je drsk koliko hoće, sjajna mesecina obasjava reku."
Ova odlična osobina polimorfizma znači da prilikom izmene koda ne moramo biti previše napeti, jer je polimorfizam važna osobina koja programeru omogućava da "razdvoji ono što se menja od onoga što ostaje nepromenjeno".
03. Polimorfizam i konstruktori
Pozivanje polimorfne metode unutar konstruktora daje zanimljiv rezultat. Pogledajmo spisak programa 3-1:
public class Wangxiaosan extends Wangsan {
private int age = 3;
public Wangxiaosan(int age) {
this.age = age;
System.out.println("Uzranja Wangxiaosana: " + this.age);
}
public void write() { // podklasa redefiniše metod nadklase
System.out.println("Moja treća godina kada krećem u vrtić je: " + this.age);
}
public static void main(String[] args) {
new Wangxiaosan(4);
// Pre polaska u vrtić
// Moja treća godina kada krećem u vrtić je: 0
// Nakon polaska u vrtić
// Uzranja Wangxiaosana: 4
}
}
class Wangsan {
Wangsan () {
System.out.println("Pre polaska u vrtić");
write();
System.out.println("Nakon polaska u vrtić");
}
public void write() {
System.out.println("Ja sam u vrtić krenuo sa tri i po godine");
}
}Da li te iznenada iznenađuje rezultat? Pri kreiranju objekta Wangxiaosan prosleđena je starost 4, ali izlaz nije ni "Ja sam u vrtić krenuo sa tri i po godine", ni "Moja treća godina kada krećem u vrtić je: 4".
Zašto?
Zato što se pri kreiranju objekta podklase najpre poziva konstruktor nadklase, a konstruktor nadklase zatim poziva polimorfni metod koji je podklasa redefinisala. Pošto nadklasa ne zna koje su vrednosti polja u objektu podklase, privremeno inicijalizuje svojstvo tipa int na 0, a tek potom poziva konstruktor podklase (konstruktor podklase zna da je starost 4).
04. Polimorfizam i konverzija naniže
Konverzija naniže znači eksplicitno prevođenje reference nadklase u tip podklase; to nije bezbedno, jer referenca nadklase ponekad ukazuje na objekat nadklase, pa će konverzija naniže baciti ClassCastException što znači da je konverzija tipa promašena. Ali ako referenca nadklase ukazuje na objekat podklase, konverzija naniže će uspeti.
Pogledajmo spisak programa 4-1:
public class Wangxiaosi extends Wangsi {
public void write() {
System.out.println("Zapamtimo mržnju, pokazimo odlučnost da se osnažimo");
}
public void eat() {
System.out.println("Ne volim da čitam, volim samo da jedem");
}
public static void main(String[] args) {
Wangsi[] wangsis = { new Wangsi(), new Wangxiaosi() };
// wangsis[1] može da se konvertuje naniže
((Wangxiaosi) wangsis[1]).write();
// wangsis[0] ne može da se konvertuje naniže
((Wangxiaosi)wangsis[0]).write();
}
}
class Wangsi {
public void write() {
System.out.println("Ne zaboravimo sramotu domovine");
}
public void read() {
System.out.println("Svake nedelje pročitaj jednu dobru knjigu");
}
}4) Kratak pregled
Dobro, Sanmej, toliko o nasleđivanju za ovaj put. Tri glavne osobine Java objektne orijentacije — enkapsulacija, nasleđivanje i polimorfizam — sjajno si već savladala/o.
- Enkapsulacija: podrazumeva enkapsulaciju klase; enkapsulacija obuhvata svojstva i metode klase, pri čemu se spoljašnjem svetu izlažu samo metode, a ne i detalji upotrebe. Zato pri projektovanju članskih promenljivih klase one se najčešće postavljaju kao privatne, a pristup im se ostvaruje preko
getisetmetoda. - Nasleđivanje: podklasa nasleđuje nadklasu, odnosno "sin preuzima očev zanat"; podklasa poseduje sva svojstva i metode nadklase, osim privatnih, a na toj osnovi može dodati i svoja nova svojstva i metode. Glavni cilj je iskorišćavanje koda.
- Polimorfizam: polimorfizam je sposobnost istog ponašanja da ima više različitih oblika ispoljavanja. Drugim rečima, jedna nadklasa može imati nekoliko podklasa, a podklase na različite načine implementiraju metod nadklase; pri pozivu metoda nadklase referenca nadklase ukazuje na različite instance podklasa i time se izvršavaju različiti metodi — to je tzv. metod nadklase koji je polimorfan.
Na kraju, poklanjam ti jednu sliku da u njoj razmršiš odnose među njima.

"Dobro, Erge, da s tim malo svarim, zaista ih je danas puno. Ti malo odmori", odgovorila je Sanmej.
Referenca: https://bbs.huaweicloud.com/blogs/271358, autor: bigsai, priredio: Chenmo Wang Er
