php statičko povezivanje. Kasno statičko uvezivanje. Primjer statičkog i dinamičkog povezivanja u Javi

Ovaj paragraf, unatoč svojoj kratkoći, vrlo je važan - gotovo sav stručan programiranje u Javi se temelji na korištenju polimorfizma. Ujedno je ova tema studentima jedna od najtežih za razumijevanje. Stoga se preporuča nekoliko puta pažljivo pročitati ovaj odlomak.

Metode klase označene su statičkim modifikatorom s razlogom - za njih, prilikom prevođenja programskog koda, statičko povezivanje. To znači da u kontekstu koje klase je naziv metode naveden u izvornom kodu, poveznica se postavlja na metodu te klase u prevedenom kodu. Odnosno, provodi se vezanje naziva metode na mjestu poziva s izvršnim kodom ovu metodu. Ponekad statičko povezivanje nazvao rano uvezivanje, budući da se javlja u fazi kompilacije programa. Statičko povezivanje u Javi se koristi u još jednom slučaju - kada je klasa deklarirana s finalnim modifikatorom ("final", "final").

Objektne metode u Javi su dinamičke, odnosno podložne su dinamičko povezivanje. Javlja se u fazi izvođenja programa izravno tijekom poziva metode, a u fazi pisanja ove metode nije unaprijed poznato iz koje će klase biti izvršen poziv. To je određeno tipom objekta za koji ovaj kod radi - kojoj klasi objekt pripada, iz koje je klase metoda pozvana. Ovo se vezanje događa dugo nakon što je kod metode kompajliran. Stoga se ova vrsta uveza često naziva kasni uvez.

Programski kod temeljen na pozivu dinamičke metode, ima svojstvo polimorfizam– isti kod radi različito ovisno o vrsti objekta koji ga poziva, ali radi iste stvari na razini apstrakcije koja se odnosi na izvorni kod metode.

Kako bismo objasnili ove riječi, koje nisu baš jasne na prvo čitanje, razmotrimo primjer iz prethodnog paragrafa - rad metode moveTo. Neiskusni programeri misle da ovu metodu treba nadjačati u svakoj klasi potomku. To se zapravo može učiniti i sve će raditi ispravno. Ali takav će kod biti iznimno suvišan - nakon svega, implementacija metode bit će u svim klasama potomcima Lik potpuno isto:

public void moveTo(int x, int y)( hide(); this.x=x; this.y=y; show(); );

Štoviše, ovaj slučaj ne iskorištava polimorfizam. Dakle, nećemo to učiniti.

Također je često zagonetno zašto apstraktna klasa Lik napišite implementaciju ove metode. Uostalom, pozivi na hide i show metode koje se u njemu koriste, na prvi bi pogled trebali biti pozivi apstraktne metode– odnosno, čini se da uopće ne mogu raditi!

Ali metode skrivanja i prikazivanja su dinamične, što, kao što već znamo, znači da se povezivanje naziva metode i njenog izvršnog koda vrši u fazi izvršavanja programa. Stoga, činjenica da su te metode navedene u kontekstu klase Lik, ne znači da će biti pozvani iz razreda Lik! Štoviše, možete jamčiti da metode skrivanja i prikazivanja nikada neće biti pozvane iz ove klase. Neka imamo varijable dot1 tipa Dot i circle1 tipa Circle i njima su dodijeljene reference na objekte odgovarajućih tipova. Pogledajmo kako se ponašaju pozivi dot1.moveTo(x1,y1) i circle1.moveTo(x2,y2).

Prilikom poziva dot1.moveTo(x1,y1) dolazi do poziva klase Lik metoda moveTo. Doista, ova metoda u klasi Dot nije nadjačana, što znači da je naslijeđena od Lik. U metodi moveTo, prva izjava je poziv metode dinamičkog skrivanja. Implementacija ove metode preuzeta je iz klase čiji je objekt dot1 koji poziva ovu metodu instanca. Odnosno iz klase Dot. Dakle, poanta je skrivena. Tada se mijenjaju koordinate objekta, nakon čega se on poziva dinamička metoda pokazati. Implementacija ove metode preuzeta je iz klase čiji je objekt dot1 koji poziva ovu metodu instanca. Odnosno iz klase Dot. Stoga se na novoj lokaciji prikazuje točka.

Za pozivanje circle1.moveTo(x2,y2) sve je apsolutno slično - metode dinamičkog hide i showa pozivaju se iz klase čiji je objekt circle1 instanca, odnosno iz klase Circle. Dakle, krug je skriven na starom mjestu, a prikazan na novom.

To jest, ako je objekt točka, točka se pomiče. A ako je objekt krug, krug se kreće. Štoviše, ako jednog dana netko napiše, na primjer, klasu Ellipse koja je potomak Circle i stvori objekt Elipsa elipsa=nova elipsa(…), tada će pozivanje ellipse.moveTo(…) premjestiti elipsu na novu lokaciju. A to će se dogoditi u skladu s načinom na koji su metode hide i show implementirane u klasi Ellipse. Imajte na umu da će kompajlirani polimorfni kod klase raditi prije mnogo vremena Lik. Polimorfizam je osiguran činjenicom da se reference na te metode ne stavljaju u kod metode moveTo u vrijeme kompajliranja - konfiguriraju se na metode s takvim imenima iz klase pozivajućeg objekta odmah u trenutku poziva metode moveTo.

Postoje dvije vrste objektno orijentiranih programskih jezika: dinamičke metode– zapravo dinamičan i virtualni. Prema principu rada, oni su potpuno slični i razlikuju se samo u značajkama implementacije. Poziv virtualne metode brže. Pozivanje dinamičkih je sporije, ali servisni stol dinamičke metode(DMT – Dynamic Methods Table) zauzima nešto manje memorije od tablice virtualne metode(VMT – Virtualna tablica metoda).

Možda se čini kao izazov dinamičke metode nije vremenski učinkovit zbog duljine vremena potrebnog za traženje imena. Zapravo, tijekom poziva se ne vrši traženje imena, već se koristi mnogo brži mehanizam koji koristi spomenutu tablicu virtualnih (dinamičkih) metoda. Ali nećemo se zadržavati na specifičnostima implementacije ovih tablica, budući da Java ne razlikuje ove vrste metoda.

6.8. Objekt osnovne klase

Klasa Object je osnovna klasa za sve Java klase. Stoga su sva njegova polja i metode naslijeđene i sadržane u svim klasama. Klasa Object sadrži sljedeće metode:

  • public Boolean equals(Object obj)– vraća true u slučaju kada su vrijednosti objekta iz kojeg je metoda pozvana i objekta koji je prošao kroz obj referencu na popisu parametara jednake. Ako objekti nisu jednaki, vraća se false. U klasi Objekt, jednakost se tretira kao referentna jednakost i ekvivalentna je operatoru usporedbe "==" . Ali u potomcima se ova metoda može nadjačati i može usporediti objekte prema njihovom sadržaju. Na primjer, to se događa za objekte numeričkih klasa ljuske. To se lako može provjeriti kodom poput ovog:

    Dupli d1=1,0,d2=1,0; System.out.println("d1==d2 ="+(d1==d2)); System.out.println("d1.equals(d2) ="+(d1.equals(d2)));

    Prvi redak izlaza će dati d1==d2 =false, a drugi će dati d1. jednako(d2)=točno

  • javni int hashCode()– pitanja hash kod objekt. Hash kod je uvjetno jedinstveni numerički identifikator povezan s elementom. Iz sigurnosnih razloga, ne možete dati adresu objekta aplikacijskom programu. Stoga u Javi hash kod zamjenjuje adresu objekta u slučajevima kada je za neku svrhu potrebno pohraniti tablice adresa objekata.
  • zaštićeni klon objekta() baca CloneNotSupportedException – metoda kopira objekt i vraća vezu na stvoreni klon (duplikat) objekta. U potomcima klase Object potrebno ju je redefinirati, te također naznačiti da klasa implementira sučelje Clonable. Pokušaj pozivanja metode iz objekta koji se ne može klonirati uzrokuje podizanje iznimke CloneNotSupportedException("Klon nije podržan"). O sučeljima i iznimnim situacijama bit će riječi kasnije.

    Postoje dvije vrste kloniranja: plitko (plitko), kada se vrijednosti polja izvornog objekta kopiraju jedan na jedan u klon, i duboko (duboko), u kojem se stvaraju novi objekti za polja referentni tip, kloniranje objekata na koje se izvorna polja odnose. U plitkom kloniranju, i original i klon će referirati na iste objekte. Ako objekt ima samo polja primitivni tipovi, nema razlike između plitkog i dubokog kloniranja. Implementaciju kloniranja provodi programer koji razvija klasu; ne postoji mehanizam automatskog kloniranja. U fazi razvoja klase trebali biste odlučiti koju opciju kloniranja odabrati. U velikoj većini slučajeva to je potrebno duboko kloniranje.

  • javna konačna klasa getClass()– vraća referencu na metaobjekt klase tipa. Uz njegovu pomoć možete dobiti informacije o klasi kojoj objekt pripada i pozvati njegove metode klase i polja klase.
  • zaštićena praznina finalize() baca Throwable – poziva se prije nego što je objekt uništen. Mora se nadjačati u onim potomcima Objekta u kojima je potrebno izvršiti neke pomoćne radnje prije uništavanja objekta (zatvoriti datoteku, prikazati poruku, nacrtati nešto na ekranu, itd.). Ova metoda je detaljnije opisana u odgovarajućem odlomku.
  • javni niz u niz ()– vraća nizovnu reprezentaciju objekta (što je moguće adekvatnije). U klasi Object, ova metoda proizvodi niz potpuno kvalificiranog naziva objekta (s nazivom paketa), iza kojeg slijedi znak "@", a zatim heksadecimalni hash kod objekta. Većina standardnih klasa nadjačava ovu metodu. Za numeričke klase vraća se string reprezentacija broja, za string klase – sadržaj niza, za znakovne klase – sam znak (a ne string reprezentacija njegovog koda!). Na primjer, sljedeći isječak koda

    Objekt obj=novi Objekt(); System.out.println(" obj.toString() daje "+obj.toString()); Double d=novo Double(1.0); System.out.println(" d.toString() daje "+d.toString()); Znak c="A"; System.out.println("c.toString() daje "+c.toString());

    dat će zaključak

    obj.toString() daje java.lang.Object@fa9cf d.toString() daje 1.0 c.toString() daje A

Postoje i metode obavijestiti(), obavijestiSve(), te nekoliko preopterećenih varijanti metode čekati, dizajniran za rad s nitima. O njima se govori u odjeljku o nitima.

6.9. Dizajneri. Zadržane riječi super i ovo. Inicijalizacijski blokovi

Kao što je već spomenuto, objekti u Javi se kreiraju korištenjem rezervirane riječi new, nakon koje slijedi konstruktor - posebna podrutina koja stvara objekt i inicijalizira polja stvorenog objekta. Povratni tip nije naveden za njega i nije ni metoda objekta (pozvana kroz naziv klase kada objekt još ne postoji) niti metoda klase (objekt i njegova polja dostupni su u konstruktoru putem ove reference) . Zapravo, konstruktor, u kombinaciji s new operatorom, vraća referencu na objekt koji se stvara i može se smatrati posebnom vrstom metode koja kombinira značajke metode klase i metode objekta.

Ako objekt ne zahtijeva nikakvu dodatnu inicijalizaciju kada je kreiran, možete koristiti konstruktor, koji je prisutan prema zadanim postavkama za svaku klasu. Ovo je naziv klase, iza kojeg slijede prazne zagrade - bez popisa parametara. Nema potrebe navesti takav konstruktor kada se razvija klasa; on je prisutan automatski.

Ako je potrebna inicijalizacija, obično se koriste konstruktori s popisom parametara. Pogledali smo primjere takvih konstruktora za klase Dot i Circle. Klase Dot i Circle naslijeđene su od apstraktne klase, u kojem nije bilo konstruktora. Ako postoji nasljeđivanje od neapstraktne klase, to jest one koja već ima konstruktor (čak i zadani konstruktor), pojavljuju se neke specifičnosti. Prva izjava u konstruktoru mora biti poziv konstruktoru iz nadklase. Ali to se ne radi kroz naziv ove klase, već korištenjem rezervirane riječi super(od "superclass"), nakon čega slijedi popis parametara potrebnih za baka i djed konstruktor. Ovaj konstruktor inicijalizira podatkovna polja koja su naslijeđena od superklase (uključujući od bilo kojih ranijih predaka).

Na primjer, napišimo klasu FilledCircle - potomak od Circle, čija će instanca biti nacrtana kao krug u boji.

paket java_gui_example; import java.awt.*; javna klasa FilledCircle extends Circle( /** Stvara novu instancu FilledCircle */ public FilledCircle(Graphics g,Color bgColor, int r,Color color) ( super(g,bgColor,r); this.color=color; ) public void show())( Color oldC=graphics.getColor(); graphics.setColor(color); graphics.setXORMode(bgColor); graphics.fillOval(x,y,size, size); graphics.setColor(oldC); graphics . setPaintMode(); ) public void hide())( Color oldC=graphics.getColor(); graphics.setColor(color); graphics.setXORMode(bgColor); graphics.fillOval(x,y,size, size); graphics .setColor (oldC graphics.setPaintMode(); Općenito, logika za stvaranje složenih objekata: prvo se kreira i inicijalizira nadređeni dio objekta, počevši od dijela naslijeđenog iz klase Object, pa dalje po hijerarhiji, završavajući s dijelom koji pripada samoj klasi. To je razlog zašto je obično prva izjava konstruktora poziv baki i djedu konstruktoru super ( popis parametara

), jer pristup neinicijaliziranom dijelu objekta koji pripada nadređenoj klasi može dovesti do nepredvidivih posljedica.

U ovom razredu koristimo napredniji način crtanja i "skrivanja" oblika u odnosu na prethodne razrede. Temelji se na XOR (exclusive or) načinu crtanja. Ovaj način se postavlja pomoću metode setXORMode. U tom slučaju ponovljeni ispis figure na isto mjesto dovodi do vraćanja izvorne slike u izlazno područje. Prijelaz na normalni način slikanja provodi se metodom setPaintMode.

Vrlo često se koriste u konstruktorima podaci

. Cilj polimorfizma, primijenjenog na objektno orijentirano programiranje, je korištenje jednog imena za definiranje radnji zajedničkih klasi.
U Javi su varijable objekta polimorfne. Na primjer:
Varijabla tipa King može se odnositi ili na objekt tipa King ili na objekt bilo koje podklase King.
Uzmimo sljedeći primjer:

class King ( javni nevažeći govor() ( System .out .println ("Ja sam kralj Andala!") ; ) javni nevažeći govor(String citat) ( System .out .println ("Mudar čovjek je rekao: " + citat) javni nevažeći govor (Boolean speakLoudly) ( if (speakLoudly) System .out .println ( "JA SAM KRALJ ANDALA!!!11") ;
else System .out .println ("ja" sam... kralj..." ) ; ) ) klasa AerysTargaryen extends King ( @Override public void words() ( System .out .println ("Spalite ih sve... " ) ; ) @Override public void voice(String quotation) ( System .out .println (quotation+ " ... A sad ih sve spali!" ) ; ) ) class Kingdom ( public static void main(String args) ( King king = new AerysTargaryen() ; king.speech ("Homo homini lupus est")Što se događa kada se pozove metoda koja pripada objektu
kralj?1. Prevodilac provjerava deklarirani tip objekta i naziv metode, numerira sve metode s imenom govor u klasi AerusTargarien i sve javne metodegovor u nadrazredimaAerusTargarien
. Prevodilac sada zna moguće kandidate kada poziva metodu.2. Prevodilac određuje tipove argumenata koji se prosljeđuju metodi. Ako se pronađe samo jedna metoda čiji potpis odgovara argumentima, vrši se poziv.Ovaj proces king.speech("Homo homini lupus est")prevoditelj će odabrati metodu govor (navod niza) , ne.
govor()Ako prevoditelj pronađe više metoda



s odgovarajućim parametrima (ili bez njih), prikazuje se poruka o pogrešci.
Prevodilac sada zna naziv i tipove parametara metode koju treba pozvati.3. U slučaju da je pozvana metoda, privatni, statičkikonačni ili konstruktor, koristi se statičko vezanje ( rano uvezivanje ). U drugim slučajevima, metoda koja se poziva određena je stvarnim tipom objekta kroz koji se poziv pojavljuje. one. koristi se tijekom izvođenja programa.

dinamičko uvezivanje (kasno uvezivanje)
4. Virtualni stroj unaprijed stvara tablicu metoda za svaku klasu, koja navodi potpise svih metoda i stvarnih metoda koje treba pozvati.Tablica metoda za klasu Kralj
  • izgleda ovako: govor() -, ne
  • Kralj.govor() - govor (navod niza) -
  • govor() - govor (navod niza)
govor (Booleov speakLoudly)I za razred
  • AerysTargaryen - ovako: govor() - . , ne
  • Kralj. AerysTargaryen
  • govor (Booleov speakLoudly) -Kralj. govor (Booleov speakLoudly)
Metode naslijeđene od Object-a zanemarene su u ovom primjeru.
Prilikom pozivakralj.govor() :
  1. Određuje se stvarni tip varijablekralj . U ovom slučaju jestAerysTargaryen.
  2. Virtualni stroj određuje klasi kojoj metoda pripada, ne
  3. Metoda se zove.
Povezivanje svih metoda uJavaprovodi se polimorfno, kasnim vezanjem.Dinamičko uvezivanje ima jednu važnu značajku: dopuštamodificirati programe bez ponovnog kompajliranja njihovih kodova. To je ono što programi radedinamički proširivo ( rastezljiv).
Što se događa ako pozovete dinamički vezanu metodu konstruiranog objekta u konstruktoru? Na primjer:
klasa King ( King () ( Sustav . out . println ( "Poziv King konstruktora" ) ); govor () ; //polimorfna metoda nadjačana u AerysTargaryen) javni nevažeći govor() ( System .out .println ("Ja sam kralj Andala!" ) ; ) ) klasa AerysTargaryen proširuje King (privatni String žrtveName; AerysTargaryen() ( System .out .println ( "Nazovi konstruktora Aerys Targaryen") ; žrtveName = "Lyanna Stark" ;

govor() ; ) @Override public void speak() ( System .out .println ("Burn " + žrtvaName + "!" ) ; ) ) class Kingdom ( public static void main(String args) ( King king = new AerysTargaryen() ; ) ) Proizlaziti:!
Zovite King konstruktora
Burn null! Nazovite Aerys Targaryen konstruktoricu Burn Lyannu Stark
  1. Konstruktor osnovne klase uvijek se poziva tijekom konstrukcije izvedene klase. Poziv se automatski pomiče gore u lancu nasljeđivanja tako da se na kraju pozivaju konstruktori svih osnovnih klasa kroz lanac nasljeđivanja.
  2. To znači da prilikom pozivanja konstruktora
  3. novi AerysTargaryen() će se zvati: novi objekt()
novi kralj()

Međutim, dinamički vezan poziv može ići u "vanjski" dio hijerarhije, odnosno u izvedene klase. Ako pozove metodu izvedene klase u konstruktoru, to može dovesti do manipulacije neinicijaliziranim podacima, što vidimo u izlazu ovog primjera.

Rezultat programa određen je izvođenjem algoritma za inicijalizaciju objekta:

  1. Memorija dodijeljena novom objektu popunjava se binarnim nulama.
  2. Konstruktori osnovne klase pozivaju se redoslijedom koji je ranije opisan. U ovom trenutku se poziva nadjačana metoda govor() (da, prije pozivanja konstruktora klaseAerysTargaryen), gdje se otkriva da varijablažrtvaName je nula zbog prve faze.
  3. Inicijalizatori članova klase pozivaju se redoslijedom kojim su definirani.
  4. Izvršava se tijelo konstruktora izvedene klase.
Konkretno, zbog takvih problema u ponašanju, vrijedi se pridržavati sljedećeg pravila za pisanje konstruktora:
- izvoditi u konstruktoru samo najnužnije i najjednostavnije akcije za inicijalizaciju objekta
- ako je moguće, izbjegavajte pozivanje metoda koje nisu definirane kao privatni ili konačni (što je u ovom kontekstu ista stvar).
Korišteni materijali:
  1. Eckel B. - Razmišljanje u Javi , 4. izdanje - 8. poglavlje
  2. Cay S. Horstmann, Gary Cornell - Core Java 1 - Poglavlje 5
  3. Wikipedia

Uvezivanje- zamjena specifičnih poziva funkcija u programske kodove - razredne metode. Ima smisla samo za izvedene klase.

Obično prevodilac ima potrebne informacije da odredi na koju se funkciju misli. Na primjer, ako program naiđe na poziv obj.f(), prevodilac jedinstveno odabire funkciju f() ovisno o vrsti odredišnog obj. Ako program koristi pokazivače na instance klase:ptr->f(), izbor funkcije - metode klase određen je tipom pokazivača.

Ako se odabir funkcije vrši tijekom kompajliranja, imamo posla s statičko povezivanje.

U ovom slučaju, funkcija - metoda osnovne klase - bit će pozvana za pokazivač na osnovnu klasu, čak i ako je pokazivaču na osnovnu klasu dodijeljena vrijednost adrese instance izvedene klase.

Ako se odabir funkcije provodi u fazi izvođenja programa, imamo posla s dinamičko povezivanje.

U ovom slučaju, ako je tijekom izvođenja programa pokazivaču na osnovnu klasu dodijeljena adresa instance osnovne klase, bit će pozvana metoda osnovne klase; Ako je pokazivaču na osnovnu klasu dodijeljena adresa instance izvedene klase, bit će pozvana metoda izvedene klase.

Virtualne funkcije

Prema zadanim postavkama, izvedene klase su statički povezane. Ako se dinamičko vezanje treba koristiti za bilo koju metodu klase, takve metode moraju biti deklarirane virtualni .

Virtualne funkcije:

    imati virtualnu ključnu riječ u prototipu u osnovnoj klasi;

    obavezne funkcije člana klase:

    Sve izvedene klase moraju imati isti prototip (navođenje riječi virtual u izvedenim klasama nije potrebno).

Ako bilo koja metoda u izvedenim klasama ima isto ime kao u osnovnoj klasi, ali drugačiji popis parametara, imamo preopterećene funkcije.

Primjer: klase točke i kružnice.

ispis virtualne praznine();

klasa Krug: javna točka(

void print(); // možete virtualno prazniti print();

void Point::print()

cout<< "Point (" << x << ", " << y << ")";

void Circle::print()

cout<< "Circle with center in "; Point::print();

cout<< "and radius " << rad;

Upotreba:

Točka p1(3,5), p2(1,1), *pPtr;

Krug c1(1), c2(p2, 1);

pPtr = pPtr->print(); // dobiti: Točka (3, 5)

pPtr = pPtr->print(); // dobiti:

Kružnica sa središtem u točki (1, 1) i radijusom 1

Primjer dinamičkog uvezivanja: popis

Najčešća upotreba dinamičkog vezivanja je s klasama spremnika koji sadrže pokazivač na osnovnu klasu; Takve klase spremnika mogu uključivati ​​informacije koje se odnose i na osnovne i na bilo koje izvedene klase.

Razmotrimo primjer - popis koji sadrži i točke i krugove.

// konstruktor

Stavka():info(NULL), sljedeći(NULL)()

Stavka (točka *p):info(p), sljedeći(NULL)()

Popis():glava(NULL)()

void insert(Point *p)(p->next = head; head = p;)

void List::print()

for(Stavka *cur = glava; cur; cur = cur->next)(

cur->info->print();

cout<< endl;

Korištenje klase:

Točka *p = nova točka(1,2);

mylist.insert(p);

p = novi ciklus(1,2,1);

mylist.insert(p);

Kružnica sa središtem u točki (1, 2) i radijusom 1