This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
laboratoare:genericitate [2016/11/19 23:13] Cosmin Petrisor [Bounded Wildcards] |
laboratoare:genericitate [2019/11/24 19:44] (current) Florin Mihalache [Exerciții] |
||
---|---|---|---|
Line 1: | Line 1: | ||
= Genericitate = | = Genericitate = | ||
- | * Responsabil: [[cosmin_ioan.petrisor@cti.pub.ro|Cosmin Petrişor]] | + | == Obiective == |
- | * Data publicării: 19.11.2016 | + | |
- | * Data ultimei modificări: 19.11.2016 | + | |
- | == Introducere == | + | Scopul acestui laborator este prezentarea conceptului de genericitate și modalitățile de creare și folosire a claselor, metodelor și interfețelor generice în Java. |
- | Genericitatea este un concept nou, introdus o dată cu JDK 5.0. Din unele puncte de vedere se poate asemăna cu conceptul de //template// din C++. Mecanismul genericității oferă un mijloc de **abstractizare a tipurilor de date** și este util mai ales în ierarhia de colecții. | + | Aspectele urmărite sunt: |
+ | * prezentarea structurilor generice simple | ||
+ | * conceptele de wildcard și bounded wildcards | ||
+ | * utilitatea genericității în design-ul unui sistem | ||
+ | |||
+ | |||
+ | == Introducere == | ||
Să urmărim exemplul de mai jos: | Să urmărim exemplul de mai jos: | ||
Line 42: | Line 46: | ||
void add(E x); | void add(E x); | ||
Iterator<E> iterator(); | Iterator<E> iterator(); | ||
- | ... | ||
} | } | ||
Line 52: | Line 55: | ||
</code> | </code> | ||
- | Sintaxa ''<E>'' este folosită pentru a defini tipuri formale în cadrul interfețelor. Aceste tipuri pot fi folosite în mod asemănător cu tipurile uzuale, cu anumite restricții totuși. În momentul în care invocăm o structură generică ele vor fi înlocuite cu tipurile efective utilizate în invocare. Concret, fie un apel de forma: | + | Sintaxa ''<E>'' (poate fi folosită orice literă) este folosită pentru a defini tipuri formale în cadrul interfețelor. Aceste tipuri pot fi folosite în mod asemănător cu tipurile uzuale, cu anumite restricții totuși. În momentul în care invocăm o structură generică ele vor fi înlocuite cu tipurile efective utilizate în invocare. Concret, fie un apel de forma: |
<code java> | <code java> | ||
Line 60: | Line 63: | ||
În această situație, tipul formal ''E'' a fost înlocuit (la compilare) cu tipul efectiv ''Integer''. | În această situație, tipul formal ''E'' a fost înlocuit (la compilare) cu tipul efectiv ''Integer''. | ||
- | |||
- | <note> | ||
- | O analogie (simplistă) referitoare la acest mecanism de lucru cu tipurile se poate face cu mecanismul funcțiilor: acestea se definesc utilizând parametri //formali//, urmând ca în momentul unui apel acești parametri să fie înlocuiți cu parametri //actuali//. | ||
- | </note> | ||
== Genericitatea în subtipuri == | == Genericitatea în subtipuri == | ||
Line 174: | Line 173: | ||
<note> | <note> | ||
- | Trebuie reținut faptul că **în continuare nu putem introduce valori** într-o colecție ce folosește //bounded wildcards// și este dată ca parametru unei funcții. | + | Utilizarea bounded wildcards se manifestă în următoarele 2 situații : |
+ | * lower bounded wildcards se folosesc atunci când vrem să **modificăm** o colecție generică | ||
+ | * upper bounded wildcards se folosesc atunci când vrem să **parcurgem fără să modificăm** o colecție generică | ||
</note> | </note> | ||
== Type Erasure == | == Type Erasure == | ||
- | [[https://docs.oracle.com/javase/tutorial/java/generics/erasure.html | Type Erasure]] este un mecanism prin care compilatorul Java înlocuieşte la **compile time** parametrii de genericitate ai unei clase generice cu prima lor apariţie (ţinând cont de restricţii în cazul Bounded Wildcards) sau cu ''Object'' dacǎ parametrii nu apar (Raw Type). De exemplu, următorul cod: | + | [[https://docs.oracle.com/javase/tutorial/java/generics/erasure.html|Type Erasure]] este un mecanism prin care compilatorul Java înlocuieşte la **compile time** parametrii de genericitate ai unei clase generice cu prima lor apariţie (ţinând cont de restricţii în cazul Bounded Wildcards) sau cu ''Object'' dacǎ parametrii nu apar (Raw Type). De exemplu, următorul cod: |
<code java> | <code java> | ||
Line 198: | Line 199: | ||
<code java> | <code java> | ||
- | class GenericClass <T>{ | + | class GenericClass <T> { |
void genericFunction(List<String> stringList) { | void genericFunction(List<String> stringList) { | ||
stringList.add("foo"); | stringList.add("foo"); | ||
} | } | ||
// {...} | // {...} | ||
- | public static void main(String[] args) { | + | public static void main(String[] args) { |
- | GenericClass genericClass = new GenericClass(); | + | GenericClass genericClass = new GenericClass(); |
- | List<Integer> integerList = new ArrayList<Integer>(); | + | List<Integer> integerList = new ArrayList<Integer>(); |
- | integerList.add(100); | + | |
- | genericClass.genericFunction(integerList); | + | integerList.add(100); |
- | System.out.println(integerList.get(0)); // 100 | + | genericClass.genericFunction(integerList); |
- | System.out.println(integerList.get(1)); // foo | + | |
- | } | + | System.out.println(integerList.get(0)); // 100 |
+ | System.out.println(integerList.get(1)); // foo | ||
+ | } | ||
} | } | ||
</code> | </code> | ||
- | Observăm că în ''main'' se instanţiază clasa ''GenericClass'' cu Raw Type, apoi se trimite ca argument metodei ''genericFunction'' un ''ArrayList<Integer>''. Codul nu va genera erori şi va afişa //100//, apoi //foo//. Acest lucru se întâmplă tot din cauza mecanismului de Type Erasure. Să urmărim ce se întâmplă: la instanţierea clasei ''GenericClass'' nu se specifică tipul generic al acesteia iar compilatorul va înlocui în corpul clasei peste tot //T// cu ''Object'' şi va dezactiva verificarea de tip. Așadar, obiectul ''genericClass'' va aparţine unei clase de forma: | + | Observăm că în ''main'' se instanţiază clasa ''GenericClass'' cu Raw Type, apoi se trimite ca argument metodei ''genericFunction'' un ''ArrayList<Integer>''. Codul nu va genera erori şi va afişa //100//, apoi //foo//. Acest lucru se întâmplă tot din cauza mecanismului de **Type Erasure**. Să urmărim ce se întâmplă: la instanţierea clasei ''GenericClass'' nu se specifică tipul generic al acesteia iar compilatorul va înlocui în corpul clasei peste tot ''T'' cu ''Object'' şi va dezactiva verificarea de tip. Așadar, obiectul ''genericClass'' va aparţine unei clase de forma: |
<code java> | <code java> | ||
Line 226: | Line 229: | ||
</code> | </code> | ||
<note important> | <note important> | ||
- | Modelul de mai sus este bad practice tocmai pentru că are un comportament nedeterminat și poate conduce la erori. De aceea nu e recomandat să folosiți Raw Types, ci să specificați **întotdeauna** tipul obiectelor în cazul instanțierii claselor generice! | + | Modelul de mai sus este **bad practice** tocmai pentru că are un comportament nedeterminat și poate conduce la erori. De aceea nu e recomandat să folosiți Raw Types, ci să specificați **întotdeauna** tipul obiectelor în cazul instanțierii claselor generice! |
</note> | </note> | ||
== Metode generice == | == Metode generice == | ||
- | Java ne oferă posibilitatea scrierii de metode generice (deci având un tip-parametru) pentru a facilita prelucrarea unor structuri generice (date ca parametru). | + | Java ne oferă posibilitatea scrierii de metode generice (deci având un tip-parametru) pentru a facilita prelucrarea unor structuri generice. |
Să exemplificăm acest fapt. Observăm în continuare 2 căi de implementare ale unei metode ce copiază elementele unui vector intrinsec într-o colecție: | Să exemplificăm acest fapt. Observăm în continuare 2 căi de implementare ale unei metode ce copiază elementele unui vector intrinsec într-o colecție: | ||
<code java> | <code java> | ||
- | // Metoda corecta | + | // Metoda corectă |
static <T> void correctCopy(T[] a, Collection<T> c) { | static <T> void correctCopy(T[] a, Collection<T> c) { | ||
for (T o : a) | for (T o : a) | ||
- | c.add(o); // Operatia va fi permisa | + | c.add(o); // Operaţia va fi permisă |
} | } | ||
- | // Metoda incorecta | + | // Metoda incorectă |
static void incorrectCopy(Object[] a, Collection<?> c) { | static void incorrectCopy(Object[] a, Collection<?> c) { | ||
for (Object o : a) | for (Object o : a) | ||
- | c.add(o); // Operatie incorecta, semnalata ca eroare de catre compilator | + | c.add(o); // Operatie incorectă, semnalată ca eroare de către compilator |
} | } | ||
</code> | </code> | ||
- | Trebuie remarcat faptul că //correctCopy()// este o metodă validă, care se execută corect, însă //incorrectCopy()// nu este, din cauza limitării pe care o cunoaştem deja, referitoare la adăugarea elementelor într-o colecție generică cu tip specificat. Putem remarca, de asemenea, că, și în acest caz, putem folosi //wildcards// sau //bounded wildcards//. Astfel, următoarele declaraţii de metode sunt corecte: | + | Trebuie remarcat faptul că ''correctCopy()'' este o metodă validă, care se execută corect, însă ''incorrectCopy()'' nu este, din cauza limitării pe care o cunoaştem deja, referitoare la adăugarea elementelor într-o colecție generică cu tip specificat. Putem remarca, de asemenea, că, și în acest caz, putem folosi //wildcards// sau //bounded wildcards//. Astfel, următoarele declaraţii de metode sunt corecte: |
<code java> | <code java> | ||
- | // O metoda ce copiaza elementele dintr-o lista in alta lista | + | // Copiază elementele dintr-o listă în altă listă |
public static <T> void copy(List<T> dest, List<? extends T> src) { ... } | public static <T> void copy(List<T> dest, List<? extends T> src) { ... } | ||
- | // O metoda de adaugare a unor elemente intr-o colectie, cu restrictionarea tipului generic | + | // Adaugă elemente dintr-o colecţie în alta, cu restricţionarea tipului generic |
public <T extends E> boolean addAll(Collection<T> c); | public <T extends E> boolean addAll(Collection<T> c); | ||
</code> | </code> | ||
Line 259: | Line 262: | ||
== Exerciții == | == Exerciții == | ||
- | - (**6p**) Implementați o **coadă de priorități** care acceptă doar //tipuri comparabile// (descendente din ''java.lang.Comparable''). Folosiți coada pentru a **sorta în ordine crescătoare** o serie de valori (veți testa valori numerice, generate aleator). Utilizați //bounded wildcards//. | + | - (**6p**) Implementați o **tabelă de dispersie** generică care va permite să stocaţi perechi de tip cheie-valoare. |
- | * (**1p**) Aveți grijă la tipul parametrului cozii pe care o implementați (este restricționat) | + | * (**2p**) Scrieţi antetul clasei ''MyHashMap'' şi prototipul funcţiilor **put** şi **get**. Aveţi grijă la parametrizarea tipurilor. |
- | * (**3p**) Pentru păstrarea la orice moment a ordinii valorilor conținute în colecția voastră, puteți să implementați coada ca o listă simplu-înlănțuită, în care capul listei să fie cel mai "mic" element | + | * (**2p**) Implementaţi metoda **put**. Vă puteți crea o clasă internă cu rol de //entry// şi puteţi stoca //entry-urile// într-o colecţie generică existentă în Java. |
- | * Vă puteți crea o clasă internă care să încapsuleze un nod de listă înlănțuită | + | * (**1p**) Implementaţi metoda **get**. |
- | * Atenție la adăugarea de noduri în coadă și la cazurile extreme | + | * (**1p**) Testaţi implementarea voastră folosind o clasă definită de voi, care suprascrie metoda **hashCode** din ''Object''. |
- | * (**1p**) Folosiți o **colecție generică** în care veți stoca valorile numerice inițiale (de adăugat) | + | |
- | * (**1p**) Implementați **o metodă generică** de copiere a valorilor din această colecție în coada de priorități. | + | |
- | * (**Bonus 3p**) Implementați colecția voastră ca //iterabilă//, compatibilă cu syntactic-sugar-ul **for-each** | + | |
- | * Trebuie să implementați interfața ''Iterable''; atenție, și ea este generică | + | |
- | * Creați-vă //iteratorul// (parametrizat!) ca o clasă internă care să rețină datele necesare (nodul din listă la care a ajuns, etc.) | + | |
- | * nu este necesar să implementați metoda ''remove'' din ''Iterator'' | + | |
- | * Afișați-vă acum rezultatele folosind **for-each** pe coada voastră! | + | |
- (**4p**) Să considerăm interfața ''Sumabil'', ce conține metoda ''void addValue(Sumabil value)''. Această metodă adună la valoarea curentă (stocată în instanța ce apelează metoda) o altă valoare, aflată într-o instanță cu același tip. Pornind de la această interfață, va trebui să: | - (**4p**) Să considerăm interfața ''Sumabil'', ce conține metoda ''void addValue(Sumabil value)''. Această metodă adună la valoarea curentă (stocată în instanța ce apelează metoda) o altă valoare, aflată într-o instanță cu același tip. Pornind de la această interfață, va trebui să: | ||
* Definiți clasele ''MyVector3'' și ''MyMatrix'' (ce reprezintă un vector cu 3 coordonate și o matrice de dimensiune 4 x 4), ce implementează Sumabil | * Definiți clasele ''MyVector3'' și ''MyMatrix'' (ce reprezintă un vector cu 3 coordonate și o matrice de dimensiune 4 x 4), ce implementează Sumabil | ||
Line 277: | Line 273: | ||
== Resurse == | == Resurse == | ||
* <html><a class="media mediafile mf_pdf" href="/poo/laboratoare/genericitate?do=export_pdf">PDF laborator</a></html> | * <html><a class="media mediafile mf_pdf" href="/poo/laboratoare/genericitate?do=export_pdf">PDF laborator</a></html> | ||
- | * {{|Soluții}} | ||
==Referinţe== | ==Referinţe== | ||