This shows you the differences between two versions of the page.
poo-ca-cd:laboratoare:agregare-mostenire [2023/10/22 17:49] alexandra.nioata [Exerciții] |
poo-ca-cd:laboratoare:agregare-mostenire [2024/10/20 22:48] (current) alexandra.nioata |
||
---|---|---|---|
Line 24: | Line 24: | ||
* la **definirea** obiectului (înaintea constructorului: folosind fie o valoare inițială, fie blocuri de inițializare) | * la **definirea** obiectului (înaintea constructorului: folosind fie o valoare inițială, fie blocuri de inițializare) | ||
* în cadrul **constructorului** | * în cadrul **constructorului** | ||
- | * chiar **înainte de folosire** (acest mecanism se numește inițializare leneșă (//lazy initialization//)) | + | * chiar **înainte de folosire** (acest mecanism se numește inițializare leneșă (//lazy initialization//) |
Exemple de cod: | Exemple de cod: | ||
Line 577: | Line 577: | ||
În exemplul de mai sus, la declararea unui ArrayList, se observă semnele "<>", utilizate pentru a specifica tipul de date al elementelor, pe care ArrayList le va conține. Acest lucru se numește genericitate și permite compilatorului să verifice în timpul compilării că se folosește tipul corect de date. Conceptul de genericitate se va studia mai mult în [[https://ocw.cs.pub.ro/courses/poo-ca-cd/laboratoare/genericitate|laboratorul 10]] . | În exemplul de mai sus, la declararea unui ArrayList, se observă semnele "<>", utilizate pentru a specifica tipul de date al elementelor, pe care ArrayList le va conține. Acest lucru se numește genericitate și permite compilatorului să verifice în timpul compilării că se folosește tipul corect de date. Conceptul de genericitate se va studia mai mult în [[https://ocw.cs.pub.ro/courses/poo-ca-cd/laboratoare/genericitate|laboratorul 10]] . | ||
</note> | </note> | ||
+ | |||
+ | ==== Cuvântul-cheie "final". Obiecte immutable ==== | ||
+ | |||
+ | Variabilele declarate cu atributul ''final'' pot fi inițializate **o singură dată**. Observăm că astfel unei variabile de tip referință care are atributul ''final'' îi poate fi asignată o singură valoare (variabila poate puncta către un singur obiect). O încercare nouă de asignare a unei astfel de variabile va avea ca efect generarea unei erori la compilare. | ||
+ | |||
+ | Totuși, obiectul către care punctează o astfel de variabilă poate fi modificat intern, prin apeluri de metode sau acces la câmpuri. | ||
+ | |||
+ | Exemplu: | ||
+ | |||
+ | <code java5> | ||
+ | class Student { | ||
+ | |||
+ | private final Group group; // a student cannot change the group he was assigned in | ||
+ | private static final int UNIVERSITY_CODE = 15; // declaration of an int constant | ||
+ | |||
+ | public Student(Group group) { | ||
+ | // reference initialization; any other attempt to initialize it will be an error | ||
+ | this.group = group; | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | Dacă toate atributele unui obiect admit o unică inițializare, spunem că obiectul respectiv este ''immutable'', în sensul că //nu putem schimba obiectul in sine (informația pe care o stochează, de exemplu), ci doar referința către un alt obiect.//. Exemple de astfel de obiecte sunt instanțele claselor ''String'' și ''Integer''. Odată create, prelucrările asupra lor (ex.: ''toUpperCase()'') se fac prin **instanțierea de noi obiecte** și nu prin alterarea obiectelor înseși. | ||
+ | |||
+ | Exemplu: | ||
+ | |||
+ | <code java5> | ||
+ | String s1 = "abc"; | ||
+ | |||
+ | String s2 = s1.toUpperCase(); // s1 does not change; the method returns a reference to a new object which can be accessed using s2 variable | ||
+ | s1 = s1.toUpperCase(); // s1 is now a reference to a new object | ||
+ | </code> | ||
+ | <note tip> | ||
+ | Observăm că în acest exemplu am folosit un String literal. Literalii sunt păstrați într-un ''String pool'' pentru a limita memoria utilizată. Asta înseamnă că dacă mai declarăm un alt literal "abc", nu se va mai aloca memorie pentru încă un String, ci vom primi o referință către s-ul inițial. În cazul în care folosim constructorul pentru String, se alocă memorie pentru obiectul respectiv și primim o referință nouă. | ||
+ | Pentru a evidenția concret cum funcționează acest ''String pool'', să luăm următorul exemplu: | ||
+ | </note> | ||
+ | <code java5> | ||
+ | String s1 = "a" + "bc"; | ||
+ | String s2 = "ab" + "c"; | ||
+ | </code> | ||
+ | În momentul în care compilatorul va încerca să aloce memorie pentru cele 2 obiecte, va observa că ele conțin, de fapt, aceeași informație. Prin urmare, va instanția un singur obiect, către care vor pointa ambele variabile, s1 și s2. Observați că această optimizare (de a reduce memoria) e posibilă datorită faptului că obiectele de tip String sunt **immutable**. | ||
+ | |||
+ | O întrebare legitimă este, așadar, cum putem compara două String-uri (ținând cont de faptul că avem referințele către ele, cum am arătat mai sus). Să urmărim codul de mai jos: | ||
+ | |||
+ | <code java5> | ||
+ | String a = "abc"; | ||
+ | String b = "abc"; | ||
+ | System.out.println(a == b); // True | ||
+ | |||
+ | String c = new String("abc"); | ||
+ | String d = new String("abc"); | ||
+ | System.out.println(c == d); // False | ||
+ | </code> | ||
+ | |||
+ | <note important> | ||
+ | Operatorul <nowiki>"=="</nowiki> compară //referințele//. Dacă am fi vrut să comparăm șirurile în sine am fi folosit metoda ''equals''. Același lucru este valabil și pentru oricare alt tip de referință: operatorul <nowiki>"=="</nowiki> testează egalitatea //referințelor// (i.e. dacă cei doi operanzi sunt de fapt același obiect). | ||
+ | |||
+ | |||
+ | Dacă vrem să testăm "egalitatea" a două obiecte, se apelează metoda: ''public boolean equals(Object obj)''. | ||
+ | |||
+ | Reţineţi semnătura acestei metode! | ||
+ | </note> | ||
+ | O consecință a faptului că obiectele de tip String sunt imutabile este determinată de faptul că efectuarea de modificări succesive conduce la crearea unui număr foarte mare de obiecte in String pool. | ||
+ | <code java5> | ||
+ | public static String concatenationUsingTheStringClass() { | ||
+ | String t = "Java"; | ||
+ | for (int i = 0; i < 10000; i++) { | ||
+ | t = t + "POO"; | ||
+ | } | ||
+ | return t; | ||
+ | } | ||
+ | </code> | ||
+ | În acest caz, numărul de obiecte create în memorie este unul foarte mare. Dintre acestea doar cel rezultat la final este util. Pentru a preveni alocarea nejustificată a obiectelor de tip String care reprezintă pași intermediari în obținerea șirului dorit putem alege să folosim clasa StringBuilder creată special pentru a efectua operații pe șiruri de caractere. | ||
+ | <code java5> | ||
+ | public static String concatenationUsingTheStringBuilderClass() { | ||
+ | StringBuilder sb = new StringBuilder("Java"); | ||
+ | for (int i = 0; i < 10000; i++) { | ||
+ | sb.append("POO"); | ||
+ | } | ||
+ | return sb.toString(); | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | Cuvântul cheie final poate fi folosit și în alt context decât cel prezentat anterior. De exemplu, aplicat unei clase împiedică o eventuală derivare a acestei clase prin moștenire. | ||
+ | <code java5> | ||
+ | final class ParentClass { | ||
+ | } | ||
+ | |||
+ | class ChildClass extends ParentClass { | ||
+ | // compilation error, the class ParentClass cannot be extended | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | În mod similar, în cazul în care aplicăm cuvântul cheie final unei metode, acest lucru împiedică o eventuală suprascriere a acelei metode. | ||
+ | <code java5> | ||
+ | class ParentClass { | ||
+ | public final void dontOverride() { | ||
+ | System.out.println("You cannot override this method"); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | class ChildClass extends ParentClass { | ||
+ | public void dontOverride() { // compilation error, the method dontOverride() from | ||
+ | System.out.println("But I want to!"); // the parent class cannot be overriden | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
| | ||
====Summary==== | ====Summary==== | ||
Line 607: | Line 714: | ||
<note important> | <note important> | ||
- | Veti încărca soluția voastră pe LambdaChecker, contest [[https://beta.lambdachecker.io/contest/11 | POO - LAB3]]. | + | Pentru a încărca soluția, va trebui să accesați link-ul https://code.devmind.ro/login, să intrați pe tab-ul Contests, unde veți găsi laboratorul grupei voastre. |
- | Încercați să accesați de aici contest-ul, altfel e posibil să vă ceară o parolă care nu există. De asemena, să fiți logați pe LambdaChecker înainte de accesa link-ul. m( | ||
</note> | </note> | ||
- | Schelet de laborator: | ||
- | **Task 1** [1p] | + | **Task 1 [1p]** |
Veți proiecta o clasă Form care va avea câmpul privat color (String). | Veți proiecta o clasă Form care va avea câmpul privat color (String). | ||
Line 619: | Line 724: | ||
Clasa va avea, de asemenea: | Clasa va avea, de asemenea: | ||
- | * un constructor fără parametri, care va inițializa culoarea cu "white"; | ||
- | * un constructor cu parametri; | ||
- | * o metodă de tip float getArea(), care va întoarce valoarea 0; | ||
- | * o metodă toString(): "This form has the color [color]". | ||
- | **Task 2** [2p] | + | - un constructor fără parametri, care va inițializa culoarea cu “white”; |
- | Din clasa Form derivați clasele Square, Triangle, Circle: | + | - un constructor cu parametri; |
+ | |||
+ | - o metodă de tip float getArea(), care va întoarce valoarea 0; | ||
+ | |||
+ | - o metodă toString(): “This form has the color [color]”. | ||
+ | |||
+ | |||
+ | **Task 2 [2p]** | ||
+ | |||
+ | Din clasa Form derivați clasele Square, Triangle, Circle: | ||
+ | |||
+ | - clasa Triangle (triunghi isoscel) va avea 2 membri height și base (adiacenta unghiurilor congruente) de tip float; | ||
+ | |||
+ | - clasa Circle va avea membrul radius de tip float; | ||
+ | |||
+ | - clasa Square va avea membrul side de tip float. | ||
- | * clasa Triangle va avea 2 membri height și base de tip float; | ||
- | * clasa Circle va avea membrul radius de tip float; | ||
- | * clasa Square va avea membrul side de tip float. | ||
Clasele vor avea: | Clasele vor avea: | ||
- | * constructori fără parametri; | + | - constructori fără parametri; |
- | * constructori care permit inițializarea membrilor. Identificați o modalitate de reutilizare a codului existent; | + | |
- | * suprascrieți metoda getArea() pentru a întoarce aria specifică fiecărei figuri geometrice; | + | - constructori care permit inițializarea membrilor. Identificați o modalitate de reutilizare a codului existent; |
- | * suprascrieți metoda toString() în clasele derivate, astfel încât aceasta să utilizeze implementarea metodei toString() din clasa de baza. | + | |
+ | - suprascrieți metoda getArea() pentru a întoarce aria specifică fiecărei figuri geometrice; | ||
+ | |||
+ | - suprascrieți metoda toString() în clasele derivate, astfel încât aceasta să utilizeze implementarea metodei toString() din clasa de baza. | ||
+ | |||
+ | |||
+ | **Task 3 [2p]** | ||
- | **Task 3** [2p] | ||
- | |||
Adăugați o metodă equals() în clasa Triangle. | Adăugați o metodă equals() în clasa Triangle. | ||
Justificați criteriul de echivalență ales. | Justificați criteriul de echivalență ales. | ||
- | <note important>**Hint:** | + | Hint: Puteți genera automat metoda, cu ajutorul IDE. Selectați câmpurile considerate și analizați în ce fel va fi suprascrisă metoda equals. |
- | Puteți genera automat metoda, cu ajutorul IDE. Selectați câmpurile considerate și | + | |
- | analizați în ce fel va fi suprascrisă metoda equals.</note> | + | |
- | **Task 4** - //''Upcasting''// [2p] | + | **Task 4 - Upcasting [1p]** |
+ | |||
+ | Creați un vector de obiecte Form și populați-l cu obiecte de tip Triangle, Circle și Square (upcasting). | ||
- | Creați un vector de obiecte Form și populați-l cu obiecte de tip Triangle, Circle și Square (upcasting). | ||
Parcurgeți acest vector și apelați metoda toString() pentru elementele sale. Ce observați? | Parcurgeți acest vector și apelați metoda toString() pentru elementele sale. Ce observați? | ||
- | **Task 5** - //''Downcasting''// [2p] | + | **Task 5 - Downcasting [2p]** |
+ | |||
+ | Adăugați: | ||
+ | |||
+ | - clasei Triangle metoda printTriangleDimensions, | ||
+ | |||
+ | - clasei Circle metoda printCircleDimensions | ||
+ | |||
+ | - clasei Square metoda printSquareDimensions | ||
+ | |||
+ | Implementarea metodelor constă în afișarea bazei și înălțimii, razei, respectiv laturii. | ||
+ | |||
+ | |||
+ | Parcurgeți vectorul de la exercițiul anterior și, folosind downcasting la clasa corespunzătoare, apelați metodele specifice fiecărei clase: | ||
+ | |||
+ | |||
+ | - printTriangleDimensions pentru Triangle | ||
+ | |||
+ | - printCircleDimensions pentru Circle | ||
+ | |||
+ | - printSquareDimensions pentru Square | ||
- | Adăugați clasei Triangle metoda printTriangleDimensions, clasei Circle metoda printCircleDimensions și clasei Square metoda printSquareDimensions. | ||
- | Implementarea metodelor constă în afișarea bazei si inalțimii, razei, respectiv laturii. | ||
- | Parcurgeți vectorul de la exercițiul anterior și, folosind downcasting la clasa corespunzătoare, apelați | ||
- | metodele specifice fiecărei clase (printTriangleDimensions pentru Triangle, printCircleDimensions pentru Circle și printSquareDimensions pentru Square). | ||
Pentru a stabili tipul obiectului curent folosiți operatorul instanceof. | Pentru a stabili tipul obiectului curent folosiți operatorul instanceof. | ||
- | **Task 6** - //''Agregare''// [1p] | + | **Task 6 - Agregare [1p]** |
Afișați dimensiunile formelor din vectorul creat fără a folosi operatorul instanceof. | Afișați dimensiunile formelor din vectorul creat fără a folosi operatorul instanceof. | ||
+ | |||
+ | **Task 7 - Final [1p]** | ||
+ | |||
+ | Afișați perimetrul fiecărui obiect din vectorul creat utilizând exclusiv funcția printPerimeter, modificând doar ce se afla in corpul funcției, lasând antetul identic. | ||
+ | |||
==== Resurse ==== | ==== Resurse ==== |