This shows you the differences between two versions of the page.
|
poo-ca-cd:laboratoare:abstractizare-clase-speciale-si-restrictii [2025/10/27 00:34] florian_luis.micu [Obiecte imutabile] |
poo-ca-cd:laboratoare:abstractizare-clase-speciale-si-restrictii [2025/10/27 01:22] (current) florian_luis.micu [Exerciții] |
||
|---|---|---|---|
| Line 10: | Line 10: | ||
| Aspectele urmărite sunt: | Aspectele urmărite sunt: | ||
| - | * înțelegerea conceptelor de agregare și compunere. | + | * înțelegerea abstractizării și a modului în care ascunde detaliile interne. |
| - | * utilizarea corectă a moștenirii pentru refolosirea codului. | + | * utilizarea corectă a claselor abstracte și interfețelor pentru refolosirea și organizarea codului. |
| - | * înțelegerea diferenței între moștenire și agregare. | + | * înțelegerea diferenței dintre clase normale, abstracte și interfețe. |
| - | * exersarea conversiilor upcasting și downcasting. | + | * exersarea implementării metodelor abstracte și default. |
| - | * implementarea polimorfismului în Java. | + | * înțelegerea și utilizarea polimorfismului în Java. |
| - | * diferența dintre overriding și overloading. | + | * folosirea obiectelor imutabile pentru cod sigur și clar. |
| - | * întrebuințarea cuvântului cheie super. | + | * aplicarea constantelor cu final pentru valori fixe. |
| Aspectele **bonus** urmărite sunt: | Aspectele **bonus** urmărite sunt: | ||
| - | * Problema diamantului. | + | * Problema diamantului pentru metode default în interfețe. |
| - | * Diferența dintre getClass() și instanceof. | + | * clase de tip Record. |
| - | * Organizarea memoriei pentru moștenire în Java. | + | * clase de tip Enum. |
| <note warning> | <note warning> | ||
| * În acest laborator există mai multe secțiuni marcate **[Optional]**. Aceste secțiuni cuprind informații **bonus** care vă pot fi prezentate în **timpul laboratorului** sau pe care le puteți aprofunda **în afara** acestuia, ele nefiind necesare pentru laboratoarele viitoare sau pentru teme. | * În acest laborator există mai multe secțiuni marcate **[Optional]**. Aceste secțiuni cuprind informații **bonus** care vă pot fi prezentate în **timpul laboratorului** sau pe care le puteți aprofunda **în afara** acestuia, ele nefiind necesare pentru laboratoarele viitoare sau pentru teme. | ||
| * De asemenea, veți întâlni câteva secțiuni marcate **[Nice to know]**. Vă recomandăm ca acestea să aibă **prioritate** în parcurgerea secțiunilor de tip **[Optional]**, deoarece vă pot oferi informații bonus care să fie și foarte probabil utile pentru **teme** sau **laboratoare viitoare**. | * De asemenea, veți întâlni câteva secțiuni marcate **[Nice to know]**. Vă recomandăm ca acestea să aibă **prioritate** în parcurgerea secțiunilor de tip **[Optional]**, deoarece vă pot oferi informații bonus care să fie și foarte probabil utile pentru **teme** sau **laboratoare viitoare**. | ||
| - | </note> | ||
| - | |||
| - | =====🔒 Obiecte imutabile și constante===== | ||
| - | |||
| - | ====Constante folosind keyword-ul final==== | ||
| - | |||
| - | Reamintim din laboratoarele trecute cum ''final'' poate fi folosit pentru a crea **câmpuri** sau **variabile locale constante**: | ||
| - | |||
| - | <code java> | ||
| - | public class Alien { | ||
| - | private final String name; // camp constant | ||
| - | | ||
| - | public Alien(String name) { | ||
| - | this.name = name; | ||
| - | } | ||
| - | | ||
| - | public String sayHelloToHumans() { | ||
| - | // Constante locale | ||
| - | final String evilMessage = " prepare your world to be conquered!"; | ||
| - | final String goodMessage = " we want to taste shaorma!"; | ||
| - | | ||
| - | if (name.equals("Good Boy Roy")) { | ||
| - | return "My name is " + name + goodMessage; | ||
| - | } | ||
| - | | ||
| - | return "My name is " + name + badMessage; | ||
| - | } | ||
| - | | ||
| - | public String getName() { | ||
| - | return name; | ||
| - | } | ||
| - | } | ||
| - | </code> | ||
| - | |||
| - | De asemenea, putem marca **parametrii** unei metode ca fiind ''final'' pentru a indica faptul că aceștia **nu pot fi schimbați în corpul unei metode**: | ||
| - | |||
| - | <code java> | ||
| - | public class Alien { | ||
| - | private final String name; // camp constant | ||
| - | | ||
| - | public Alien(String name) { | ||
| - | this.name = name; | ||
| - | } | ||
| - | | ||
| - | public String sayHelloToHumans() { | ||
| - | // Constante locale | ||
| - | final String evilMessage = " prepare your world to be conquered!"; | ||
| - | final String goodMessage = " we want to taste shaorma!"; | ||
| - | | ||
| - | if (name.equals("Good Boy Roy")) { | ||
| - | return "My name is " + name + goodMessage; | ||
| - | } | ||
| - | | ||
| - | return "My name is " + name + badMessage; | ||
| - | } | ||
| - | | ||
| - | public void shootRayGun(final Human human) { | ||
| - | human.decreaseHealthBy(70); | ||
| - | System.out.println("Resistence is futile!"); | ||
| - | | ||
| - | human = new Human(); // eroare, human este final | ||
| - | human.job = "plumber"; // se poate, final se refera doar la referinta obiectului | ||
| - | } | ||
| - | | ||
| - | public String getName() { | ||
| - | return name; | ||
| - | } | ||
| - | } | ||
| - | </code> | ||
| - | |||
| - | ====Obiecte imutabile==== | ||
| - | |||
| - | Un obiect **imutabil** este un obiect a cărui stare internă **nu poate fi modificată** după ce a fost creat. Cu alte cuvinte, toate câmpurile sale sunt constante după inițializare, iar orice operație aparentă de "modificare" va genera de fapt **un nou obiect**. | ||
| - | |||
| - | ===Caracteristici ale Obiectelor Imutabile=== | ||
| - | |||
| - | Pentru ca o clasă să fie considerată imutabilă, trebuie să respecte următoarele reguli: | ||
| - | * Toate câmpurile sale trebuie să fie **private** și **finale**. | ||
| - | * Clasa trebuie declarată **final** pentru a preveni moștenirea. | ||
| - | * Nu trebuie să existe **setters** sau alte metode care modifică starea internă. | ||
| - | * Obiectele returnate de metode trebuie să fie **copii**, nu referințe directe către câmpurile interne (pentru a evita modificarea indirectă). | ||
| - | |||
| - | ===Avantaje=== | ||
| - | |||
| - | * **Siguranță în programe paralelizabile**: Obiectele imutabile pot fi partajate între thread-uri fără sincronizare suplimentară. | ||
| - | * **Simplitate**: Nu este nevoie de protecție împotriva modificărilor accidentale. | ||
| - | * **Fiabilitate**: Pot fi folosite ca chei în colecții (HashMap, HashSet) fără riscul ca starea lor să se schimbe. | ||
| - | * **Cache și reutilizare**: Obiectele pot fi memorate în cache fără teama că valorile se vor modifica ulterior. | ||
| - | |||
| - | ===Dezavantaje=== | ||
| - | |||
| - | * **Consum mai mare de memorie**: Crearea de obiecte noi la fiecare modificare poate fi costisitoare. | ||
| - | * **Performanță scăzută** în cazurile în care modificările frecvente sunt inevitabile. | ||
| - | |||
| - | ===Crearea unei clase imutabile=== | ||
| - | |||
| - | Exemplu de clasă imutabilă: | ||
| - | |||
| - | <code java Adress.java> | ||
| - | public final class Adress { | ||
| - | private final String street; | ||
| - | private final String city; | ||
| - | private final int zipCode; | ||
| - | |||
| - | public Adresa(String street, String city, int zipCode) { | ||
| - | this.street = street; | ||
| - | this.city = city; | ||
| - | this.zipCode = zipCode; | ||
| - | } | ||
| - | |||
| - | public String getStreet() { return street; } | ||
| - | public String getCity() { return city; } | ||
| - | public int getZipCode() { return zipCode; } | ||
| - | |||
| - | @Override | ||
| - | public String toString() { | ||
| - | return street + ", " + city + " (" + zipCode + ")"; | ||
| - | } | ||
| - | } | ||
| - | </code> | ||
| - | |||
| - | <code java Main.java> | ||
| - | public class Main { | ||
| - | public static void main(String[] args) { | ||
| - | Adresa adresa1 = new Adresa("Str. Florilor", "București", 12345); | ||
| - | Adresa adresa2 = new Adresa("Str. Florilor", "București", 12345); | ||
| - | |||
| - | // ambele obiecte sunt independente și nu pot fi modificate | ||
| - | System.out.println(adresa1); // Str. Florilor, București (12345) | ||
| - | } | ||
| - | } | ||
| - | </code> | ||
| - | |||
| - | ===Exemple de clase imutabile din Java=== | ||
| - | |||
| - | Java oferă numeroase exemple de clase imutabile, printre care: | ||
| - | * ''String'' | ||
| - | * Wrapper-urile pentru tipurile primitive (''Integer'', ''Double'', ''Boolean'' etc.). | ||
| - | * ''LocalDate'', ''LocalTime'', ''LocalDateTime'' (din pachetul ''java.time''). | ||
| - | |||
| - | <note tip> | ||
| - | Vom vorbi mai multe despre imutabilitate în contextul folosirii obiectelor de tip ''String'' și wrappers (''Integer'', ''Double'' etc.) în următoarele laboratoare. | ||
| </note> | </note> | ||
| Line 174: | Line 32: | ||
| Abstractizarea este unul dintre cele 4 principii POO de bază (**Abstractizare, Încapsulare, Moștenire, Polimorfism**). | Abstractizarea este unul dintre cele 4 principii POO de bază (**Abstractizare, Încapsulare, Moștenire, Polimorfism**). | ||
| - | Acest principiu este folosit pentru a ascunde detaliile interne ale obiectelor și evidențiază doar comportamentele esențiale. Ea oferă un șablon comun pentru o categorie de obiecte, fără a specifica implementarea fiecăruia. În Java, abstractizarea este realizată prin clase abstracte și interfețe. | + | Acest principiu este folosit pentru a ascunde detaliile interne ale obiectelor și evidențiază doar comportamentele esențiale. Ea oferă un **șablon comun** pentru o categorie de obiecte, fără a specifica implementarea fiecăruia. În Java, abstractizarea este realizată prin **clase abstracte** și **interfețe**. |
| ====Clase și metode abstracte==== | ====Clase și metode abstracte==== | ||
| - | Programarea orientată pe obiecte permite modelarea unor concepte generale care pot avea comportamente variate în funcție de implementare. Clasele și metodele abstracte oferă un mecanism esențial pentru proiectarea ierarhiilor de clase flexibile și extensibile. | + | Programarea orientată pe obiecte permite modelarea unor concepte generale care pot avea comportamente variate în funcție de implementare. Clasele și metodele abstracte oferă un mecanism esențial pentru proiectarea ierarhiilor de clase **flexibile** și **extensibile**. |
| ===Ce este o metodă abstractă?=== | ===Ce este o metodă abstractă?=== | ||
| Line 226: | Line 84: | ||
| <note important> | <note important> | ||
| - | Este important să înțelegem că dacă avem cel puțion o metodă abstractă, trebuie să declarăm și clasa acesteia ca fiind abstractă. | + | Este important să înțelegem că dacă avem **cel puțin o metodă abstractă**, trebuie să declarăm și clasa acesteia ca fiind abstractă. |
| </note> | </note> | ||
| Line 237: | Line 95: | ||
| O clasă abstractă, **nu poate fi instanțiată direct**, trebuie moștenită de o altă clasă folosind keyword-ul ''extends''. | O clasă abstractă, **nu poate fi instanțiată direct**, trebuie moștenită de o altă clasă folosind keyword-ul ''extends''. | ||
| - | Acest mecanism funcționează și pe lanțuri de moștenire între clase abstracte: o clasă abstractă poate moșteni o altă clasă abstractă și poate adăuga metode abstracte noi. Toate metodele abstracte din lanț trebuie să fie implementate în cele din urmă într-o clasă concretă pentru a putea crea obiecte. | + | Acest mecanism funcționează și pe **lanțuri de moștenire** între clase abstracte: o clasă abstractă poate moșteni o altă clasă abstractă și poate adăuga metode abstracte noi. Toate metodele abstracte din lanț **trebuie să fie implementate** în cele din urmă într-o clasă concretă pentru a putea crea obiecte. |
| <code java Vehicle.java> | <code java Vehicle.java> | ||
| Line 296: | Line 154: | ||
| <note important> | <note important> | ||
| - | * Clasa ''Vehicle'' și clasa ''Car'' nu pot fi instanțiate, deoarece nu implementează toate metodele abstracte moștenite și proprii. | + | * Clasa ''Vehicle'' și clasa ''Car'' nu pot fi instanțiate, deoarece **nu implementează** toate metodele abstracte moștenite și proprii. |
| - | * Dacă dorim să nu se poată instanția clasa ''Animal'' de mai sus, am putea să o declarăm ca fiind abstractă chiar dacă aceasta nu ar avea metode abstracte:<code java> | + | * Dacă dorim să nu se poată instanția clasa ''Animal'' de mai sus, am putea să o declarăm ca fiind abstractă chiar dacă aceasta **nu** ar avea metode abstracte:<code java> |
| public abstract class Animal { // valid | public abstract class Animal { // valid | ||
| private double weight; | private double weight; | ||
| Line 313: | Line 171: | ||
| <note tip> | <note tip> | ||
| - | * O clasă abstractă nu poate fi instanțiată, deoarece o clasă abstractă poate conține metode abstracte care nu au încă o implementare. Chiar și dacă clasa nu conține metode abstracte această restricție este păstrată pentru a putea exista scenariul de mai sus. | + | * O clasă abstractă nu poate fi instanțiată, deoarece o clasă abstractă **poate** conține metode abstracte care nu au încă o implementare. Chiar și dacă clasa **nu** conține metode abstracte această restricție este păstrată pentru a putea exista scenariul de mai sus. |
| - | * O clasă abstractă poate avea constructori chiar dacă nu poate fi instanțiată direct, deoarece acei constructori pot fi folosiți la inițializarea stării interne a clasei, ca în exemplul de mai sus. | + | * O clasă abstractă poate avea constructori chiar dacă nu poate fi instanțiată direct, deoarece acei constructori pot fi folosiți la **inițializarea stării interne** a clasei, ca în exemplul de mai sus. |
| </note> | </note> | ||
| Line 339: | Line 197: | ||
| ====Interfețe==== | ====Interfețe==== | ||
| - | Interfețele oferă un mecanism prin care putem defini **comportamente** fără a impune moștenire de implementare. Ele stabilesc un contract pe care orice clasă îl poate îndeplini, indiferent din ce parte a ierarhiei de clase provine. | + | Interfețele oferă un mecanism prin care putem defini **comportamente** fără a forța clasele să moștenească implementarea. Ele stabilesc un contract pe care orice clasă îl poate îndeplini, indiferent din ce parte a ierarhiei de clase provine. |
| ===Ce este o interfață?=== | ===Ce este o interfață?=== | ||
| Line 413: | Line 271: | ||
| ===Iniţializarea câmpurilor în interfeţe=== | ===Iniţializarea câmpurilor în interfeţe=== | ||
| - | În interfețe toate câmpurile sunt implicit ''public static final''. Nu pot exista **blank final**s (câmpuri finale neinițializate), dar pot exista constante non-primitive dacă sunt inițializate la declarație. | + | În interfețe toate câmpurile sunt implicit ''public static final''. Nu pot exista **blank finals** (câmpuri finale neinițializate), dar pot exista **constante non-primitive** dacă sunt inițializate la declarație. |
| <code java> | <code java> | ||
| Line 423: | Line 281: | ||
| ===Interfețele ca tipuri=== | ===Interfețele ca tipuri=== | ||
| - | După ce definim o interfață, ea devine un tip de referință în Java, la fel ca o clasă. Asta înseamnă că putem: | + | După ce definim o interfață, ea devine un **tip de referință** în Java, la fel ca o clasă. Asta înseamnă că putem: |
| * Declara variabile de tipul interfeței | * Declara variabile de tipul interfeței | ||
| * Folosi interfața ca tip pentru parametrii unor metode | * Folosi interfața ca tip pentru parametrii unor metode | ||
| * Specifica interfața ca tip de return al unei metode | * Specifica interfața ca tip de return al unei metode | ||
| - | Astfel, orice obiect care implementează interfața poate fi atribuit unei variabile de acel tip, indiferent de clasa sa concretă. | + | Astfel, orice obiect care implementează interfața poate fi atribuit unei variabile de acel tip, indiferent de clasa sa concretă (upcasting). |
| <code java> | <code java> | ||
| Line 476: | Line 334: | ||
| ===Moștenire multiplă=== | ===Moștenire multiplă=== | ||
| - | În Java, o interfață poate moșteni mai multe interfețe folosind keyword-ul extends. Aceasta permite combinarea comportamentelor din mai multe surse fără a fi nevoie de moștenire multiplă de clase (care nu este permisă în Java). | + | În Java, **o interfață** poate moșteni **mai multe interfețe** folosind keyword-ul ''extends''. Acesta permite combinarea comportamentelor din mai multe surse fără a fi nevoie de moștenire multiplă de clase (care nu este permisă în Java). |
| <code java> | <code java> | ||
| Line 499: | Line 357: | ||
| </code> | </code> | ||
| - | Totodată, o clasă poate moșteni mai multe interfețe folosind keyword-ul ''implements'': | + | Totodată, **o clasă** poate moșteni **mai multe interfețe** folosind keyword-ul ''implements'': |
| <code java> | <code java> | ||
| Line 554: | Line 412: | ||
| </code> | </code> | ||
| - | <note tip>Situația de mai sus **nu** reprezintă //Problema diamantului//, deoarece chiar dacă se pot moșteni mai multe interfețe, acestea nu au un corp, deci implementarea este lăsată la latitudinea clasei care le va implementa.</note> | + | <note tip>Situația de mai sus **nu** se încadrează la //Problema diamantului//, deoarece chiar dacă se pot moșteni mai multe interfețe, acestea nu au un corp, deci implementarea este lăsată la latitudinea clasei care le va implementa.</note> |
| + | |||
| + | <spoiler [Optional] Metode default în interfețe și Problema diamantului> | ||
| + | **Metode default în interfețe** | ||
| + | |||
| + | În Java (de la Java 8), interfețele pot avea **metode cu implementare** folosind cuvântul cheie ''default''. | ||
| + | |||
| + | Scopul lor: | ||
| + | * să permită adăugarea de noi metode în interfețe fără să strice compatibilitatea cu clasele care le folosesc; | ||
| + | * să ofere o implementare implicită, dar care poate fi suprascrisă de clasele care implementează interfața. | ||
| + | |||
| + | Exemplu: | ||
| + | <code java> | ||
| + | interface Animal { | ||
| + | default void eat() { | ||
| + | System.out.println("Animal is eating"); | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | **//Problema diamantului// în interfețe** | ||
| + | |||
| + | Apare această problemă când o clasă implementează două interfețe care au **aceeași metodă default cu aceeași semnătură**. | ||
| + | |||
| + | <code java> | ||
| + | interface A { | ||
| + | default void hello() { System.out.println("Hello from A"); } | ||
| + | } | ||
| + | |||
| + | interface B { | ||
| + | default void hello() { System.out.println("Hello from B"); } | ||
| + | } | ||
| + | |||
| + | class C implements A, B { | ||
| + | // EROARE: conflict, Java nu știe ce versiune de hello() să folosească | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | Java nu știe dacă să o folosească pe cea din A sau B, ceea ce rezultă într-un **conflict**. | ||
| + | |||
| + | **Soluția pentru //Problema diamantului// în Java** | ||
| + | |||
| + | Clasa care implementează interfețele trebuie să rezolve conflictul **suprascriind** metoda: | ||
| + | |||
| + | <code java> | ||
| + | class C implements A, B { | ||
| + | @Override | ||
| + | public void hello() { | ||
| + | A.super.hello(); // sau B.super.hello(); | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| + | </spoiler> | ||
| ===De ce folosim interfețe?=== | ===De ce folosim interfețe?=== | ||
| Line 569: | Line 479: | ||
| ====Clase abstracte vs. Interfețe==== | ====Clase abstracte vs. Interfețe==== | ||
| - | După cum se poate observa, atât clasele abstracte cât și interfețele oferă avantaje similare. Pentru a observa asemănările și diferențele dintre acestea am creat următorul tabel: | + | După cum se poate observa, atât clasele abstracte cât și interfețele oferă avantaje similare. Să observăm următorul tabel pentru o comparație directă: |
| ^ Caracteristică ^ Clasă abstractă ^ Interfață ^ | ^ Caracteristică ^ Clasă abstractă ^ Interfață ^ | ||
| Line 584: | Line 494: | ||
| ====Exemplu ierarhie de clase==== | ====Exemplu ierarhie de clase==== | ||
| - | Proiectele voastre ar trebui să conțină o combinație între clase abstracte și interfețe. | + | Un proiect complex ar trebui să conțină o **combinație** între clase abstracte și interfețe. |
| - | Să presupunem următoarea ierarhie de clase. | + | Să presupunem următoarea ierarhie de clase: |
| {{:poo-ca-cd:laboratoare:abstractizare-clase-speciale-si-restrictii:inheritance_example.png?nolink&600|}} | {{:poo-ca-cd:laboratoare:abstractizare-clase-speciale-si-restrictii:inheritance_example.png?nolink&600|}} | ||
| Line 596: | Line 506: | ||
| - Interfețele (''Domesticable'', ''Guardable'', ''Flyable'') definesc capabilități suplimentare, fără a impune moștenirea ierarhiei. | - Interfețele (''Domesticable'', ''Guardable'', ''Flyable'') definesc capabilități suplimentare, fără a impune moștenirea ierarhiei. | ||
| - Clasele pot implementa mai multe interfețe pentru a combina comportamente, exemplu: ''Dog'' poate fi ''Domesticable'' și ''Guardable''. | - Clasele pot implementa mai multe interfețe pentru a combina comportamente, exemplu: ''Dog'' poate fi ''Domesticable'' și ''Guardable''. | ||
| + | |||
| + | |||
| + | =====🔒 Obiecte imutabile și constante===== | ||
| + | |||
| + | ====Constante folosind keyword-ul final==== | ||
| + | |||
| + | Reamintim din laboratoarele trecute cum ''final'' poate fi folosit pentru a crea **câmpuri** sau **variabile locale constante**: | ||
| + | |||
| + | <code java> | ||
| + | public class Alien { | ||
| + | private final String name; // camp constant | ||
| + | | ||
| + | public Alien(String name) { | ||
| + | this.name = name; | ||
| + | } | ||
| + | | ||
| + | public String sayHelloToHumans() { | ||
| + | // Constante locale | ||
| + | final String evilMessage = " prepare your world to be conquered!"; | ||
| + | final String goodMessage = " we want to taste shaorma!"; | ||
| + | | ||
| + | if (name.equals("Good Boy Roy")) { | ||
| + | return "My name is " + name + goodMessage; | ||
| + | } | ||
| + | | ||
| + | return "My name is " + name + badMessage; | ||
| + | } | ||
| + | | ||
| + | public String getName() { | ||
| + | return name; | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | De asemenea, putem marca **parametrii** unei metode ca fiind ''final'' pentru a indica faptul că aceștia **nu pot fi schimbați în corpul unei metode**: | ||
| + | |||
| + | <code java> | ||
| + | public class Alien { | ||
| + | private final String name; // camp constant | ||
| + | | ||
| + | public Alien(String name) { | ||
| + | this.name = name; | ||
| + | } | ||
| + | | ||
| + | public String sayHelloToHumans() { | ||
| + | // Constante locale | ||
| + | final String evilMessage = " prepare your world to be conquered!"; | ||
| + | final String goodMessage = " we want to taste shaorma!"; | ||
| + | | ||
| + | if (name.equals("Good Boy Roy")) { | ||
| + | return "My name is " + name + goodMessage; | ||
| + | } | ||
| + | | ||
| + | return "My name is " + name + badMessage; | ||
| + | } | ||
| + | | ||
| + | public void shootRayGun(final Human human) { | ||
| + | human.decreaseHealthBy(70); | ||
| + | System.out.println("Resistence is futile!"); | ||
| + | | ||
| + | human = new Human(); // eroare, human este final | ||
| + | human.job = "plumber"; // se poate, final se refera doar la referinta obiectului | ||
| + | } | ||
| + | | ||
| + | public String getName() { | ||
| + | return name; | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | ====Obiecte imutabile==== | ||
| + | |||
| + | Un obiect **imutabil** este un obiect a cărui stare internă **nu poate fi modificată** după ce a fost creat. Cu alte cuvinte, toate câmpurile sale sunt constante după inițializare, iar orice operație aparentă de "modificare" va genera de fapt **un nou obiect**. | ||
| + | |||
| + | ===Caracteristici ale Obiectelor Imutabile=== | ||
| + | |||
| + | Pentru ca o clasă să fie considerată imutabilă, trebuie să respecte următoarele reguli: | ||
| + | * Toate câmpurile sale trebuie să fie **private** și **finale**. | ||
| + | * Clasa trebuie declarată **final** pentru a preveni moștenirea. | ||
| + | * Nu trebuie să existe **setters** sau alte metode care modifică starea internă. | ||
| + | * Obiectele returnate de metode trebuie să fie **copii**, nu referințe directe către câmpurile interne (pentru a evita modificarea indirectă). | ||
| + | |||
| + | ===Avantaje=== | ||
| + | |||
| + | * **Siguranță în programe paralelizabile**: Obiectele imutabile pot fi partajate între thread-uri fără sincronizare suplimentară. | ||
| + | * **Simplitate**: Nu este nevoie de protecție împotriva modificărilor accidentale. | ||
| + | * **Fiabilitate**: Pot fi folosite ca chei în colecții (HashMap, HashSet) fără riscul ca starea lor să se schimbe. | ||
| + | * **Cache și reutilizare**: Obiectele pot fi memorate în cache fără teama că valorile se vor modifica ulterior. | ||
| + | |||
| + | ===Dezavantaje=== | ||
| + | |||
| + | * **Consum mai mare de memorie**: Crearea de obiecte noi la fiecare modificare poate fi costisitoare. | ||
| + | * **Performanță scăzută** în cazurile în care modificările frecvente sunt inevitabile. | ||
| + | |||
| + | ===Crearea unei clase imutabile=== | ||
| + | |||
| + | Exemplu de clasă imutabilă: | ||
| + | |||
| + | <code java Adress.java> | ||
| + | public final class Adress { | ||
| + | private final String street; | ||
| + | private final String city; | ||
| + | private final int zipCode; | ||
| + | |||
| + | public Adresa(String street, String city, int zipCode) { | ||
| + | this.street = street; | ||
| + | this.city = city; | ||
| + | this.zipCode = zipCode; | ||
| + | } | ||
| + | |||
| + | public String getStreet() { return street; } | ||
| + | public String getCity() { return city; } | ||
| + | public int getZipCode() { return zipCode; } | ||
| + | |||
| + | @Override | ||
| + | public String toString() { | ||
| + | return street + ", " + city + " (" + zipCode + ")"; | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | <code java Main.java> | ||
| + | public class Main { | ||
| + | public static void main(String[] args) { | ||
| + | Adresa adresa1 = new Adresa("Str. Florilor", "București", 12345); | ||
| + | Adresa adresa2 = new Adresa("Str. Florilor", "București", 12345); | ||
| + | |||
| + | // ambele obiecte sunt independente și nu pot fi modificate | ||
| + | System.out.println(adresa1); // Str. Florilor, București (12345) | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | ===Exemple de clase imutabile din Java=== | ||
| + | |||
| + | Java oferă numeroase exemple de clase imutabile, printre care: | ||
| + | * ''String'' | ||
| + | * Wrapper-urile pentru tipurile primitive (''Integer'', ''Double'', ''Boolean'' etc.). | ||
| + | * ''LocalDate'', ''LocalTime'', ''LocalDateTime'' (din pachetul ''java.time''). | ||
| + | |||
| + | <note tip> | ||
| + | Vom vorbi mai multe despre imutabilitate în contextul folosirii obiectelor de tip ''String'' și wrappers (''Integer'', ''Double'' etc.) în următoarele laboratoare. | ||
| + | </note> | ||
| + | |||
| =====[Nice to know] 🎛️ Clase speciale===== | =====[Nice to know] 🎛️ Clase speciale===== | ||
| ====Enums==== | ====Enums==== | ||
| - | Enums sunt tipuri speciale de clasă care definesc un set fix de constante. Ele oferă o modalitate sigură și lizibilă de a reprezenta valori finite și constante într-un program. | + | Enums sunt tipuri speciale de clasă care definesc un **set fix de constante**. Ele oferă o modalitate **sigură** și **lizibilă** de a reprezenta **valori finite** și **constante** într-un program. |
| Exemplu simplu: | Exemplu simplu: | ||
| Line 708: | Line 762: | ||
| ===Avantaje ale folosirii Enum=== | ===Avantaje ale folosirii Enum=== | ||
| - | * Cod mai clar și mai lizibil comparativ cu constantele int sau String. | + | * Cod mai clar și mai lizibil comparativ cu constantele de tip ''int'' sau ''String''. |
| - | * Siguranță la compilare: nu poți atribui valori invalide. | + | * Siguranță la compilare, deoarece nu putem atribui valori invalide. |
| - | * Poți adăuga metode și comportamente specifice fiecărei constante. | + | * Putem adăuga metode și comportamente specifice fiecărei constante. |
| * Se integrează bine cu switch/case pentru decizii pe valori finite. | * Se integrează bine cu switch/case pentru decizii pe valori finite. | ||
| <note tip> | <note tip> | ||
| - | Puteți folosi în continuare câmpuri de tipul ''static final'' într-o clasă normală care are rol de a ține constante, însă este de preferat să folosiți Enum-uri fiind o alternativă mai modernă. | + | Puteți folosi în continuare câmpuri de tipul ''static final'' într-o clasă normală care are rol de a ține constante, însă este de **preferat** să folosiți Enum-uri fiind o alternativă mai modernă cu toate avantajele de mai sus. |
| </note> | </note> | ||
| Line 838: | Line 892: | ||
| * Veți învăța despre **Spring** când veți face web back-end development. | * Veți învăța despre **Spring** când veți face web back-end development. | ||
| </note> | </note> | ||
| + | |||
| + | =====Summary===== | ||
| + | **Abstractizare** | ||
| + | * Permite ascunderea detaliilor interne și expunerea doar a comportamentului esențial. | ||
| + | * Realizată prin **clase abstracte** și **interfețe**. | ||
| + | * Favorizează **polimorfismul** și **reutilizarea codului**. | ||
| + | |||
| + | **Clase abstracte** | ||
| + | * Pot conține metode **abstracte** și metode cu implementare. | ||
| + | * Pot avea **câmpuri și constructori**. | ||
| + | * **Nu pot fi instanțiate** direct. | ||
| + | * Forțează subclasele să implementeze metodele abstracte. | ||
| + | * Permite moștenire între clase abstracte. | ||
| + | |||
| + | **Interfețe** | ||
| + | * Definește un **contract** de comportamente (metode abstracte). | ||
| + | * Poate conține metode **default** (cu implementare) și **static**. | ||
| + | * Permite **moștenire multiplă** fără conflicte de ierarhie (exceptând problema diamantului). | ||
| + | * Polimorfism: o variabilă de tip interfață poate referi orice clasă care o implementează. | ||
| + | * Nu poate avea constructori. | ||
| + | * Toate câmpurile sunt **implicit public static final**. | ||
| + | |||
| + | **Problema diamantului (în interfețe)** | ||
| + | * Apare când o clasă implementează **două interfețe** cu aceeași metodă default. | ||
| + | * Soluția: clasa trebuie să **suprascrie metoda** și să aleagă implementarea. | ||
| + | |||
| + | **Constante (final)** | ||
| + | * Variabilele final nu pot fi modificate după inițializare. | ||
| + | * Folosite pentru **constante globale**, **valoare fixă în clase** sau **enum-uri**. | ||
| + | * În interfețe, toate câmpurile sunt **implicit public static final**. | ||
| + | |||
| + | **Obiecte imutabile** | ||
| + | * Obiectele nu își pot schimba starea după creare. | ||
| + | * Se obțin prin câmpuri private final și **fără setteri**. | ||
| + | * Oferă **siguranță în programarea concurentă**. | ||
| + | |||
| + | **Enums** | ||
| + | * Tip special care reprezintă un set fix de constante. | ||
| + | * Fiecare constantă este de fapt un **obiect de tip enum**. | ||
| + | * Pot avea **câmpuri, constructori și metode**. | ||
| + | * Metode utile: .values(), .ordinal(), .name(). | ||
| + | |||
| + | **Records** | ||
| + | * Introduse în Java 16, pentru **tipuri de date imutabile**. | ||
| + | * Au câmpuri finale, constructor generat automat, equals(), hashCode() și toString(). | ||
| + | * Perfect pentru **DTO-uri** sau obiecte care transportă date fără logică suplimentară. | ||
| + | |||
| + | |||
| + | |||
| + | =====Exerciții===== | ||
| + | |||
| + | <note warning> | ||
| + | * Exercițiile vor fi făcute pe platforma [[https://code.devmind.ro/|Devmind Code]]. Găsiți exercițiile din acest laborator în **contestul aferent**. | ||
| + | * Vă recomandăm să copiați scheletul și să faceți exercițiile **mai întâi** în IntelliJ, deoarece acolo aveți acces la o serie de **instrumente** specifice unui IDE. După ce ați terminat exercițiile puteți să le **copiați** pe Devmind Code. | ||
| + | </note> | ||
| + | |||
| + | <note note> | ||
| + | În contest veți observa că problema se cheamă //Problem-LAB05-POO//, însă nu este o problemă. Vom redenumi numele problemei cât de curând. | ||
| + | </note> | ||
| + | |||
| + | ====Task 1 (2p)==== | ||
| + | |||
| + | Implementaţi interfaţa ''Task'' în cele 3 moduri de mai jos: | ||
| + | * Un task (''OutTask.java'') care să afișeze un mesaj la output. Mesajul este specificat în contructorul clasei. | ||
| + | * Un task (''RandomOutTask.java'') care genereaza un număr aleator de tip ''int'' și afișeaza un mesaj cu numărul generat la output. Generarea se va realiza în constructor utilizandu-se o instanță globală a unui obiect de tip [[https://docs.oracle.com/javase/8/docs/api/java/util/Random.html#Random-long-|Random]] care a fost inițializat cu seed-ul **12345**. | ||
| + | * Un task (''CounterOutTask.java'') care incrementeaza un contor global și afișează valoarea contorului după fiecare incrementare. | ||
| + | |||
| + | <note>**Notă**: Acesta este un exemplu simplu pentru Design Pattern-ul [[http://en.wikipedia.org/wiki/Command_pattern|Command Pattern]], despre care vom învăța în următoarele laboratoare.</note> | ||
| + | |||
| + | {{:poo-ca-cd:laboratoare:clase-abstracte-interfete:ex1.png?600|}} | ||
| + | |||
| + | ====Task 2 (3p)==== | ||
| + | Interfaţa ''Container'' specifică interfaţa comună pentru colecţii de obiecte Task, în care se pot adăuga și din care se pot elimina elemente. | ||
| + | |||
| + | Interfața conține metodele: | ||
| + | * ''pop():Task'' | ||
| + | * ''push(Task):void'' | ||
| + | * ''size():int'' | ||
| + | * ''isEmpty():boolean'' | ||
| + | * ''transferFrom(Container):void'' | ||
| + | |||
| + | Creaţi două tipuri de containere care implementează această clasă: | ||
| + | * ''Stack'' - care implementează o strategie de tip [[https://en.wikipedia.org/wiki/Stack_(abstract_data_type)|LIFO]] | ||
| + | * ''Queue'' - care implementează o strategie de tip [[https://en.wikipedia.org/wiki/Queue_(abstract_data_type)|FIFO]] | ||
| + | |||
| + | **Bonus (nepunctat)**: Incercați să evitaţi codul similar în locuri diferite! | ||
| + | |||
| + | {{:poo-ca-cd:laboratoare:clase-abstracte-interfete:ex2.png?600|}} | ||
| + | |||
| + | ====Task 3 (2p)==== | ||
| + | Creați 4 interfețe: ''Minus'', ''Plus'', ''Mult'', ''Div'' care conțin câte o metodă aferentă numelui ce are ca argument un număr de tipul float. | ||
| + | |||
| + | Spre exemplu interfața ''Minus'' va declara metoda: | ||
| + | <code java> | ||
| + | void minus(float value); | ||
| + | </code> | ||
| + | Scrieți clasa ''Operation'' care să implementeze toate aceste interfețe. Pe lânga metodele interfețelor implementate aceasta va conține: | ||
| + | * un câmp ''number'' de tipul ''float'' asupra căruia se vor efectua operațiile implementate | ||
| + | * o metodata getter ''getNumber'' care va returna valoarea câmpului ''value'' | ||
| + | * un constructor care primește ca parametru valoarea inițială pentru campul ''value'' | ||
| + | |||
| + | Pentru cazul în care metoda div este apelată cu valoare 0 operația nu va fi efectuată și se va afișa mesajul "Division by 0 is not possible". | ||
| + | |||
| + | ====Task 4 (3p)==== | ||
| + | Implementaţi clasa ''Song'' și clasa abstracta ''Album''. | ||
| + | |||
| + | Song: | ||
| + | * stochează atributele ''name'' de tip ''String''. ''id'' de tip ''int'' si ''composer'' de tip ''String'' | ||
| + | * are un constructor care va inițializa atributele specificate anterior | ||
| + | * implementează metodele de tip getter și setter pentru fiecare atribut | ||
| + | * suprascrie metoda ''toString()'' care va returna un ''String'' de forma "Song{name=''name'', id=''id'', composer=''composer''}" | ||
| + | |||
| + | Album: | ||
| + | * stochează o listă de cântece | ||
| + | * declară metoda abstractă numită addSong care primește un Song și nu returnează nimic | ||
| + | * implementează metoda removeSong care primește un song și nu returnează nimic | ||
| + | * suprascrie metoda ''toString()'' care va returna un ''String'' de forma "Album{songs=[Song, Song, ...]}" | ||
| + | |||
| + | După implementarea claselor ''Song'' și ''Album'', creați clasele ''DangerousAlbum'', ''ThrillerAlbum'' și ''BadAlbum'', care vor moșteni clasa ''Album'' și vor implementa metoda ''addSong'' după următoarele reguli: | ||
| + | * ''DangerousAlbum'' conține doar melodii cu id-ul număr prim | ||
| + | * ''ThrillerAlbum'' conține melodii scrise doar de ''Michael Jackson'' și au id-ul număr par | ||
| + | * ''BadAlbum'' conține doar melodii care au nume de 3 litere și id număr palindrom | ||
| + | |||
| + | În cazul în care criteriul de adăugare specific unui album nu este respectat, melodia nu va fi adaugată în acesta. | ||
| + | |||
| + | ===== Resurse și link-uri utile ===== | ||
| + | * [[https://docs.oracle.com/javase/tutorial/java/IandI/abstract.html|Abstract methods and classes]] | ||
| + | * [[https://docs.oracle.com/javase/tutorial/java/concepts/interface.html|Interfaces]] | ||
| + | * [[https://docs.oracle.com/en/java/javase/25/language/records.html|Records]] | ||
| + | * [[https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html|Enums]] | ||
| + | |||