This shows you the differences between two versions of the page.
|
poo-ca-cd:laboratoare:genericitate-si-tipuri-parametrizate [2025/12/08 09:04] florian_luis.micu [Tipuri generice cu limitări] |
poo-ca-cd:laboratoare:genericitate-si-tipuri-parametrizate [2025/12/10 01:06] (current) florian_luis.micu [Wildcard Capture] |
||
|---|---|---|---|
| Line 3: | Line 3: | ||
| * Autori: [[miculuis1@gmail.com | Florian-Luis Micu ]], [[sorinabuf@gmail.com | Sorina-Anamaria Buf ]], [[stefancocioran@gmail.com | Ștefan Cocioran ]] | * Autori: [[miculuis1@gmail.com | Florian-Luis Micu ]], [[sorinabuf@gmail.com | Sorina-Anamaria Buf ]], [[stefancocioran@gmail.com | Ștefan Cocioran ]] | ||
| * Data publicării: 08.12.2025 | * Data publicării: 08.12.2025 | ||
| - | * Data ultimei modificări: 08.12.2025 | + | * Data ultimei modificări: 10.12.2025 |
| + | * reordonare secțiuni. | ||
| + | * introducerea Generic Singleton Factory ca materie [Optional]. | ||
| + | * introducerea CRTP ca materie [Optional]. | ||
| ======Obiective====== | ======Obiective====== | ||
| Line 17: | Line 20: | ||
| * compilarea cu warning-uri pentru raw types și dangerous casts | * compilarea cu warning-uri pentru raw types și dangerous casts | ||
| * glosar pentru denumirea tipurilor generice. | * glosar pentru denumirea tipurilor generice. | ||
| + | * prezentarea pattern-ului avansat Generic Singleton Factory. | ||
| + | * prezentarea pattern-ului CRTP. | ||
| <note warning> | <note warning> | ||
| Line 23: | Line 28: | ||
| </note> | </note> | ||
| - | ======🏷️ Genericics====== | + | ======🏷️ Generics====== |
| Genericitatea este unul dintre cele mai elegante mecanisme introduse în Java pentru a ajuta la scrierea unui cod **general**, **sigur** și **reutilizabil**. | Genericitatea este unul dintre cele mai elegante mecanisme introduse în Java pentru a ajuta la scrierea unui cod **general**, **sigur** și **reutilizabil**. | ||
| Line 226: | Line 231: | ||
| Compilatorul deduce **automat** tipul ''T'' în fiecare apel. | Compilatorul deduce **automat** tipul ''T'' în fiecare apel. | ||
| + | |||
| + | <note warning> | ||
| + | În Java, tipurile generice **nu pot fi primitive**, drept urmare va trebui să folosim mereu **obiecte** sau **clase wrapper**. | ||
| + | </note> | ||
| ====Tipuri generice multiple==== | ====Tipuri generice multiple==== | ||
| Line 240: | Line 249: | ||
| Aceasta este baza map-urilor, dicționarelor și tuplurilor. | Aceasta este baza map-urilor, dicționarelor și tuplurilor. | ||
| + | ====Interfețe generice==== | ||
| - | =====Tipuri generice cu limitări===== | + | Interfețele pot fi la rândul lor generice: |
| - | Există situații în care dorim ca un parametru generic să fie **restricționat** la un anumit tip (sau **subclase** ale acestuia). | + | <code java> |
| + | public interface Transformer<T, R> { | ||
| + | R apply(T input); | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | <note tip> | ||
| + | Interfața de mai sus este una specială, deoarece se numește **interfață funcțională**. Vom discuta mai multe despre acestea în laboratorul viitor. | ||
| + | </note> | ||
| + | |||
| + | =====Heritable Generics===== | ||
| + | |||
| + | Generics pot participa la **lanțuri de moștenire** exact ca orice alte clase, dar cu câteva detalii importante. | ||
| + | |||
| + | ====Moștenire păstrând parametrul de tip==== | ||
| <code java> | <code java> | ||
| - | public class NumberBox<T extends Number> { | + | class MyList<T> extends ArrayList<T> { |
| - | private T value; | + | // Moștenește întreaga funcționalitate și rămâne generică |
| } | } | ||
| </code> | </code> | ||
| - | Această constrângere oferă două avantaje: | + | Clasa derivată continuă să fie generică și poate funcționa exact ca tipul original. |
| - | * garantează că putem apela metodele definite în ''Number'', | + | |
| - | * previne utilizarea unor tipuri incompatibile (ex. ''String''). | + | |
| - | De asemenea, Java permite **bounded type parameters multiple**: | + | ====Moștenire cu specializarea tipului (fixarea parametrului)==== |
| <code java> | <code java> | ||
| - | public <T extends Number & Comparable<T>> void process(T value) { ... } | + | class IntegerList extends ArrayList<Integer> { } |
| </code> | </code> | ||
| - | Această combinație este utilă în **algoritmi de sortare** sau **agregare numerică**. | + | Clasa nu mai este generică. Acceptă doar valori de tip Integer. |
| - | Un alt exemplu destul de des folosit cu limitări în tipuri generice este acesta: | + | Acest lucru este util pentru a crea **alias-uri semantice**: |
| <code java> | <code java> | ||
| - | public static <T extends Comparable<T>> T max(T a, T b) { | + | class UserIdList extends ArrayList<UserId> { } |
| - | return a.compareTo(b) > 0 ? a : b; | + | |
| - | } | + | |
| </code> | </code> | ||
| - | Practic, am făcut o metodă care întoarce maximul dintre două obiecte, doar dacă acel obiect **implementează interfața Comparable** pentru a putea folosi metoda ''compareTo''. | + | ====Moștenire cu parametri ignorați (de evitat)==== |
| + | <code java> | ||
| + | class Bad<T> extends Box<String> { } | ||
| + | </code> | ||
| + | Parametrul generic T există, dar **nu mai este folosit**. Asta înseamnă că următorul cod compilează: | ||
| + | |||
| + | <code java> | ||
| + | Bad<Integer> b = new Bad<>(); | ||
| + | </code> | ||
| + | |||
| + | Deși clasa funcționează doar cu ''String'' intern. Aceasta creează confuzie și design ineficient. | ||
| + | |||
| + | ====Moștenire cu limitări pe tipuri==== | ||
| + | |||
| + | <code java> | ||
| + | class SortedBox<T extends Comparable<T>> extends Box<T> { } | ||
| + | </code> | ||
| + | |||
| + | Clasa poate fi instanțiată doar cu tipuri care sunt comparabile între ele. | ||
| + | |||
| + | Acest tip de moștenire apare des în structuri de date (liste ordonate, heap-uri, arbori etc.). | ||
| + | |||
| + | <note tip> | ||
| + | Vom vedea mai jos ce face exact ''extends'' în cadrul generics. | ||
| + | </note> | ||
| =====Wildcards – flexibilitate în genericitate===== | =====Wildcards – flexibilitate în genericitate===== | ||
| Line 281: | Line 325: | ||
| ====Wildcard neîngrădit (?)==== | ====Wildcard neîngrădit (?)==== | ||
| - | Folosim ? când tipul nu este relevant, ci doar existența lui: | + | Folosim ''?'' când tipul nu este relevant, ci doar existența lui: |
| <code java> | <code java> | ||
| Line 331: | Line 375: | ||
| * **Consumer Super** → folosește ''super'' când lista primește valori. | * **Consumer Super** → folosește ''super'' când lista primește valori. | ||
| </note> | </note> | ||
| + | |||
| + | <note important> | ||
| + | Deși folosim ''extends'', acesta se poate referi atât la **clase** cât și la **interfețe** (vezi exemplul de mai sus despre Comparable). | ||
| + | </note> | ||
| + | |||
| + | ====Wildcard Capture==== | ||
| + | |||
| + | Wildcard-urile (''?'') sunt utile pentru flexibilitate, dar **nu** pot fi folosite ca **tipuri reale** în operații care necesită consistență de tip. | ||
| + | |||
| + | De exemplu, următoarea metodă nu compilează: | ||
| + | |||
| + | <code java> | ||
| + | void resetFirst(List<?> list) { | ||
| + | list.set(0, list.get(0)); // Eroare | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | Motivul: | ||
| + | * ''list.get(0)'' întoarce un ''Object'' | ||
| + | * ''list.set'' necesită un tip exact, pe care ''?'' nu îl reprezintă | ||
| + | * compilatorul nu poate garanta că tipurile sunt compatibile | ||
| + | |||
| + | Wildcard-ul înseamnă “nu cunosc tipul”, deci nu poate fi folosit ca tip concret. | ||
| + | |||
| + | Pentru a rezolva această problemă, folosim **Wildcard Capture**. Definim o **metodă generică auxiliară** care introduce un **parametru de tip real**: | ||
| + | |||
| + | <code java> | ||
| + | <T> void resetHelper(List<T> list) { | ||
| + | list.set(0, list.get(0)); // Aici merge | ||
| + | } | ||
| + | |||
| + | void resetFirst(List<?> list) { | ||
| + | resetHelper(list); // Compilatorul captează wildcard-ul ca tip T | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | **Ce se întâmplă aici?** | ||
| + | * ''<?>'' este “capturat” și tratat ca un tip ''T'' concret | ||
| + | * compilatorul poate garanta coerența între ''get()'' și ''set()'' | ||
| + | * funcția helper permite operații care altfel ar fi imposibile | ||
| + | |||
| + | ===Un exemplu clasic din JDK=== | ||
| + | |||
| + | Metoda ''Collections.swap'' folosește exact acest mecanism: | ||
| + | |||
| + | <code java> | ||
| + | static void swap(List<?> list, int i, int j) { | ||
| + | swapHelper(list, i, j); | ||
| + | } | ||
| + | |||
| + | private static <T> void swapHelper(List<T> list, int i, int j) { | ||
| + | T tmp = list.get(i); | ||
| + | list.set(i, list.get(j)); | ||
| + | list.set(j, tmp); | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | - Metoda publică primește orice listă (''List<?>''). | ||
| + | - Metoda privată capturează wildcard-ul ca ''T'' și permite operații sigure. | ||
| + | |||
| + | =====Tipuri generice cu limitări===== | ||
| + | |||
| + | Există situații în care dorim ca un parametru generic să fie **restricționat** la un anumit tip (sau **subclase** ale acestuia). | ||
| + | |||
| + | Pentru a limita un tip putem folosi keyword-ul ''extends'', astfel doar o clasă moștenită de clasa extinsă poate fi folosită. În acest caz, această noțiune se cheamă **bounded generics**. | ||
| + | |||
| + | <code java> | ||
| + | public class NumberBox<T extends Number> { | ||
| + | private T value; | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | Această constrângere oferă două avantaje: | ||
| + | * garantează că putem apela metodele definite în ''Number'', | ||
| + | * previne utilizarea unor tipuri incompatibile: | ||
| + | * ''NumberBox<Integer>'' - valid | ||
| + | * ''NumberBox<Double>'' - valid | ||
| + | * ''NumberBox<String>'' - invalid | ||
| + | |||
| + | ====Limitări multiple==== | ||
| + | |||
| + | De asemenea, Java permite **multiple bounded type parameters**: | ||
| + | |||
| + | <code java> | ||
| + | public <T extends Number & Comparable<T>> void process(T value) { ... } | ||
| + | </code> | ||
| + | |||
| + | Ordinea este importantă: | ||
| + | - prima limitare trebuie să fie o clasă | ||
| + | - restul pot fi interfețe | ||
| + | |||
| + | Combinația de mai sus este utilă în **algoritmi de sortare** sau **agregare numerică**. | ||
| + | |||
| + | Un alt exemplu destul de des folosit cu limitări în tipuri generice este acesta: | ||
| + | |||
| + | <code java> | ||
| + | public static <T extends Comparable<T>> T max(T a, T b) { | ||
| + | return a.compareTo(b) > 0 ? a : b; | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | Practic, am făcut o metodă care întoarce maximul dintre două obiecte, doar dacă acel obiect **implementează interfața Comparable** pentru a putea folosi metoda ''compareTo''. | ||
| + | |||
| Line 351: | Line 498: | ||
| - Tipurile sunt transformate în Object sau limitele lor (extends). | - Tipurile sunt transformate în Object sau limitele lor (extends). | ||
| + | <spoiler [Nice to know] Instanțierea unui tip generic> | ||
| + | |||
| + | Nu putem scrie ''new T()'', deoarece tipul T **nu există la runtime** și Java nu știe dacă acest tip are un constructor fără parametrii. Drept consecință, codul următor este invalid: | ||
| + | |||
| + | <code java> | ||
| + | public class Box<T> { | ||
| + | private T value = new T(); // EROARE | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | Dar, putem folosi următorul exemplu de cod pentru a construi obiecte generice: | ||
| + | |||
| + | <code java> | ||
| + | public class Box<T> { | ||
| + | private final T value; | ||
| + | |||
| + | public Box(Class<T> clazz) throws Exception { | ||
| + | this.value = clazz.getDeclaredConstructor().newInstance(); | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | </spoiler> | ||
| + | |||
| + | <html><br></html> | ||
| ====Bridge Methods==== | ====Bridge Methods==== | ||
| Line 394: | Line 566: | ||
| =====Invarianță, Covarianță și Contravarianță===== | =====Invarianță, Covarianță și Contravarianță===== | ||
| - | Java are trei moduri principale de a trata relațiile dintre tipurile generice. | + | Java are **trei moduri principale** de a trata relațiile dintre tipurile generice. |
| ====Invarianță (comportamentul implicit)==== | ====Invarianță (comportamentul implicit)==== | ||
| Line 424: | Line 596: | ||
| ====Contravarianță (? super T)==== | ====Contravarianță (? super T)==== | ||
| - | Pentru a defini un scenariu de moștenire ca în exemplul despre invarianță, putem folosi bounded wildcards, astfel listelor li se poate permite să accepte **tipuri părinte**. | + | Pentru a defini un scenariu de moștenire ca în exemplul despre invarianță, putem folosi **bounded wildcards**, astfel listelor li se poate permite să accepte **tipuri părinte**. |
| <code java> | <code java> | ||
| Line 431: | Line 603: | ||
| Aceasta permite scrierea în colecție (''add(Integer)''), dar **nu permite** citirea unui tip specific. | Aceasta permite scrierea în colecție (''add(Integer)''), dar **nu permite** citirea unui tip specific. | ||
| + | |||
| + | ====De ce List<String> nu este List<Object>?==== | ||
| + | |||
| + | În mod natural, ai putea crede că dacă ''String'' este un subtip al lui ''Object'', atunci și ''List<String>'' ar trebui să fie un subtip al lui ''List<Object>''. Dar nu este așa. | ||
| + | |||
| + | Exemplu: | ||
| + | <code java> | ||
| + | List<String> stringList = new ArrayList<>(); | ||
| + | |||
| + | // Deși pare logic, nu este permis: | ||
| + | List<Object> objects = stringList; // Eroare de compilare! | ||
| + | </code> | ||
| + | |||
| + | De ce? Dacă ar fi fost permis, atunci: | ||
| + | <code java> | ||
| + | objects.add(new Object()); // ar fi permis... | ||
| + | String s = stringList.get(0); // ...dar aici am obține un Object, nu un String! | ||
| + | </code> | ||
| + | |||
| + | Acesta este motivul pentru care generics în Java sunt invariante. | ||
| =====Cast-uri în Generics===== | =====Cast-uri în Generics===== | ||
| Line 464: | Line 656: | ||
| <note tip> | <note tip> | ||
| - | Arrays verifică tipurile la runtime, în schimb generics nu o fac. | + | * **Reified** înseamnă că informația despre **tip** există și este păstrată la **runtime**. |
| + | * Arrays verifică tipurile la **runtime**, în schimb generics nu o fac. | ||
| </note> | </note> | ||
| + | |||
| + | =====[Nice to know] Glosar – Litere folosite în Generics===== | ||
| + | |||
| + | ^Literă ^ Semnificație^ | ||
| + | |T | Type| | ||
| + | |E | Element (în special în colecții)| | ||
| + | |K | Key| | ||
| + | |V | Value| | ||
| + | |R | Result| | ||
| + | |? | wildcard, tip necunoscut| | ||
| + | |||
| =====[Optional] Warning-uri de compilare===== | =====[Optional] Warning-uri de compilare===== | ||
| Line 480: | Line 684: | ||
| IntelliJ va rula automat această comandă pentru voi. | IntelliJ va rula automat această comandă pentru voi. | ||
| </note> | </note> | ||
| - | =====[Nice to know] Glosar – Litere folosite în Generics===== | ||
| - | ^Literă ^ Semnificație^ | + | =====[Optional] Generic Singleton Factory===== |
| - | |T | Type| | + | |
| - | |E | Element (în special în colecții)| | + | Acesta este un pattern foarte elegant, folosit intern în **JDK**, mai ales în ''Collections''. |
| - | |K | Key| | + | |
| - | |V | Value| | + | O singură instanță poate fi folosită pentru **toate tipurile**, iar generics asigură siguranța. |
| - | |R | Result| | + | |
| - | |? | wildcard, tip necunoscut| | + | <code java> |
| + | public class SingletonFactory { | ||
| + | private static final Box<Object> INSTANCE = new Box<>(); | ||
| + | |||
| + | @SuppressWarnings("unchecked") | ||
| + | public static <T> Box<T> getInstance() { | ||
| + | return (Box<T>) INSTANCE; | ||
| + | } | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | Utilizare: | ||
| + | |||
| + | <code java> | ||
| + | Box<String> a = SingletonFactory.getInstance(); | ||
| + | Box<Integer> b = SingletonFactory.getInstance(); | ||
| + | </code> | ||
| + | |||
| + | |||
| + | Observație: | ||
| + | * Instanța din spate e aceeași (''Box<Object>''). | ||
| + | * Dar fiecare apelant primește o versiune tipizată în siguranță. | ||
| + | |||
| + | <note important> | ||
| + | De ce funcționează codul de mai sus? | ||
| + | - instanța este imutabilă sau fără stare | ||
| + | - cast-ul este sigur | ||
| + | - comportamentul este identic pentru orice tip | ||
| + | </note> | ||
| + | |||
| + | <note tip> | ||
| + | Acest pattern apare în: | ||
| + | * ''Collections.emptyList()'' | ||
| + | * ''Collections.emptyMap()'' | ||
| + | * ''Optional.empty()'' | ||
| + | * ''Stream.empty()'' | ||
| + | </note> | ||
| + | |||
| + | =====[Optional] Self-bounded Generics (Curiously Recurring Template Pattern — CRTP)===== | ||
| + | |||
| + | Este un pattern avansat unde un **tip generic** se referă la el **însuși**. | ||
| + | |||
| + | <code java> | ||
| + | class ComparableSelf<T extends ComparableSelf<T>> { } | ||
| + | </code> | ||
| + | |||
| + | Scopul acestui pattern este de a asigura că **subclasele** folosesc un **tip compatibil cu ele însele**. | ||
| + | |||
| + | De exemplu: | ||
| + | <code java> | ||
| + | class Person extends ComparableSelf<Person> { } | ||
| + | </code> | ||
| + | |||
| + | Beneficiul major este că putem scrie algoritmi care impun **tipuri consistente**. | ||
| + | |||
| + | Exemplu tipic: | ||
| + | <code java> | ||
| + | interface SelfComparable<T extends SelfComparable<T>> { | ||
| + | int compareTo(T other); | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | Implementare corectă: | ||
| + | <code java> | ||
| + | class Car implements SelfComparable<Car> { | ||
| + | public int compareTo(Car other) { ... } | ||
| + | } | ||
| + | </code> | ||
| + | |||
| + | Implementare incorectă (interzisă de self-bounding): | ||
| + | <code java> | ||
| + | class Car implements SelfComparable<Object> { ... } | ||
| + | </code> | ||
| + | |||
| + | Self-bounded generics **împiedică** astfel de erori de design. | ||
| + | |||
| ======Exerciții====== | ======Exerciții====== | ||