Ce este legarea timpurie și tardivă? Legarea metodei timpurie și târzie. Legarea timpurie și târzie în Java

10.11.2019 Programe

Ultima actualizare: 04.02.2019

Anterior, am analizat două moduri de a schimba funcționalitatea metodelor moștenite de la o clasă de bază - ascunderea și suprascrierea. Care este diferența dintre aceste două metode?

Trece peste

Să luăm un exemplu cu modificarea metodei:

Clasa Persoană ( șir public Prenume ( obține; set; ) șir public Nume ( obține; set; ) Persoană publică (șir prenume, șir Prenume) ( Prenume = prenume; Nume = prenume; ) public virtual void Display() ( Console.WriteLine ($"(Prenume) (Nume)"); ) ) class Angajat: Persoană ( public șir Companie ( get; set; ) public Employee(șir Prenume, șir Nume, șir companie) : bază(Prenume, Prenume) ( Companie = companie; ) public override void Display() ( Console.WriteLine($"(FirstName) (LastName) rulează în (Companie)"); ) )

Să creăm și un obiect Employee și să-l transmitem unei variabile de tip Persoană:

Persoana tom = nou angajat ("Tom", "Smith", "Microsoft"); tom.Display(); // Tom Smith lucrează la Microsoft

Acum obținem un rezultat diferit față de ascunderea. Iar când se apelează tom.Display() se execută implementarea metodei Display din clasa Employee.

Pentru a lucra cu metode virtuale, compilatorul generează un tabel de metode virtuale (Virtual Method Table sau VMT). Adresele metodelor virtuale sunt scrise în el. Fiecare clasă are propriul său tabel.

Când este creat un obiect de clasă, compilatorul transmite cod special constructorului obiectului care conectează obiectul și tabelul VMT.

Și când este apelată o metodă virtuală, adresa tabelului său VMT este preluată din obiect. Adresa metodei este apoi preluată de la VMT și controlul este transferat acestuia. Adică, procesul de selectare a implementării unei metode este efectuat în timpul execuției programului. Acesta este de fapt modul în care se execută o metodă virtuală. Vă rugăm să rețineți că, deoarece mediul de rulare trebuie mai întâi să obțină adresa metodei dorite din tabelul VMT, acest lucru încetinește ușor execuția programului.

Ascundere

Acum să luăm aceleași clase Persoană și Angajat, dar în loc să suprascriem, folosim ascunderea:

Clasa Persoană ( șir public Prenume ( obține; set; ) șir public Nume ( obține; set; ) Persoană publică (șir prenume, șir prenume) ( Prenume = prenume; Nume = prenume; ) public void Display() ( Console.WriteLine( $"(FirstName) (LastName)"); ) ) class Angajat: Persoana ( public string Companie ( get; set; ) public Employee(string firstName, string lastNume, string company) : base(firstName, lastName) ( Compania = firma ; ) public new void Display() ( Console.WriteLine($"(FirstName) (LastName) rulează în (Companie)"); ) )

Și să vedem ce se întâmplă în următorul caz:

Persoana tom = nou angajat ("Tom", "Smith", "Microsoft"); tom.Display(); //Tom Smith

Variabila tom reprezintă tipul Persoană, dar stochează o referință la obiectul Employee. Cu toate acestea, la apelarea metodei Display, versiunea metodei care este definită în clasa Person va fi executată, și nu în clasa Angajat. De ce? Clasa Employee nu înlocuiește metoda Display moștenită de la clasa de bază, ci definește de fapt o nouă metodă. Prin urmare, atunci când tom.Display() este apelat, este apelată metoda Display din clasa Person.

Înainte de a atinge utilizarea efectivă a funcțiilor virtuale, este necesar să se ia în considerare astfel de concepte ca fiind obligatorii timpurii și târzii. Să comparăm două abordări pentru achiziționarea, de exemplu, a unui kilogram de portocale. În primul caz, știm dinainte că trebuie să cumpărăm 1 kg. portocale. Prin urmare, luăm un pachet mic, nu mult, dar suficienți bani pentru a acoperi acest kilogram. În al doilea caz, când plecăm de acasă, nu știm ce sau cât trebuie să cumpărăm. Prin urmare, luăm mașina (ce dacă sunt multe lucruri și ceva greu), ne aprovizionăm cu genți de dimensiuni mari și mici și luăm cât mai mult posibil mai mulți bani. Mergem la piață și rezultă că trebuie să cumpărăm doar 1 kg. portocale.

Exemplul de mai sus reflectă într-o anumită măsură sensul utilizării timpurii și legarea tardivă, respectiv. Este evident că pt acest exemplu prima varianta este optima. În al doilea caz, am oferit prea mult, dar nu am avut nevoie. Pe de altă parte, dacă în drum spre piață decidem că nu avem nevoie de portocale și decidem să cumpărăm 10 kg. mere, atunci în primul caz nu vom mai putea face asta. În al doilea caz, este ușor.

Acum să privim acest exemplu din punct de vedere al programării. Când folosim legarea timpurie, se pare că îi spunem compilatorului: „Știu exact ce vreau. Prin urmare, legați strâns (static) toate apelurile de funcție.” Când folosim mecanismul de legare tardivă, se pare că îi spunem compilatorului: „Nu știu încă ce vreau. Când va veni momentul, vă voi spune ce și cum vreau.”

Astfel, în timpul legării timpurii, apelantul și metodele apelate sunt legate cu prima ocazie, de obicei la compilare.

Când legați cu întârziere metoda apelată și metoda apelant, acestea nu pot fi legate în timpul compilării. Prin urmare, a fost implementat un mecanism special care determină modul în care va avea loc legarea metodelor de apelare și de apelare atunci când apelul este efectiv efectuat.

Este clar că viteza și eficiența legării timpurii sunt mai mari decât cele ale legării târzii. În același timp, legarea tardivă oferă o oarecare versatilitate a legăturii.

În cele din urmă, am ajuns la funcțiile și metodele virtuale în sine. Din păcate, pentru a ilustra metodele virtuale este destul de dificil să tragem vreo analogie cu lumea fizică. Prin urmare, vom lua în considerare imediat această problemă din punct de vedere al programării.

Deci, pentru ce sunt folosite metodele virtuale? Există metode virtuale pentru a face ca „succesorul” să se comporte diferit de „strămoș”, menținând în același timp compatibilitatea cu acesta.

Iată definiția metodelor virtuale:

Metoda virtuală este o metodă care, atunci când este declarată în descendenți, trece peste tot peste metoda corespunzătoare, chiar și în metodele descrise pentru strămoș dacă este chemată pentru copil..

Adresa unei metode virtuale este cunoscută doar în momentul în care programul este executat. Când este apelată o metodă virtuală, adresa acesteia este preluată din tabelul de metode virtuale al clasei sale. Astfel, ceea ce este necesar se numește.

Avantajul utilizării metodelor virtuale este că folosește legarea tardivă, ceea ce permite procesarea obiectelor al căror tip este necunoscut la momentul compilării.

Pentru a ilustra utilizarea metodelor virtuale, voi da un exemplu în limbaj C++ pe care l-am împrumutat de la unul Tutorial C++. Chiar dacă nu ești foarte versat în această limbă, sper ca explicațiile mele măcar să explice cumva sensul ei.

#include // conectarea bibliotecii standard C++, // care descrie unele funcții utilizate în clasa de program vehicul // clasa „vehicul” ( roți int; greutate float; public: // începutul secțiunii publice (deschise) a clasei virtual void mesaj (void) (cout message(); // apelează metoda mesajului obiectului șterge uniciclu; // șterge obiectul monociclu // Toate blocurile ulterioare de 3 linii sunt absolut identice cu primul // bloc cu singurul diferența fiind că clasa obiectului creat // se schimbă în mașină, camion, barcă monociclu = mașină nouă; monociclu->mesaj(); șterge monociclu; monociclu = camion nou; monociclu->mesaj(); șterge monociclu; monociclu = barcă nouă; monociclu->mesaj(); șterge monociclu; )

Rezultatele programului (ieșire pe ecran):

Vehicul Autoturism Vehicul Barcă

Să luăm în considerare exemplul dat. Avem trei clase mașină, camionȘi barcă, care sunt derivate din clasa de bază vehicul. În clasa de bază vehicul funcția virtuală descrisă mesaj. În două din cele trei clase ( mașină, barcă) își descrie și funcțiile mesaj, și în clasă camion nicio descriere a funcției sale mesaj. Toate rândurile pentru care nu am oferit comentarii nu au o importanță fundamentală pentru acest exemplu. Acum să trecem peste blocul principal al programului - funcții principal(). Descrierea unei variabile monociclu, ca un pointer către un obiect de tip vehicul. Nu voi intra în detalii despre motivul pentru care este un indicator către un obiect. Așa ar trebui să fie. În acest caz, tratați lucrul cu pointerul ca și cum ați lucra cu obiectul în sine. Detalii despre lucrul cu pointerii pot fi găsite în descrierile unui anumit limbaj OOP. Apoi, creați un obiect de clasă vehicul, variabil monociclu indică acest obiect. După aceasta numim metoda mesaj obiect monociclu, iar în linia următoare ștergem acest obiect. În următoarele trei blocuri de 3 linii, efectuăm operații similare, cu singura diferență că lucrăm cu obiecte de clasă mașină, camion, barcă. Folosirea unui pointer ne permite să folosim același pointer pentru toate clasele derivate. Suntem interesați de apelul de funcție mesaj pentru fiecare dintre obiecte. Dacă nu am fi specificat că funcţia mesaj clasă vehicul este virtual( virtual), atunci compilatorul va lega static (hard) orice apel la o metodă de pe obiectul pointer monociclu cu metoda mesaj clasă vehicul, deoarece în descriere am spus că variabila monociclu indică un obiect de clasă vehicul. Acestea. ar produce legarea timpurie. Ieșirea unui astfel de program ar fi rezultatul a patru linii „Vehicul”. Dar folosind o funcție virtuală în clasă, am obținut rezultate ușor diferite.

Când lucrați cu obiecte de clasă mașinăȘi barcă se numesc propriile metode mesaj, care este confirmat de afișarea mesajelor corespunzătoare pe ecran. La clasa camion nu exista metoda mesaj, din acest motiv se numește metoda corespunzătoare a clasei de bază vehicul.

Foarte des, o clasă care conține o metodă virtuală este numită clasă polimorfă. Cea mai importantă diferență este că clasele polimorfe pot gestiona obiecte al căror tip este necunoscut la momentul compilării. Funcțiile descrise ca virtuale în clasa de bază pot fi modificate în clasele derivate, iar legarea nu va avea loc în etapa de compilare (ceea ce se numește legare timpurie), ci în momentul accesului la aceasta metoda(legare târzie).

Metodele virtuale sunt descrise folosind un cuvânt cheie virtualîn clasa de bază. Aceasta înseamnă că într-o clasă derivată, această metodă poate fi suprascrisă de o metodă care este mai potrivită pentru acea clasă derivată. Odată declarată virtuală într-o clasă de bază, o metodă va rămâne virtuală pentru toate clasele derivate. Dacă o metodă virtuală nu este suprascrisă într-o clasă derivată, atunci când este apelată, o metodă cu acel nume va fi găsită în ierarhia clasei (adică în clasa de bază).

Ultimul lucru despre care trebuie menționat când vorbim funcții virtuale, este conceptul de clase abstracte. Dar ne vom uita la asta în pasul următor.

2

Să spunem că nu a existat nicio funcție Bună, și doar numim ob.display practic, apoi apelează funcția de afișare din clasa B, nu clasa A.

Apelul la display() este setat o dată de compilator la versiunea definită în clasa de bază. Aceasta se numește rezoluție statică a apelurilor de funcție sau legarea statică - apelul de funcție este efectuat înainte ca programul să fie executat. Aceasta este uneori numită și legarea timpurie, deoarece funcția display() este specificată la momentul compilării programului.

Acum, cum poate apela funcția de afișare a clasei derivate fără a utiliza cuvântul cheie virtual (legare târzie) înainte de funcția de afișare în clasa de bază?

Acum, în acest program, trecerea obiectului ca apel după valoare, apel cu pointer și apel prin referire la funcția Hello funcționează bine. Acum, dacă folosim polimorfism și dorim să redăm o funcție membru a unei clase derivate dacă este numită, trebuie să adăugăm cuvânt cheie virtual înainte de funcția de mapare a bazei de date. Dacă treceți valoarea unui obiect atunci când apelați folosind un pointer și apelați prin referință, este un apel de funcție în clasa derivată, dar dacă treceți obiectul după valoare, nu de ce este așa?>

Clasa A (public: void display(); // virtual void display() (cout<< "Hey from A" <display() ) int main() ( B obj; Bună (obj); // obj //&ob return 0; )

  • 2 raspunsuri
  • Triere:

    Activitate

4

acum cum poate apela funcția de afișare a clasei derivate fără a utiliza cuvântul cheie virtual (legare târzie) înainte de funcția de afișare în clasa de bază?

O funcție non-virtuală este pur și simplu rezolvată de compilator în funcție de tipul static al obiectului (sau referință sau pointer) pe care îl apelează. Astfel, un obiect dat de tip derivat, precum și o referință la subobiectul său:

Bb; A&a=b;

veți obține rezultate diferite de la apelarea unei funcții non-virtuale:

B.afisare(); // numit ca B a.display(); // numit ca A

Dacă cunoașteți tipul real, atunci puteți specifica cum doriți să numiți această versiune:

Static_cast (un ecran(); // numit ca B

dar ceea ce ar fi teribil de greșit este dacă obiectul la care se referă a nu are tipul B .

Acum, dacă folosim Polymorphism și dorim să mapam o funcție membru a unei clase derivate dacă este apelată, trebuie să adăugăm un cuvânt cheie virtual înainte de funcția map din bază.

A corecta. Dacă faceți o funcție virtuală, aceasta va fi rezolvată în timpul execuției în funcție de tipul dinamic al obiectului, chiar dacă utilizați un alt tip de referință sau pointer pentru a-l accesa. Deci ambele exemple de mai sus l-ar numi B.

Dacă trecem valoarea unui obiect prin apel prin pointer și apelăm prin referință, apelează funcția din clasa derivată, dar dacă trecem obiectul după valoare, asta nu înseamnă de ce este așa?

Dacă îl treci după valoare, atunci tu felierea it: copierea doar a părții A a unui obiect pentru a face un nou obiect de tip A . Deci, indiferent dacă această funcție este virtuală, apelând-o pe acest obiect va selecta versiunea lui A , deoarece este A și nimic altceva decât A .

0

wikipedia spune că tăierea obiectelor are loc deoarece nu există spațiu pentru a stoca membri suplimentari ai clasei derivate în superclasă, deci este tăiată. De ce nu are loc tăierea obiectelor dacă o trecem prin referință sau indicator? De ce superclasa primește spațiu suplimentar pentru a o depozita? -

---.NET Assemblys --- Legare tardivă

Legare tardivă este o tehnologie care vă permite să instanțiați un anumit tip și să-i apelați membrii în timpul execuției fără a-i codifica existența în timpul compilării. Când se creează o aplicație care necesită legarea tardivă la un tip dintr-un ansamblu extern, nu există niciun motiv pentru a adăuga o referință la acel ansamblu și, prin urmare, nu este specificat direct în manifestul codului de apelare.

La prima vedere, nu este ușor să vezi beneficiile legăturii târzii. Într-adevăr, dacă este posibil să efectuați legarea timpurie a unui obiect (de exemplu, adăugarea unei referințe de ansamblu și plasarea unui tip folosind noul cuvânt cheie), ar trebui să faceți acest lucru. Unul dintre cele mai convingătoare motive este că legarea timpurie permite ca erorile să fie capturate în timpul compilării, mai degrabă decât în ​​timpul executării. Cu toate acestea, legarea tardivă joacă, de asemenea, un rol important în orice aplicație extensibilă pe care o creați.

Clasa System.Activator

Sistemul de clasă. Activatorul (definit în ansamblul mscorlib.dll) joacă un rol cheie în procesul de legare târzie în .NET. În exemplul actual, singurul lucru de interes pentru moment este Metoda Activator.CreateInstance()., care vă va permite să instanțiați un tip cu legare tardivă. Această metodă are mai multe supraîncărcări și, prin urmare, oferă o flexibilitate destul de ridicată. În versiunea sa cea mai simplă, CreateInstance() preia un obiect Type valid care descrie entitatea care ar trebui să fie alocată memoriei din mers.

Pentru a vedea ce înseamnă acest lucru, să creăm un nou proiect de tip Console Application, să importam spațiile de nume System.I0 și System.Reflection în el folosind cuvântul cheie using și apoi să modificăm clasa Program așa cum se arată mai jos:

Utilizarea sistemului; folosind System.Reflection; folosind System.IO; namespace ConsoleApplication1 ( clasa Program ( static void Main() ( Assembly ass = null; try ( ass = Assembly.Load("fontinfo"); ) catch (FileNotFoundException ex) ( Console.WriteLine (ex.Message); ) if (ass) != null) CreateBinding(ass); Console.ReadLine(); ) static void CreateBinding(Assembly a) ( încercați ( Type color1 = a.GetType("FontColor"); // Folosiți obiectul de legare late obj = Activator.CreateInstance( color1); Console.WriteLine("Obiect creat!"); ) catch (Excepție ex) ( Console.WriteLine(ex.Message); ) ) ) )

Înainte de a putea rula această aplicație, trebuie să copiați manual ansamblul fontinfo.dll în subdirectorul bin\Debug din directorul acestei noi aplicații folosind Windows Explorer. Chestia este că metoda Assembly.Load() este apelată aici, ceea ce înseamnă că CLR va sonda doar folderul client (dacă doriți, puteți utiliza metoda Assembly.LoadFrom() și specifica calea completă către asamblare, dar în acest caz în acest caz nu este nevoie de acest lucru).

Acest paragraf, în ciuda conciziei sale, este foarte important - aproape toate profesionale programareîn Java se bazează pe utilizarea polimorfismului. În același timp, acest subiect este unul dintre cele mai greu de înțeles de către elevi. Prin urmare, se recomandă să recitiți cu atenție acest paragraf de mai multe ori.

Metodele de clasă sunt marcate cu modificatorul static dintr-un motiv - pentru ele, la compilarea codului programului, legătură statică. Aceasta înseamnă că în contextul cărei clase este specificat numele metodei în codul sursă, legătura este plasată către metoda acelei clase în codul compilat. Adică se realizează legarea numelui metodei la locul de apel cu cod executabil aceasta metoda. Uneori legătură statică numit legarea timpurie, deoarece are loc în etapa de compilare a programului. Legătura staticăîn Java este folosit într-un alt caz - când o clasă este declarată cu modificatorul final („final”, „final”).

Metodele obiect din Java sunt dinamice, adică sunt supuse legături dinamice. Are loc în etapa de execuție a programului direct în timpul unui apel de metodă, iar la etapa de scriere a acestei metode nu se știe dinainte din ce clasă se va efectua apelul. Aceasta este determinată de tipul de obiect pentru care funcționează acest cod - cărei clasă îi aparține obiectul, din ce clasă este numită metoda. Această legare are loc mult timp după ce codul metodei a fost compilat. Prin urmare, acest tip de legare este adesea numit legarea tardivă.

Cod de programare pe bază de apel metode dinamice, are proprietatea polimorfism– același cod funcționează diferit în funcție de ce tip de obiect îl numește, dar face aceleași lucruri la nivelul de abstractizare legat de codul sursă al metodei.

Pentru a explica aceste cuvinte, care nu sunt foarte clare la prima lectură, să luăm în considerare exemplul din paragraful anterior - munca metodei moveTo. Programatorii neexperimentați cred că această metodă ar trebui să fie înlocuită în fiecare clasă descendentă. Acest lucru se poate face de fapt și totul va funcționa corect. Dar un astfel de cod va fi extrem de redundant - la urma urmei, implementarea metodei va fi în toate clasele descendente Figura exact la fel:

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

Mai mult, acest caz nu profită de polimorfism. Deci nu vom face asta.

De asemenea, este adesea nedumerit de ce clasa abstractă Figura scrie o implementare a acestei metode. La urma urmei, apelurile la metodele de ascundere și afișare utilizate în el, la prima vedere, ar trebui să fie apeluri metode abstracte– adică par să nu poată lucra deloc!

Dar metodele hide and show sunt dinamice, ceea ce, după cum știm deja, înseamnă că asocierea numelui metodei și a codului său executabil se face în etapa de execuție a programului. Prin urmare, faptul că aceste metode sunt specificate în contextul clasei Figura, nu înseamnă că vor fi chemați din clasă Figura! Mai mult, puteți garanta că metodele hide and show nu vor fi apelate niciodată din această clasă. Să avem variabile dot1 de tip Dot și circle1 de tip Circle și li se atribuie referințe la obiecte de tipurile corespunzătoare. Să vedem cum se comportă apelurile dot1.moveTo(x1,y1) și circle1.moveTo(x2,y2).

Când apelați dot1.moveTo(x1,y1) există un apel de la clasă Figura metoda moveTo. Într-adevăr, această metodă din clasa Dot nu este suprascrisă, ceea ce înseamnă că este moștenită de la Figura. În metoda moveTo, prima instrucțiune este un apel la metoda ascunsă dinamică. Implementarea acestei metode este preluată din clasa căreia obiectul dot1 care apelează această metodă este o instanță. Adică din clasa Dot. Astfel, punctul este ascuns. Apoi coordonatele obiectului sunt modificate, după care este numit metoda dinamica spectacol. Implementarea acestei metode este preluată din clasa căreia obiectul dot1 care apelează această metodă este o instanță. Adică din clasa Dot. Astfel, un punct este afișat în noua locație.

Pentru apelarea circle1.moveTo(x2,y2) totul este absolut similar - metodele dinamice hide and show sunt apelate din clasa a cărei instanță este obiectul circle1, adică din clasa Circle. Astfel, este cercul care este ascuns în locul vechi și afișat în cel nou.

Adică, dacă obiectul este un punct, punctul se mișcă. Și dacă obiectul este un cerc, cercul se mișcă. Mai mult, dacă într-o zi cineva scrie, de exemplu, o clasă Elipse care este descendentă a lui Circle și creează un obiect Elipse elipse=elipsă nouă(…), apoi apelând ellipse.moveTo(…) va muta elipsa într-o nouă locație. Și acest lucru se va întâmpla în conformitate cu modul în care metodele hide and show sunt implementate în clasa Ellipse. Rețineți că codul polimorfic compilat al clasei va funcționa cu mult timp în urmă Figura. Polimorfismul este asigurat de faptul că referințele la aceste metode nu sunt plasate în codul metodei moveTo la momentul compilării - ele sunt configurate la metode cu astfel de nume din clasa obiectului apelant imediat în momentul apelării metodei moveTo.

Există două tipuri de limbaje de programare orientate pe obiecte: metode dinamice– de fapt dinamică şi virtual. Conform principiului de funcționare, ele sunt complet similare și diferă doar în caracteristicile de implementare. Apel metode virtuale Mai repede. Apelarea celor dinamice este mai lent, dar tabelul de servicii metode dinamice(DMT – Dynamic Methods Table) ocupă puțin mai puțină memorie decât un tabel metode virtuale(VMT – Virtual Methods Table).

Poate părea o provocare metode dinamice nu este eficientă în timp din cauza duratei de timp necesare pentru a căuta nume. De fapt, nu se face căutarea de nume în timpul apelului, dar se folosește un mecanism mult mai rapid folosind tabelul menționat de metode virtuale (dinamice). Dar nu ne vom opri asupra specificului implementării acestor tabele, deoarece în Java nu există nicio distincție între aceste tipuri de metode.

6.8. Clasa de bază Object

Clasa Object este clasa de bază pentru toate clasele Java. Prin urmare, toate câmpurile și metodele sale sunt moștenite și conținute în toate clasele. Clasa Object conține următoarele metode:

  • public Boolean equals(Object object)– returnează true în cazul în care valorile obiectului de la care este apelată metoda și ale obiectului trecut prin referința obj din lista de parametri sunt egale. Dacă obiectele nu sunt egale, se returnează false. În clasa Object, egalitatea este tratată ca egalitate de referință și este echivalentă cu operatorul de comparație „==". Dar la descendenți această metodă poate fi suprascrisă și poate compara obiecte după conținutul lor. De exemplu, acest lucru se întâmplă pentru obiectele claselor numerice shell. Acest lucru poate fi verificat cu ușurință cu un cod ca acesta:

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

    Prima linie de ieșire va da d1==d2 =false, iar a doua va da d1. equals(d2)=true

  • public int hashCode()– probleme cod hash obiect. Un cod hash este un identificator numeric unic condiționat asociat cu un element. Din motive de securitate, nu puteți da adresa obiectului unui program de aplicație. Prin urmare, în Java, un cod hash înlocuiește adresa unui obiect în cazurile în care, într-un anumit scop, este necesară stocarea tabelelor cu adresele obiectelor.
  • clona obiect protejat() aruncă CloneNotSupportedException – metoda copiază un obiect și returnează un link către clona creată (duplicată) a obiectului. În descendenții clasei Object, este necesar să o redefiniți și, de asemenea, să indicați că clasa implementează interfața Clonable. Încercarea de a apela o metodă dintr-un obiect neclonabil cauzează ridicând o excepție CloneNotSupportedException(„Clonarea nu este acceptată”). Interfețele și situațiile de excepție vor fi discutate mai târziu.

    Există două tipuri de clonare: superficială (shallow), când valorile câmpurilor obiectului original sunt copiate unu-la-unu în clonă și deep (profundă), în care sunt create noi obiecte pentru câmpurile de un tip de referință, clonând obiectele la care se referă câmpurile originale. În clonarea superficială, atât originalul, cât și clona vor face referire la aceleași obiecte. Dacă obiectul are numai câmpuri tipuri primitive, nu există nicio diferență între clonarea superficială și cea profundă. Implementarea clonării este realizată de programatorul care dezvoltă clasa; nu există un mecanism de clonare automată. Și este în etapa de dezvoltare a clasei că ar trebui să decideți ce opțiune de clonare să alegeți. În marea majoritate a cazurilor este necesar clonarea profundă.

  • clasă finală publică getClass()– returnează o referință la un metaobiect de tip clasă. Cu ajutorul acestuia, puteți obține informații despre clasa căreia îi aparține un obiect și puteți apela metodele clasei și câmpurile de clasă ale acestuia.
  • void protejat finalize() throws Throwable – numit înainte ca obiectul să fie distrus. Trebuie suprascris în acei descendenți ai Object în care este necesar să se efectueze unele acțiuni auxiliare înainte de a distruge obiectul (închiderea unui fișier, afișarea unui mesaj, desenarea ceva pe ecran etc.). Această metodă este descrisă mai detaliat în paragraful corespunzător.
  • public String toString()– returnează o reprezentare în șir a obiectului (cât mai adecvat). În clasa Object, această metodă produce un șir cu numele complet calificat al obiectului (cu numele pachetului), urmat de un caracter „@” și apoi un cod hash hexazecimal al obiectului. Majoritatea claselor standard depășesc această metodă. Pentru clasele numerice se returnează reprezentarea în șir a numărului, pentru clasele șiruri – conținutul șirului, pentru clasele de caractere – caracterul în sine (și nu reprezentarea șir a codului său!). De exemplu, următorul fragment de cod

    Object obj=new Object(); System.out.println(" obj.toString() dă "+obj.toString()); Dublu d=nou Dublu(1.0); System.out.println(" d.toString() dă "+d.toString()); Caracterul c="A"; System.out.println("c.toString() dă "+c.toString());

    va oferi o concluzie

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

Există și metode notifica (), notifyAll(), și mai multe variante supraîncărcate ale metodei aștepta, conceput pentru a lucra cu fire. Acestea sunt discutate în secțiunea despre fire.

6.9. Designeri. Cuvinte rezervate super și asta. Blocuri de inițializare

După cum sa menționat deja, obiectele în Java sunt create folosind cuvântul rezervat new, urmat de un constructor - o subrutină specială care creează un obiect și inițializează câmpurile obiectului creat. Tipul de returnare nu este specificat pentru acesta și nu este nici o metodă obiect (numită prin numele clasei când obiectul nu există încă) nici o metodă de clasă (obiectul și câmpurile sale sunt accesibile în constructor prin referința this) . De fapt, constructorul, în combinație cu operatorul nou, returnează o referință la obiectul creat și poate fi considerat un tip special de metodă care combină caracteristicile metodelor de clasă și metodelor obiect.

Dacă obiectul nu necesită nicio inițializare suplimentară atunci când este creat, puteți utiliza constructorul, care este prezent implicit pentru fiecare clasă. Acesta este numele clasei, urmat de paranteze goale - fără o listă de parametri. Nu este nevoie să specificați un astfel de constructor atunci când dezvoltați o clasă; acesta este prezent automat.

Dacă este necesară inițializarea, se folosesc de obicei constructori cu o listă de parametri. Ne-am uitat la exemple de astfel de constructori pentru clasele Dot și Circle. De la care au fost moștenite clasele Dot și Circle clase abstracte, în care nu existau constructori. Dacă există moștenire dintr-o clasă non-abstractă, adică una care are deja un constructor (chiar și un constructor implicit), apare o anumită specificitate. Prima instrucțiune dintr-un constructor trebuie să fie un apel către constructor din superclasă. Dar nu se face prin numele acestei clase, ci folosind un cuvânt rezervat super(din „superclasa”), urmată de lista de parametri necesari pentru constructorul bunic. Acest constructor inițializează câmpurile de date care sunt moștenite de la superclasă (inclusiv de la orice strămoși anterior). De exemplu, să scriem o clasă FilledCircle - un descendent al lui Circle , a cărei instanță va fi desenată ca un cerc colorat.

pachet java_gui_example; import java.awt.*; public class FilledCircle extinde Circle( /** Creează o nouă instanță a 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())( Culoare oldC=graphics.getColor(); graphics.setColor(color); graphics.setXORMode(bgColor); graphics.fillOval(x,y,size,size); grafică .setColor (oldC); graphics.setPaintMode(); ))

În general, logica creării obiectelor complexe: partea părinte a obiectului este creată și inițializată mai întâi, pornind de la partea moștenită din clasa Object și mai departe de-a lungul ierarhiei, terminând cu partea aparținând clasei însăși. Acesta este motivul pentru care, de obicei, prima declarație a unui constructor este un apel către constructorul bunic super ( lista de parametri), deoarece accesarea unei părți neinițializate a unui obiect care aparține clasei părinte poate duce la consecințe imprevizibile.

În această clasă, folosim un mod mai avansat de a desena și de a „ascunde” forme în comparație cu clasele anterioare. Se bazează pe modul de desen XOR (exclusiv sau). Acest mod este setat folosind metoda setXORMode. În acest caz, ieșirea repetată a figurii în același loc duce la restaurarea imaginii originale în zona de ieșire. Trecerea la modul normal de vopsire se realizează folosind metoda setPaintMode.

Ele sunt foarte des folosite la constructori