This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
laboratoare:clase-interne [2019/01/25 16:54] Adriana Draghici [Clase interne normale] |
laboratoare:clase-interne [2019/10/31 18:44] (current) Adriana Draghici |
||
---|---|---|---|
Line 6: | Line 6: | ||
== Obiective == | == Obiective == | ||
- | |||
- | Scopul acestui laborator este prezentarea conceptului de clasă internă și modalitățile de creare și folosire a claselor interne în Java. | ||
- | |||
- | Aspectele urmărite sunt: | ||
* prezentarea tipurilor de clase interne | * prezentarea tipurilor de clase interne | ||
- | * diferențele dintre clase interne statice și cele ne-statice | + | * utilizarea claselor interne |
- | * utilitatea claselor interne | + | * utilizarea sintaxei specifice claselor interne |
Line 18: | Line 14: | ||
==Introducere== | ==Introducere== | ||
- | Clasele declarate în interiorul unei alte clase se numesc clase interne (//nested classes//) și reprezintă o funcționalitate importantă deoarece permit gruparea claselor care sunt legate logic și controlul vizibilității uneia din cadrul celorlalte. | + | Clasele declarate în interiorul unei alte clase se numesc clase interne (//nested classes//). Acestea permit gruparea claselor care sunt legate logic și controlul vizibilității uneia din cadrul celorlalte. |
- | Clasele interne sunt de mai multe tipuri, în funcție de modul de a le instanția și de relația lor cu clasa exterioră: | + | Clasele interne sunt de mai multe tipuri: |
* clase interne normale (//regular inner classes//) | * clase interne normale (//regular inner classes//) | ||
* clase anonime (//anonymous inner classes//) | * clase anonime (//anonymous inner classes//) | ||
Line 26: | Line 22: | ||
* clase interne metodelor (//method-local inner classes//) sau blocurilor | * clase interne metodelor (//method-local inner classes//) sau blocurilor | ||
- | <note important>Unul din avantajele claselor interne este comportamentul acestora ca un **membru** al clasei. Asta face ca o clasa internă sa poata avea acces la toți membrii clasei de care aparține (//outer class//), inclusiv cei ''private''. În plus, aceasta poate avea modificatorii permiși metodelor și variabilelor claselor. Astfel, o clasa internă poate fi nu numai ''public'', ''final'', ''abstract'' dar și ''private'', ''protected'' și ''static''.</note> | + | <note important> |
+ | * O clasă internă se comportă ca un membru al clasei în care a fost declarată | ||
+ | * O clasă internă are acces la toți membrii clasei în care a fost declarată, inclusiv cei **private** | ||
+ | * O clasă internă poate fi ''public'', ''final'', ''abstract'' dar și ''private'', ''protected'' și ''static'', însumând modificatorii claselor obișnuite și cei permiși metodelor și variabilelor | ||
+ | * [[https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html|Nested classes vs. Inner classes]] | ||
+ | </note> | ||
==Clase interne "normale" == | ==Clase interne "normale" == | ||
Line 32: | Line 34: | ||
<code java Test.java> | <code java Test.java> | ||
+ | interface Engine { | ||
+ | public int getFuelCapacity(); | ||
+ | } | ||
+ | |||
class Car { | class Car { | ||
- | class Engine { | + | class OttoEngine implements Engine { |
private int fuelCapacity; | private int fuelCapacity; | ||
- | public Engine(int fuelCapacity) { | + | public OttoEngine(int fuelCapacity) { |
this.fuelCapacity = fuelCapacity; | this.fuelCapacity = fuelCapacity; | ||
} | } | ||
Line 45: | Line 51: | ||
} | } | ||
- | public Engine getEngine() { | + | public OttoEngine getEngine() { |
- | Engine engine = new Engine(11); | + | OttoEngine engine = new OttoEngine(11); |
return engine; | return engine; | ||
} | } | ||
Line 55: | Line 61: | ||
Car car = new Car(); | Car car = new Car(); | ||
- | Car.Engine firstEngine = car.getEngine(); | + | Car.OttoEngine firstEngine = car.getEngine(); |
- | Car.Engine secondEngine = car.new Engine(10); | + | Car.OttoEngine secondEngine = car.new OttoEngine(10); |
- | System.out.println(firstEngine.value()); | + | System.out.println(firstEngine.getFuelCapacity()); |
- | System.out.println(secondEngine.value()); | + | System.out.println(secondEngine.getFuelCapacity()); |
} | } | ||
} | } | ||
</code> | </code> | ||
- | În exemplul de mai sus, o dată ce avem o instanță a clasei Car, sunt folosite două modalități de a obține o instanță a clasei ''Engine'' (definită în interiorul clasei ''Car''): | + | <code html> |
- | * definim o metodă ''getEngine'', care creează și întoarce o astfel de instanță; | + | student@poo:~$ javac Test.java |
- | * instanțiem efectiv ''Engine''; observați cu atentie sintaxa folosita! Pentru a instanția ''Engine'', avem nevoie de o instanta ''Car'': ''car.new Engine(10);'' | + | student@poo:~$ ls |
+ | Car.class Car$OttoEngine.class Engine.class Test.class Test.java | ||
+ | </code> | ||
+ | |||
+ | Urmăriți exemplul de folosire a claselor interne de mai sus. Adresați-vă asistentului pentru eventuale neclarități. | ||
Dintr-o clasă internă putem accesa **referința la clasa externă** (în cazul nostru ''Car'') folosind numele acesteia și keyword-ul //this//: | Dintr-o clasă internă putem accesa **referința la clasa externă** (în cazul nostru ''Car'') folosind numele acesteia și keyword-ul //this//: | ||
Line 73: | Line 83: | ||
Car.this; | Car.this; | ||
</code> | </code> | ||
+ | |||
+ | <note hint>Rulați codul din exemplu. </note> | ||
===Modificatorii de acces pentru clase interne=== | ===Modificatorii de acces pentru clase interne=== | ||
Line 78: | Line 90: | ||
Așa cum s-a menționat și în secțiunea [[.:clase-interne#introducere|Introducere]], claselor interne le pot fi asociați **orice** identificatori de acces, spre deosebire de clasele ''top-level'' Java, care pot fi doar ''public'' sau ''package-private''. Ca urmare, clasele interne pot fi, în plus, ''private'' și ''protected'', aceasta fiind o modalitate de a **ascunde implementarea**. | Așa cum s-a menționat și în secțiunea [[.:clase-interne#introducere|Introducere]], claselor interne le pot fi asociați **orice** identificatori de acces, spre deosebire de clasele ''top-level'' Java, care pot fi doar ''public'' sau ''package-private''. Ca urmare, clasele interne pot fi, în plus, ''private'' și ''protected'', aceasta fiind o modalitate de a **ascunde implementarea**. | ||
- | <code java Test.java> | + | Folosind exemplul anterior, modificați clasa ''OttoEngine'' pentru a fi privată. Observați erorile de compilare care rezultă. |
- | interface Engine { | + | |
- | public int getFuelCapacity(); | + | |
- | } | + | |
- | class Car { | + | * Tipul ''Car.OttoEngine'' nu mai poate fi accesat din exterior. Acest neajuns poate fi rezolvat cu ajutorul interfeței ''Engine''. Asociindu-i clasei interne ''Car.OttoEngine'' supertipul ''Engine'' prin moștenire, putem instanția clasa prin **upcasting**. |
- | private class FerrariEngine implements Engine { | + | |
- | private int fuelCapacity; | + | |
- | public FerrariEngine(int fuelCapacity) { | + | * Fiind privată, clasa internă are implicit toți constructorii privați. Totuși, putem instanția obiecte de tipul ''Car.OttoEngine'' în interiorul clasei ''Car'', urmând să le întoarcem folosind tot upcasting la ''Engine''. Astfel, folosind metode getEngine, ascundem complet implementarea clasei ''Car.OttoEngine''. |
- | this.fuelCapacity = fuelCapacity; | + | |
- | } | + | |
- | public int getFuelCapacity() { | ||
- | return fuelCapacity; | ||
- | } | ||
- | } | ||
- | |||
- | public Engine getEngine() { | ||
- | FerrariEngine engine = new FerrariEngine(11); | ||
- | return engine; | ||
- | } | ||
- | } | ||
- | |||
- | public class Main { | ||
- | public static void main(String[] args) { | ||
- | Car car = new Car(); | ||
- | |||
- | Car.FerrariEngine firstEngine = car.getEngine(); // va genera eroare, deoarece | ||
- | // tipul Car.FerrariEngine nu este vizibil | ||
- | Car.FerrariEngine secondEngine = new Car().new FerrariEngine(10); // din nou eroare | ||
- | |||
- | Engine thirdEngine = car.getEngine(); // acces corect la o instanta FerrariEngine | ||
- | System.out.println(thirdEngine.getFuelCapacity()); | ||
- | } | ||
- | } | ||
- | </code> | ||
- | |||
- | Observați definirea interfeței ''Engine''. Ea este utilă pentru a putea **asocia** clasei ''FerrariEngine'' un ''tip'', care să ne permita folosirea instanțelor acesteia, altfel tipul ei nu ar fi fost vizibil pentru ca a fost declarată ''private''. Observați, de asemenea, încercarile eronate de a instanția ''FerrariEngine''. Cum clasa internă a fost declarată ''private'', acest tip nu mai este vizibil in exteriorul clasei ''Car''. | ||
Line 123: | Line 102: | ||
==Clase anonime== | ==Clase anonime== | ||
+ | În dezvoltarea software, există situații când o componentă a aplicației are o utilitate suficient de mare pentru a putea fi considerată o entitate separată (sau clasă). De multe ori, însă, aceasta nu este utilizată decât într-o porțiune restrânsă din aplicație, într-un context foarte specific (într-un lanț de moșteniri sau ierarhie de interfețe). Într-o aplicație reală, crearea unei clase pentru fiecare astfel de componentă poate duce la fenomenul de Class Explosion, care, pe scurt, ar aduce după sine o aplicație cu performanțe scăzute și greu de modificat/extins. | ||
- | Exista multe situații în care o clasă internă este instanțiată într-un singur loc (și este folosită prin ''upcasting'' la o clasă de bază sau interfață), ceea ce face ca numele clasei să nu mai fie important, iar tipul ei poate fi un subtip al unei clase sau o implementare a unei interfețe. Singurele metode care pot fi apelate pe o clasa anonimă sunt cele ale tipului pe care îl extinde sau implementează. | + | Pentru a evita acest fenomen, putem folosi **clase interne anonime** în locul definirii unei clase cu număr de utilizări reduse. Acestea nu au nume și apar în program ca instanțe ale unei clase moștenite (sau a unei interfețe extinse), care suprascriu (sau implementează) anumite metode. |
- | În Java putem crea **clase interne anonime** (fără nume) ca în exemplul următor: | + | Întorcându-ne la exemplul cu clasa top-level ''Car'', putem rescrie metoda ''getEngine()'' a acesteia astfel: |
<code java> | <code java> | ||
- | interface Engine { | + | [...] |
- | public int getFuelCapacity(); | + | |
- | } | + | |
class Car { | class Car { | ||
public Engine getEngine(int fuelCapacity) { | public Engine getEngine(int fuelCapacity) { | ||
Line 144: | Line 121: | ||
} | } | ||
} | } | ||
- | + | [...] | |
- | public class Test { | + | |
- | public static void main(String[] args) { | + | |
- | Car car = new Car(); | + | |
- | + | ||
- | Engine engine = car.getEngine(11); | + | |
- | System.out.println(engine.getFuelCapacity()); | + | |
- | } | + | |
- | } | + | |
</code> | </code> | ||
- | <note tip>IntelliJ sugerează înlocuirea cu funcții lambda însă acest concept nu este acoperit în laborator. Pentru detalii suplimentare urmăriți acest [[https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/Lambda-QuickStart/index.html#section2|exemplu]]</note> | + | <note> Modificați implementarea clasei Car. Rulați codul. Urmați instrucțiunile de mai jos pentru a restabilit funcționalitatea programului. Adresați-vă asistentului pentru neclarități.</note> |
- | Observați modalitatea de declarare a clasei anonime. Sintaxa ''return new Engine() { ... }'' reprezintă urmatoarele: | + | Metoda folosită mai sus elimină necesitatea creări unei clase interne "normale", reducând volumul codului și crescând lizibilitatea acestuia. Sintaxa ''return new Engine() { ... }'' se poate citi astfel: "Crează o clasă care implementează interfața Engine, conform următoarei implementări". |
- | * dorim sa întoarcem un obiect de tip ''Engine'' | + | |
- | * acest obiect este instanțiat imediat dupa ''return'', folosind ''new'' (referința întoarsă de ''new'' va fi ''upcast'' la clasa de baza: ''Engine'') | + | |
- | * numele clasei instanțiate este absent (ea este anonimă), însă ea este de ''tipul'' ''Engine'', prin urmare, va implementa metoda/metodele din interfață(cum e metoda ''value''). Corpul clasei urmeaza imediat instanțierii. | + | |
- | Construcția ''return new Engine() { ... }'' este echivalentă cu a spune: //creează un obiect al unei clase anonime ce implementeaza ''Engine''//. | + | Observații: |
+ | * acest obiect este instanțiat imediat după ''return'', folosind ''new'' (referința întoarsă de ''new'' va fi ''upcast'' la clasa de bază: ''Engine'') | ||
+ | * numele clasei instanțiate este absent (ea este anonimă), însă ea este de ''tipul'' ''Engine'', prin urmare, va implementa metoda/metodele din interfață(cum e metoda ''getFuelCapacity''). Corpul clasei urmeaza imediat instanțierii. | ||
+ | <note tip>IntelliJ sugerează înlocuirea cu funcții lambda însă acest concept nu este acoperit în laborator. Pentru detalii suplimentare urmăriți acest [[https://www.oracle.com/webfolder/technetwork/tutorials/obe/java/Lambda-QuickStart/index.html#section2|exemplu]]</note> | ||
+ | |||
<note important>O clasă internă anonimă poate extinde o clasă //sau// să implementeze o singură interfață, nu poate face pe ambele împreună ca la clasele ne-anonime (interne sau nu), și nici nu poate să implementeze mai multe interfețe. </note> | <note important>O clasă internă anonimă poate extinde o clasă //sau// să implementeze o singură interfață, nu poate face pe ambele împreună ca la clasele ne-anonime (interne sau nu), și nici nu poate să implementeze mai multe interfețe. </note> | ||
Line 172: | Line 142: | ||
<code java> | <code java> | ||
- | new Student("Andrei") { | + | new Engine("Otto") { |
// ... | // ... | ||
} | } | ||
</code> | </code> | ||
- | În acest exemplu, am instanțiat o clasa anonimă, ce extinde clasa ''Student'', apelând constructorul clasei de bază cu parametrul ''"Andrei"''. | + | În acest exemplu, am instanțiat o clasa anonimă, ce implementează interfața ''Engine'', apelând constructorul clasei de bază cu parametrul ''"Otto"''. |
- | + | ||
Line 192: | Line 160: | ||
* nu putem accesa câmpuri nestatice ale clasei externe din clasă internă (nu avem o instanță a clasei externe) | * nu putem accesa câmpuri nestatice ale clasei externe din clasă internă (nu avem o instanță a clasei externe) | ||
- | <code java Test.java> | ||
- | class Student { | ||
- | public int grade = 9; | ||
- | class Gradebook { | + | <note> |
- | private int i = 1; | + | * Pornind de la codul de [[.:clase-interne#Clase interne "normale"|aici]], modificați clasa internă ''OttoEngine'' pentru a fi statică. |
- | + | * Observați că trebuie modificat felul prin care aceasta este instanțiată. Pentru clasele interne statice, apelăm ''new Car.OttoEngine()''. | |
- | public int updateGrade() { | + | * Rulați codul. Observați că în interiorul clasei ''Car'', putem instanția clasa internă statică folosind doar ''new OttoEngine()'', datorită contextului. |
- | return i + Student.this.grade; // OK, putem accesa un membru al clasei exterioare | + | </note> |
- | } | + | |
- | } | + | |
- | + | ||
- | static class EvilGradebook { | + | |
- | public int k = 99; | + | |
- | + | ||
- | public int updateGrade() { | + | |
- | k += grade; // EROARE, nu putem accesa un membru nestatic al clasei exterioare | + | |
- | return k; | + | |
- | } | + | |
- | } | + | |
- | } | + | |
- | + | ||
- | public class Test { | + | |
- | public static void main(String[] args) { | + | |
- | Student student = new Student(); | + | |
- | + | ||
- | Student.Gradebook out = student.new Gradebook(); // instanțiere CORECTĂ pentru o clasă nestatică | + | |
- | Student.EvilGradebook badStudent = student.new EvilGradebook(); // instanțiere INCORECTĂ a clasei statice | + | |
- | Student.EvilGradebook smartStudent = new Student.EvilGradebook(); // instanțiere CORECTĂ a clasei statice | + | |
- | } | + | |
- | } | + | |
- | </code> | + | |
- | + | ||
- | În exemplul de mai sus se observă că folosirea membrului nestatic ''grade'' în clasa statică ''EvilGradebook'' este incorectă. De asemenea, se observă modalitățile diferite de instanțiere a celor două tipuri de clase interne (statice și nestatice): | + | |
- | * folosim o instanță a clasei exterioare - ''student'' (ca și în exemplele anterioare) pentru a instanția o clasă nestatică. | + | |
- | * folosim numele claselor pentru a instanția o clasă statică. Folosirea lui ''student'' este incorectă. | + | |
- | + | ||
- | + | ||
- | <note> | + | |
+ | <note important> | ||
* //Clasele interne statice nu au nevoie de o instanță a clasei externe -> atunci de ce le facem interne acesteia?// | * //Clasele interne statice nu au nevoie de o instanță a clasei externe -> atunci de ce le facem interne acesteia?// | ||
* pentru a grupa clasele, dacă o clasă internă statică A.B este folosită doar de A, atunci nu are rost să o facem top-level. | * pentru a grupa clasele, dacă o clasă internă statică A.B este folosită doar de A, atunci nu are rost să o facem top-level. | ||
* //Avem o clasă internă A.B, când o facem statică?// | * //Avem o clasă internă A.B, când o facem statică?// | ||
* în interiorul clasei B nu avem nevoie de nimic specific instanței clasei externe A, deci nu avem nevoie de o instanță a acesteia -> o facem statică | * în interiorul clasei B nu avem nevoie de nimic specific instanței clasei externe A, deci nu avem nevoie de o instanță a acesteia -> o facem statică | ||
- | </note> | ||
- | <note tip>Terminologia //nested classes// vs //inner classes//: | ||
- | Clasele interne normale, cele anonime si cele interne blocurilor si metodelor sunt //inner classes// datorită relației pe care o au cu clasa exterioară (depind de o instanță a acesteia). Termenul de //nested classes// se referă la definirea unei clase în interiorul altei clase, și cuprinde atât //inner classes// cât și clasele statice interne. De aceea, claselor statice interne li se spune //static nested classes// și nu //static inner classes//. | ||
</note> | </note> | ||
- | |||
Line 256: | Line 188: | ||
<code java Test.java> | <code java Test.java> | ||
- | interface Engine { | + | [...] |
- | public int getFuelCapacity(); | + | |
- | } | + | |
class Car { | class Car { | ||
public Engine getEngine() { | public Engine getEngine() { | ||
- | class FerrariEngine implements Engine { | + | class OttoEngine implements Engine { |
private int fuelCapacity = 11; | private int fuelCapacity = 11; | ||
Line 270: | Line 199: | ||
} | } | ||
- | return new FerrariEngine(); | + | return new OttoEngine(); |
- | } | + | |
- | } | + | |
- | + | ||
- | public class Test { | + | |
- | public static void main(String[] args) { | + | |
- | Car car = new Car(); | + | |
- | + | ||
- | Car.FerrariEngine badEngine = car.getEngine(); | + | |
- | // EROARE: clasa FerrariEngine nu este vizibila | + | |
- | + | ||
- | Engine goodEngine = car.getEngine(); | + | |
- | + | ||
- | System.out.println(goodEngine.getFuelCapacity()); | + | |
} | } | ||
} | } | ||
+ | [...] | ||
</code> | </code> | ||
- | + | <note> | |
- | <note important>Clasele interne declarate în metode nu pot folosi variabilele declarate în metoda respectivă și nici parametrii metodei. Pentru a le putea accesa, variabilele trebuie declarate ''final'', ca în exemplul următor. Această restricție se datorează faptului că variabilele si parametrii metodelor se află pe segmentul de stivă (zonă de memorie) creat pentru metoda respectivă, ceea ce face ca ele să nu existe la fel de mult cât clasa internă. Dacă variabila este declarată ''final'', atunci la runtime se va stoca o copie a acesteia ca un câmp al clasei interne, în acest mod putând fi accesată și după execuția metodei. | + | * Schimbați implementarea clasei Car de [[.:clase-interne#Clase interne "normale"|aici]] folosind codul de mai sus |
+ | * Observați că trebuie ''OttoEngine'' este vizibilă doar în interiorul metodei | ||
+ | * Modificați metoda ''main'' astfel încât să ruleze (Hint: [[.:clase-interne#Clase anonime|upcast]]) | ||
</note> | </note> | ||
- | <code java> | ||
- | public void f() { | ||
- | final Student s = new Student(); | ||
- | // s trebuie declarat final ca sa poata fi accesat din AlterStudent | ||
- | class AlterStudent { | ||
- | public void alterStudent() { | ||
- | s.name = "Andrei" // OK | ||
- | s = new Student(); // GRESIT! | ||
- | } | ||
- | } | ||
- | } | ||
- | </code> | ||
+ | <note important>Clasele interne declarate în metode nu pot folosi variabilele declarate în metoda respectivă și nici parametrii metodei. Pentru a le putea accesa, variabilele trebuie declarate ''final'', ca în exemplul următor. Această restricție se datorează faptului că variabilele si parametrii metodelor se află pe segmentul de stivă (zonă de memorie) creat pentru metoda respectivă, ceea ce face ca ele să nu existe la fel de mult cât clasa internă. Dacă variabila este declarată ''final'', atunci la runtime se va stoca o copie a acesteia ca un câmp al clasei interne, în acest mod putând fi accesată și după execuția metodei. Pentru o explicație detaliată citiți [[ http://techtracer.com/2008/04/14/mystery-of-accessibility-in-local-inner-classes/ | Link1 ]] și [[ http://stackoverflow.com/questions/1945663/where-are-java-final-local-variables-stored | Link2]]. | ||
+ | </note> | ||
=== Clase interne în blocuri === | === Clase interne în blocuri === | ||
Exemplu de clasa internă declarata într-un **bloc**: | Exemplu de clasa internă declarata într-un **bloc**: | ||
<code java> | <code java> | ||
- | interface Engine { | + | [...] |
- | public int getFuelCapacity(); | + | |
- | } | + | |
class Car { | class Car { | ||
public Engine getEngine(int fuelCapacity) { | public Engine getEngine(int fuelCapacity) { | ||
if (fuelCapacity == 11) { | if (fuelCapacity == 11) { | ||
- | class FerrariEngine implements Engine { | + | class OttoEngine implements Engine { |
private int fuelCapacity = 11; | private int fuelCapacity = 11; | ||
Line 324: | Line 230: | ||
} | } | ||
- | return new FerrariEngine(); | + | return new OttoEngine(); |
} | } | ||
Line 330: | Line 236: | ||
} | } | ||
} | } | ||
+ | [...] | ||
</code> | </code> | ||
- | În acest exemplu, clasa internă ''FerrariEngine'' este defintă în cadrul unui bloc //if//, dar acest lucru nu înseamnă că declarația va fi luată în considerare doar la rulare, în cazul în care condiția este adevarată. | + | <note> |
+ | * Schimbați implementarea clasei Car de [[.:clase-interne#Clase interne "normale"|aici]] folosind codul de mai sus | ||
+ | * Observați că trebuie ''OttoEngine'' este vizibilă doar în interiorul blocului | ||
+ | * Modificați metoda ''main'' astfel încât să ruleze (Hint: [[.:clase-interne#Clase anonime|upcast]]) | ||
+ | </note> | ||
+ | |||
+ | În acest exemplu, clasa internă ''OttoEngine'' este defintă în cadrul unui bloc //if//, dar acest lucru nu înseamnă că declarația va fi luată în considerare doar la rulare, în cazul în care condiția este adevarată. | ||
<note important>Semnificația declarării clasei într-un bloc este legată strict de vizibilitatea acesteia. La compilare clasa va fi creată indiferent care este valoarea de adevăr a condiției //if//.</note> | <note important>Semnificația declarării clasei într-un bloc este legată strict de vizibilitatea acesteia. La compilare clasa va fi creată indiferent care este valoarea de adevăr a condiției //if//.</note> | ||
- | |||
- | |||
- | |||
- | |||
- | |||
- | |||
==Moștenirea claselor interne== | ==Moștenirea claselor interne== | ||
Line 354: | Line 261: | ||
} | } | ||
- | class FerrariEngine extends Car.Engine { | + | class OttoEngine extends Car.Engine { |
- | FerrariEngine() { | + | OttoEngine() { |
} // EROARE, avem nevoie de o legatura la obiectul clasei exterioare | } // EROARE, avem nevoie de o legatura la obiectul clasei exterioare | ||
- | FerrariEngine(Car car) { // OK | + | OttoEngine(Car car) { // OK |
car.super(); | car.super(); | ||
} | } | ||
Line 366: | Line 273: | ||
public static void main(String[] args) { | public static void main(String[] args) { | ||
Car car = new Car(); | Car car = new Car(); | ||
- | FerrariEngine ferrariEngine = new FerrariEngine(wi); | + | OttoEngine ottoEngine = new OttoEngine(car); |
- | ferrariEngine.getFuelCapacity(); | + | ottoEngine.getFuelCapacity(); |
} | } | ||
} | } | ||
</code> | </code> | ||
- | Observăm ca ''FerrariEngine'' moșteneste doar ''Car.Engine'' însa sunt necesare: | + | Observăm ca ''OttoEngine'' moșteneste doar ''Car.Engine'' însa sunt necesare: |
- | * parametrul constructorului ''FerrariEngine'' trebuie sa fie de tipul clasei externă (''Car'') | + | * parametrul constructorului ''OttoEngine'' trebuie sa fie de tipul clasei externă (''Car'') |
- | * linia din constructorul ''FerrariEngine'': ''car.super()''. | + | * linia din constructorul ''OttoEngine'': ''car.super()''. |
+ | |||
+ | <note> | ||
+ | * Rulați codul de mai sus | ||
+ | * Ștergeți constructorul ''OttoEngine()'', rulați din nou codul | ||
+ | </note> | ||
==Utilizarea claselor interne== | ==Utilizarea claselor interne== | ||
Line 391: | Line 303: | ||
} | } | ||
});</code> | });</code> | ||
- | == Exerciții == | + | == Exerciții == |
- | + | **Task 1 - Meta** (4p) | |
- | Implementați un terminal bash simplu pornind de la scheletul de cod. Comenzile pe care va știi să le execute sunt: **echo, cd, ls și history**. Bash-ul va citi comenzi de la tastatură până când va primi comanda **exit** când se va închide (programul se termină). | + | |
- | * În clasa **BashUtils** din pachetul **bash** vom implementa fiecare comandă ca o clasă internă. | + | |
- | * În clasa **Bash** din pachetul **bash** vom citi comenzi de la tastatură și le vom trimite spre **BashUtils** spre a fi executate. | + | |
- | * Mecanismul de funcționare va fi de tipul [[ https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern | Publisher-Subscriber ]]. | + | |
- | {{ :laboratoare:clase-interne:publish_subscribe.gif ? | Publisher-Subscriber }} | + | Îndrumarul este o componentă importantă a laboratorului de POO, iar citirea și înțelegerea acestuia nu trebuie neglijate. În îndrumarul din acest laborator există mai multe căsuțe de tip **note**, de forma celei de mai jos: |
- | * Concret: Bash-ul va citi comenzile de la tastatură și le va **publica** către toți subscriberii săi. Bash-ul va funcționa ca un **Publisher**. | + | <note> Do stuff </note> |
- | * Utilitarele, **ls, echo, cd și history**, care știu cum să execute comenzile se vor înregistra la **Publisher** folosind metoda **subscribe** a acestuia și vor fi anunțate când o nouă comandă este primită. Ele vor funcționa ca **Subscriberi**. | + | |
- | + | ||
- | {{ :laboratoare:clase-interne:publish_subscribe_details_v3.png ? | BashCommands Publisher-Subscriber }} | + | |
- | + | ||
- | * În scheletul de cod veți găsi 2 interfețe: | + | |
- | * **CommandPublisher** având metodele: | + | |
- | * subscribe(CommandSubscriber) | + | |
- | * publish(Command) | + | |
- | * **CommandSubscriber** având metoda: | + | |
- | * executeCommand(Command | + | |
- | * Observăm că un **CommandPublisher** face **publish** la un obiect de tip **Command**, iar un **CommandSubscriber** primește în metoda **executeCommand** un astfel de obiect ca parametru. | + | |
- | * Găsiți în scheletul de cod implementarea clasei **Command**. Acesta este obiectul prin care Publisherul și Subscriberii comunică aka își trimit date. | + | |
- | + | ||
- | 1. **(1.5p)** Din clasa **Bash** vom **publica** comenzile prin interfața **CommandPublisher**. În acest sens în clasa **Bash** vom crea o clasă internă **BashCommandPublisher** ce implementează interfața **CommandPublisher**. | + | |
- | * În această clasă creați o [[ https://docs.oracle.com/javase/8/docs/api/java/util/ArrayList.html | listă ]] de elemente de tip CommandSubscriber. | + | |
- | * Apoi va trebui să implementați metodele: | + | |
- | * **subscribe**-prin care adăugăm un subscriber în lista de subscrieri | + | |
- | * **publish**-în care iteram prin lista de subscriberi și trimitem evenimentul către subscriberi apelând metoda definită în interfața **CommandSubscriber** (în cazul de față **executeCommand**) | + | |
- | + | ||
- | 2. **(1p)** Un obiect de tipul **Bash** va avea: | + | |
- | * un director current reținut în membrul **currentDirectory** care este de tipul [[ https://docs.oracle.com/javase/8/docs/api/java/nio/file/Path.html | Path ]] | + | |
- | * un istoric al comenzilor care este de tipul [[ https://docs.oracle.com/javase/8/docs/api/java/lang/StringBuffer.html | StringBuffer ]] . | + | |
- | <note> De ce este mai util să folosim un StringBuffer și nu un String simplu? | + | |
- | [[ http://www.javaworld.com/article/2076072/build-ci-sdlc/stringbuffer-versus-string.html | Sting vs StringBuffer ]]? | + | |
- | </note> | + | |
- | * În constructorul clasei **Bash** vom inițializa **history** și apoi **currentDirectory** cu calea către directorul curent ".". Hint: [[ https://docs.oracle.com/javase/tutorial/essential/io/pathOps.html | Paths.get ]] | + | |
- | * Instantiati și obiectul de tip **CommandPublisher** definit la exercițiul anterior. Prin intermediul lui vom publica comenzi din **Bash** în sistem. | + | |
- | + | ||
- | 3. **(2p)** În metoda **start** din Bash vom citi de la tastatură comenzi pe câte o linie folosind [[https://docs.oracle.com/javase/8/docs/api/java/util/Scanner.html | Scanner]]. | + | |
- | * Când se citește string-ul **exit** programul se termină. | + | |
- | * Pentru orice altă comandă vom crea un nou [[https://docs.oracle.com/javase/tutorial/essential/concurrency/runthread.html | Java Thread]] de pe care vom **publica** comanda către subscriberi prin instanța de **CommandPublisher** creată la exercițiul 1. | + | |
- | * Creați și instantiati o clasă internă anonimă ce **extinde** clasa **Thread**. Clasa va trebui să implementeze metoda **run** prin care îi spunem thread-ului ce să facă (în cazul nostru să apeleze metoda **publish**). | + | |
- | <note important> | + | |
- | Pentru a porni un Thread apelăm metoda **start** a acestuia. Implementarea metodei **run** și instantierea Thread-ului nu îl lansează în execuție. | + | |
- | </note> | + | |
- | <code Java> | + | |
- | Thread t = new Thread() { | + | |
- | public void run() { | + | |
- | // Do some work | + | |
- | } | + | |
- | }; | + | |
- | t.start(); | + | Identificați toate căsuțele de tip **note** din laborator și urmați instrucțiunile din acestea. Puteți folosi scheletul pus la dispoziție. Pentru a ușura procesul de evaluare, creați fișiere separate pentru fiecare task din **note**. |
- | </code> | + | |
- | + | ||
- | 4. **(1p)** Implementați comanda **echo** ca o clasă internă în **BashUtils**. | + | |
- | * Clasa va trebui să implementeze interfața **CommandSubscriber**. | + | |
- | * Metoda **executeCommand** va trebui să: | + | |
- | * verifice dacă comanda primită începe cu "echo". Altfel nu va trebui să facă nimic. | + | |
- | * să afișeze la consolă șirul aflat după cuvântul cheie "echo" | + | |
- | * Creați un obiect de tipul clasei **Echo** în constructorul clasei **Bash**. Înregistrați obiectul ca **subscriber** la instanța de **CommandPublisher** folosind metoda **subscribe**. | + | |
- | * Testați rulând metoda **main** din clasa **LinuxOS**. | + | |
- | 5. **(1.5p)** Implementați comanda **cd** care schimbă directorul curent. | ||
- | * Clasa va trebui să implementeze interfața **CommandSubscriber**. | ||
- | * Metoda **executeCommand** va trebui să: | ||
- | * verifice dacă comanda primită începe cu "cd". | ||
- | * să schimbe variabila **currentDirectory** cu noua cale. Funcția **cd** va face **append** la calea deja existentă în **currentDirectory**. Hint: [[ https://docs.oracle.com/javase/tutorial/essential/io/pathOps.html | Paths.get ]] | ||
- | * Creați un obiect de tipul clasei **Cd** în constructorul clasei **Bash**. Înregistrați obiectul ca **subscriber** la instanța de **CommandPublisher** folosind metoda **subscribe**. | ||
- | * Testați rulând metoda **main** din clasa **LinuxOS**. | ||
- | 6. **(2p)** Implementați comanda **ls** care afișează conținutul directorului curent **currentDirectory** | + | **Task 2 - Car Dealership: The Beginning** (2p) |
- | * Clasa va trebui să implementeze interfața **CommandSubscriber**. | + | |
- | * Metoda **executeCommand** va trebui să: | + | |
- | * verifice dacă comanda primită este "ls" fără parametrii. | + | |
- | * să itereze prin conținutul directorului curent și să afișeze numele fișierelor la consolă, pe câte o linie fiecare. Hint: [[ http://stackoverflow.com/questions/1844688/read-all-files-in-a-folder | cum citim conținutul unui director în Java? ]] | + | |
- | * **currentDirectory** are tipul **Path**. Putem obține un obiect de tip **File** astfel: | + | |
- | <code java>File folder = currentDirectory.toFile();</code> | + | |
- | * Creați un obiect de tipul clasei **Ls** în constructorul clasei **Bash**. Înregistrați obiectul ca **subscriber** la instanța de **CommandPublisher** folosind metoda **subscribe**. | + | |
- | * Testați rulând metoda **main** din clasa **LinuxOS**. | + | |
- | 7. **(1p)** Implementați comanda **history** care afișează la consolă conținutul membrului **StringBuffer history** din **Bash**. | + | Scheletul de cod conține implementarea unui dealership de mașini. Acesta vinde autoturisme doar la cerere, conform cu tipul de mașină cerut de client. Totuși, patronul este nemulțumit. El ar dori ca: |
- | <code> | + | * toate tipurile de mașini vândute să nu fie vizibile decât de către angajați |
- | >history | + | * dealership-ul să rămână unul cu specific și tradiție, continuând să vândă veșnic tipurile curente de mașini |
- | History is: ls | | cd .idea | ls | history |</code> | + | |
- | * Clasa va trebui să implementeze interfața **CommandSubscriber**. | + | ''Hint: aceasta este o decizie de business teribilă din partea patronului, dar acesta v-a promis că veți putea închiria orice mașină de la el, dar doar după ce îl ajutați.'' |
- | * Metoda **executeCommand** va trebui să: | + | |
- | * să adauge fiecare comanda primită în StringBuffer-ul **history** Hint: [[https://docs.oracle.com/javase/8/docs/api/java/lang/StringBuffer.html | StringBuffer append]] | + | ** Task 3 - Car Dealership: The Crisis ** (2p) |
- | * dacă comandă primită este "history", să afișeze la consolă conținutul lui **history** | + | |
- | * Creați un obiect de tipul clasei **History** în constructorul clasei **Bash**. Înregistrați obiectul ca **subscriber** la instanța de **CommandPublisher** folosind metoda **subscribe**. | + | Timpul a trecut peste dealership, iar vânzările au fost chiar bune, cu o singură excepție, mașinile de tip ''RACING''. Aceste tipuri de mașini sunt făcute pe comandă, iar cererea a fost atât de mică, încât nici după un an nu s-au vândut cele din primul lot. Astfel, dealership-ul riscă să dea faliment, din cauza datoriilor generate (mașinile de tip ''RACING'' sunt și cele mai scumpe). |
- | * Testați rulând metoda **main** din clasa **LinuxOS**. | + | |
+ | Din fericire, patronul a făcut o înțelegere cu ''Ferrari'', de unde a și cumpărat mașinile. Aceștia îl vor scăpa complet de datorii dacă acesta va elimina orice asociere dintre ''Dealership'' și ''Ferrari'', lăsând totuși posibilitatea pentru clienți de a-și ridica mașinile de la ''Dealership''. Patronul este disperat și vă cere din nou ajutorul. Va trebui să eliminați orice asociere dintre cele două entități, păstrând funcționalitatea aplicației. | ||
+ | |||
+ | ''Hint: Clienții vor mai putea să își ridice mașinile Ferrari, dar acestea nu vor mai fi administrate de către Dealership.'' | ||
+ | |||
+ | ** Task 4 - Car Dealership: The New Age ** (2p) | ||
+ | |||
+ | Business-ul merge bine, astfel că patronul nostru s-a hotărât să se extindă. El vrea să aducă în ''Dealership'' mașini de tip ''ELECTRIC'', ''Tesla'', și a vorbit chiar cu **Elon Musk** în acest sens. Această extindere este una importantă, astfel că patronul nostru vrea o performanță cât mai ridicată. Patronul vă roagă să îl ajutați cu o comparație între: | ||
+ | |||
+ | * comandarea mașinilor ''Tesla'' folosind clase anonime | ||
+ | * comandarea mașinilor ''Tesla'' folosind funcții lambda | ||
+ | |||
+ | Un tutorial despre folosirea funcțiilor lambda îl găsiți în laborator, la secțiunea [[.:clase-interne#Clase anonime|Clase anonime]]. (opțional) Pentru o mică prezentare în discuția ''Anonymous vs. Lambda'', puteți verifica secțiunea de ''Referințe'' a laboratorului. | ||
== Resurse == | == Resurse == | ||
- | * {{.:clase-interne:clase-interne-skel.zip| Schelet}} | + | * {{:laboratoare:clase-interne:lab06-clase-interne-schelet.zip| Schelet}} |
- | * {{:laboratoare:clase-interne:lab5-sol.zip|Soluție}} | + | * [[.:old-exercises#clase-interne| Old exercises]] |
- | * <html><a class="media mediafile mf_pdf" href="/poo/laboratoare/clase-interne?do=export_pdf">PDF laborator</a></html> | + | |
== Referințe == | == Referințe == | ||
- Kathy Sierra, Bert Bates. //SCJP Sun Certified Programmer for Java™ 6 - Study Guide//. Chapter 8 - Inner Classes ([[http://firozstar.tripod.com/_darksiderg.pdf|available online]]) | - Kathy Sierra, Bert Bates. //SCJP Sun Certified Programmer for Java™ 6 - Study Guide//. Chapter 8 - Inner Classes ([[http://firozstar.tripod.com/_darksiderg.pdf|available online]]) | ||
+ | - [[https://www.oracle.com/technetwork/java/jvmls2013kuksen-2014088.pdf]] |