This shows you the differences between two versions of the page.
poo-ca-cd:arhiva:laboratoare:2023:constructori-referinte [2024/10/05 20:46] alexandru.olteanu created |
poo-ca-cd:arhiva:laboratoare:2023:constructori-referinte [2024/10/05 20:47] (current) alexandru.olteanu |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ===== Laboratorul 3: Agregare și moștenire ===== | + | ===== Laboratorul 2: Constructori și referințe ===== |
- | **Video introductiv:** [[https://www.youtube.com/watch?v=Gb-p4tMrdBM|link]] | + | **Video introductiv:** [[https://www.youtube.com/watch?v=Cz3sNXVrYrM&t|link]] |
==== Obiective ==== | ==== Obiective ==== | ||
- | Scopul acestui laborator este familiarizarea studenților cu noțiunile de **agregare** și de **moștenire** a claselor. | + | |
+ | Scopul acestui laborator este familiarizarea voastră cu noțiunile de **constructori** și de **referințe** în limbajul Java. | ||
Aspectele urmărite sunt: | Aspectele urmărite sunt: | ||
- | * studierea mecanismului de moștenire | + | * tipurile de contructori și crearea de instanţe ale claselor folosind acești constructori |
- | * înțelegerea diferenței între moștenire și agregare | + | * utilizarea cuvântului-cheie **this** |
- | * downcasting și upcasting | + | |
+ | ==== Constructori ==== | ||
- | ====Agregare și Compunere==== | + | Există uneori restricții de integritate care trebuie îndeplinite pentru crearea unui obiect. Java permite acest lucru prin existența noțiunii de **constructor**, împrumutată din C++. |
+ | Astfel, la crearea unui obiect al unei clase se apelează automat o funcție numită **constructor**. Constructorul are numele clasei, nu returnează explicit un tip anume (nici măcar ''void'') și poate avea oricâți parametri. | ||
- | Agregarea și compunerea se referă la prezența unei referințe pentru un obiect într-o altă clasă. Acea clasă practic va refolosi codul din clasa corespunzătoare obiectului. | + | Crearea unui obiect se face cu sintaxa: |
- | * ** Agregarea **(aggregation) - obiectul-container poate exista și în absența obiectelor agregate, de aceea este considerată o //asociere slabă// (//weak association//). În exemplul de mai jos, un raft de bibliotecă poate exista și fără cărți. | + | <code java5> |
- | * **Compunerea **(composition) - este o agregare //puternică// (//strong//), indicând că existența unui obiect este dependentă de un alt obiect. La dispariția obiectelor conținute prin compunere, existența obiectului container încetează. În exemplul de mai jos, o carte nu poate exista fără pagini. | + | class MyClass { |
+ | ... | ||
+ | } | ||
+ | ... | ||
- | **Inițializarea** obiectelor conținute poate fi făcută în 3 momente de timp distincte: | + | MyClass instanceObject; |
- | * la **definirea** obiectului (înaintea constructorului: folosind fie o valoare inițială, fie blocuri de inițializare) | + | |
- | * în cadrul **constructorului** | + | |
- | * chiar **înainte de folosire** (acest mecanism se numește inițializare leneșă (//lazy initialization//) | + | |
- | Exemple de cod: | + | // constructor call |
+ | instanceObject = new MyClass(param_1, param_2, ..., param_n); | ||
+ | </code> | ||
+ | |||
+ | Se poate combina declararea unui obiect cu crearea lui propriu-zisă printr-o sintaxă de tipul: | ||
- | Compunere: | + | <code java5> |
- | <code java> | + | // constructor call |
- | public class Foo { | + | MyClass instanceObject = new MyClass(param_1, param_2, ..., param_n); |
- | // Obiectul de tip Bar nu poate exista dacă obiectul Foo nu există | + | |
- | private Bar bar = new Bar(); | + | |
- | } | + | |
- | </code> | + | |
- | Agregare: | + | |
- | <code java> | + | |
- | public class Foo { | + | |
- | private Bar bar; | + | |
- | + | ||
- | // Obiectul de tip Bar poate continua să existe chiar dacă obiectul Foo nu există | + | |
- | Foo(Bar bar) { | + | |
- | this.bar = bar; | + | |
- | } | + | |
- | } | + | |
</code> | </code> | ||
- | Exemplu practic: | ||
- | <code java> | ||
- | class Page { | ||
- | private String content; | ||
- | public int numberOfPages; | ||
- | public Page(String content, int numberOfPages) { | + | De reținut că, în terminologia POO, obiectul creat în urma apelului unui constructor al unei clase poartă numele de **instanță** a clasei respective. Astfel, spunem că ''instanceObject'' reprezintă o ''instanţă'' a clasei ''MyClass''. |
- | this.content = content; | + | |
- | this.numberOfPages = numberOfPages; | + | |
- | } | + | |
- | } | + | |
- | class Book { | + | Să urmărim în continuare codul: |
- | private String title; // Compunere | + | |
- | private Page[] pages; // Compunere | + | |
- | private LibraryRow libraryRow = null; // Agregare | + | |
- | + | ||
- | public Book(int size, String title, LibraryRow libraryRow) { | + | |
- | this.libraryRow = libraryRow; | + | |
- | this.title = title; | + | |
- | + | ||
- | pages = new Page[size]; | + | |
- | + | ||
- | for (int i = 0; i < size; i++) { | + | |
- | pages[i] = new Page("Page " + i, i); | + | |
- | } | + | |
- | } | + | |
- | } | + | |
- | class LibraryRow { | + | <code java5> |
- | private String rowName = null; // Agregare | + | String myFirstString, mySecondString; |
- | + | ||
- | public LibraryRow(String rowName) { | + | |
- | this.rowName = rowName; | + | |
- | } | + | |
- | } | + | |
- | class Library { | + | myFirstString = new String(); |
- | + | mySecondString = "This is my second string"; | |
- | public static void main(String[] args) { | + | |
- | LibraryRow row = new LibraryRow("a1"); | + | |
- | Book book = new Book(100, "title", row); | + | |
- | + | ||
- | // După ce nu mai există nici o referință la obiectul Carte, | + | |
- | // Garbage Collector-ul va șterge (la un moment dat, nu | + | |
- | // neapărat imediat) acea instanță, dar obiectul LibraryRow | + | |
- | // transmis constructorului nu este afectat. | + | |
- | + | ||
- | book = null; | + | |
- | } | + | |
- | } | + | |
</code> | </code> | ||
- | ====Moștenire (Inheritance)==== | ||
- | Numită și **derivare**, moștenirea este un mecanism de refolosire a codului specific limbajelor orientate obiect și reprezintă posibilitatea de a defini o clasă care **extinde** o altă clasă deja existentă. Ideea de bază este de a **prelua** funcționalitatea existentă într-o clasă și de a **adăuga** una nouă sau de a o **modela** pe cea existentă. | + | Acesta creează întâi un obiect de tip ''String'' folosind constructorul fără parametru (alocă spațiu de memorie și efectuează inițializările specificate în codul constructorului), iar apoi creează un alt obiect de tip ''String'' pe baza unui șir de caractere constant. |
- | + | ||
- | Clasa existentă este numită **clasa-părinte**, **clasa de bază** sau **super-clasă**. Clasa care extinde clasa-părinte se numește **clasa-copil (child)**, **clasa derivată** sau **sub-clasă**. | + | |
- | <note important>Spre deosebire de C++, Java nu permite //moștenire multiplă// (//multiple inheritance//), astfel că nu putem întâlni ambiguități de genul [[https://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem|Problema Rombului / Diamond Problem]]. Mereu când vom vrea să ne referim la metoda părinte (folosind cuvântul cheie ''super'', [[:poo-ca-cd:laboratoare:agregare-mostenire#cuvantul_cheie_super_intrebuintari|cum vom vedea mai jos]]), acel părinte este unic determinat.</note> | + | Clasele pe care le-am creat până acum însă nu au avut nici un constructor. În acest caz, Java crează automat un **constructor implicit** (în terminologia POO, **default constructor**) care face iniţializarea câmpurilor neinițializate, astfel: |
- | ====Agregare vs. moștenire==== | + | * referințele la obiecte se inițializează cu ''null'' |
+ | * variabilele de tip numeric se inițializează cu ''0'' | ||
+ | * variabilele de tip logic (''boolean'') se inițializează cu ''false'' | ||
- | **//Când se folosește moștenirea și când agregarea?//** | + | Pentru a exemplifica acest mecanism, să urmărim exemplul: |
- | + | ||
- | Răspunsul la această întrebare depinde, în principal, de datele problemei analizate dar și de concepția designerului, neexistând o rețetă general valabilă în acest sens. | + | |
- | În general, **agregarea** este folosită atunci când se dorește folosirea trăsăturilor unei clase în interiorul altei clase, dar nu și interfața sa (prin moștenire, noua clasă ar expune și metodele clasei de bază). Putem distinge două cazuri: | + | |
- | * uneori se dorește implementarea funcționalității obiectului conținut în noua clasă și **limitarea** acțiunilor utilizatorului doar la metodele din noua clasă (mai exact, se dorește să nu se permită utilizatorului folosirea metodelor din vechea clasă). Pentru a obține acest efect se va **agrega** în noua clasă un obiect de tipul clasei conținute și având specificatorul de acces ''private''. | + | |
- | * obiectul conținut (agregat) trebuie/se dorește a fi accesat **direct**. În acest caz vom folosi specificatorul de acces ''public''. Un exemplu în acest sens ar fi o clasă numită ''Car'' care conține ca membrii publici obiecte de tip ''Engine'', ''Wheel'' etc. | + | |
- | + | ||
- | **Moștenirea** este un mecanism care permite crearea unor versiuni "specializate" ale unor clase existente (de bază). Moștenirea este folosită în general atunci când se dorește construirea unui tip de date care să reprezinte o implementare specifică (o specializare oferită prin clasa derivată) a unui lucru mai general. Un exemplu simplu ar fi clasa ''Dacia'' care moștenește clasa ''Car''. | + | |
- | + | ||
- | **Diferența** dintre moștenire și agregare este de fapt diferența dintre cele 2 tipuri de relații majore prezente între obiectele unei aplicații : | + | |
- | * **is a** - indică faptul că o clasă este derivată dintr-o clasă de bază (intuitiv, dacă avem o clasă ''Animal'' și o clasă ''Dog'', atunci ar fi normal să avem ''Dog'' derivat din ''Animal'', cu alte cuvinte ''Dog'' //is an// ''Animal'') | + | |
- | * **has a** - indică faptul că o clasă-container are o clasă conținută în ea (intuitiv, dacă avem o clasă ''Car'' și o clasă ''Engine'', atunci ar fi normal să avem ''Engine'' referit în cadrul ''Car'', cu alte cuvinte ''Car'' //has a// ''Engine'') | + | |
- | | + | |
+ | <code java5 SomeClass.java> | ||
+ | public class SomeClass { | ||
- | ====Upcasting și Downcasting==== | + | private String name = "Some Class"; |
- | **Convertirea** unei referințe la o clasă derivată într-una a unei clase de bază poartă numele de **upcasting**. Upcasting-ul este făcut **automat** și **nu** trebuie declarat explicit de către programator. | + | public String getName() { |
- | + | return name; | |
- | {{:poo-ca-cd:laboratoare:agregare-mostenire:upcast-downcast.png?nolink&400|}} | + | |
- | + | ||
- | + | ||
- | Exemplu de upcasting: | + | |
- | + | ||
- | <code java> | + | |
- | class Instrument { | + | |
- | public void play() {} | + | |
- | + | ||
- | static void tune(Instrument i) { | + | |
- | i.play(); | + | |
- | } | + | |
- | } | + | |
- | + | ||
- | // Obiectele Wind sunt instrumente | + | |
- | // deoarece au ca și clasa-parinte clasa Instrument | + | |
- | public class Wind extends Instrument { | + | |
- | public static void main(String[] args) { | + | |
- | Wind flute = new Wind(); | + | |
- | Instrument.tune(flute); // !! Upcasting automat pentru că metoda primește | + | |
- | // un obiect de tip Instrument, nu un obiect de tip Wind | + | |
- | // Deci ar fi redundant să faci un cast explicit cum ar fi: | + | |
- | // Instrument.tune((Instrument) flute) | + | |
} | } | ||
} | } | ||
- | </code> | ||
- | + | class Test { | |
- | Deși obiectul ''flute'' este o instanță a clasei ''Wind'', acesta este pasat ca parametru în locul unui obiect de tip ''Instrument'', care este o superclasa a clasei ''Wind''. Upcasting-ul se face la pasarea parametrului. Termenul de **upcasting** provine din diagramele de clase (în special [[http://en.wikipedia.org/wiki/Unified_Modeling_Language | UML]]) în care moștenirea se reprezintă prin 2 blocuri așezate unul sub altul, reprezentând cele 2 clase (sus este clasa de bază iar jos clasa derivată), unite printr-o săgeată orientată spre clasa de bază. | + | |
- | + | ||
- | **Downcasting** este operația **inversă** upcast-ului și este o conversie explicită de tip în care se merge în **jos** pe ierarhia claselor (se convertește o clasă de bază într-una derivată). Acest cast trebuie făcut **explicit** de către programator. Downcasting-ul este **posibil** numai dacă obiectul declarat ca fiind de o clasă de bază este, de fapt, instanță clasei derivate către care se face downcasting-ul. | + | |
- | + | ||
- | Iată un exemplu în care este folosit downcasting: | + | |
- | + | ||
- | <code java5> | + | |
- | class Animal { | + | |
- | public void eat() { | + | |
- | System.out.println("Animal eating"); | + | |
- | } | + | |
- | } | + | |
- | + | ||
- | class Wolf extends Animal { | + | |
- | public void howl() { | + | |
- | System.out.println("Wolf howling"); | + | |
- | } | + | |
- | + | ||
- | public void eat() { | + | |
- | System.out.println("Wolf eating"); | + | |
- | } | + | |
- | } | + | |
- | + | ||
- | class Snake extends Animal { | + | |
- | public void bite() { | + | |
- | System.out.println("Snake biting"); | + | |
- | } | + | |
- | } | + | |
- | public class Test { | ||
public static void main(String[] args) { | public static void main(String[] args) { | ||
- | Animal[] animals = new Animal[2]; | + | SomeClass instance = new SomeClass(); |
- | + | System.out.println(instance.getName()); | |
- | animals[0] = new Wolf(); // Upcasting automat | + | |
- | animals[1] = new Snake(); // Upcasting automat | + | |
- | + | ||
- | for (int i = 0; i < animals.length; i++) { | + | |
- | animals[i].eat(); // 1 | + | |
- | + | ||
- | if (animals[i] instanceof Wolf) { | + | |
- | ((Wolf)animals[i]).howl(); // 2 | + | |
- | } | + | |
- | + | ||
- | if (animals[i] instanceof Snake) { | + | |
- | ((Snake)animals[i]).bite(); // 3 | + | |
- | } | + | |
- | } | + | |
} | } | ||
} | } | ||
</code> | </code> | ||
- | Codul va afișa: | + | La momentul execuției, în consolă se va afișa ''"Some Class"'' și nu se va genera nici o eroare la compilare, deși în clasa ''SomeClass'' nu am declarat explicit un constructor de forma: |
<code java5> | <code java5> | ||
- | Wolf eating | + | public SomeClass() { |
- | Wolf howling | + | ... // variables initialization |
- | Animal eating | + | } |
- | Snake biting | + | |
</code> | </code> | ||
- | + | Să vedem acum un exemplu general: | |
- | În liniile marcate cu **2** și **3** se execută un downcast de la ''Animal'' la ''Wolf'', respectiv ''Snake'' pentru a putea fi apelate metodele specifice definite în aceste clase. Înaintea execuției downcast-ului (conversia de tip la Wolf respectiv Snake) verificăm dacă obiectul respectiv este de tipul dorit (utilizând operatorul ''**instanceof**''). Dacă am încerca să facem downcast către tipul ''Wolf'' al unui obiect instanțiat la ''Snake'' mașina virtuală ar semnala acest lucru aruncând o excepție la rularea programului. | + | |
- | + | ||
- | Apelarea metodei ''eat()'' (linia **1**) se face direct, fără downcast, deoarece această metodă este definită și în clasa de bază ''Animal''. Datorită faptului că ''Wolf'' suprascrie (//overrides//) metoda ''eat()'', apelul ''a[0].eat()'' va afișa "Wolf eating". Apelul ''a[1].eat()'' va apela metoda din clasă de bază (la ieșire va fi afișat "Animal eating") deoarece ''a[1]'' este instanțiat la ''Snake'', iar ''Snake'' nu suprascrie metoda ''eat()''. | + | |
- | + | ||
- | <note important>Upcasting-ul este un element foarte important. De multe ori răspunsul la întrebarea: //este nevoie de moștenire?// este dat de răspunsul la întrebarea: //am nevoie de upcasting?// Aceasta deoarece upcasting-ul se face atunci când pentru unul sau mai multe obiecte din clase derivate se execută aceeași metodă definită în clasa părinte.</note> | + | |
- | ===Să încercăm să evităm folosirea instanceof=== | + | <code java5 Student.java> |
+ | public class Student { | ||
- | Totuși, deși v-am ilustrat cum ''instanceof'' ne poate ajuta să ne dăm seama la ce să facem **downcasting**, este de preferat să ne organizăm clasele și designul codului în așa fel încât să lăsăm limbajul Java să facă automat verificarea tipului și să cheme metoda corespunzătoare. Vom refactoriza codul anterior pentru a nu fi nevoie de ''instanceof'': | + | private String name; |
+ | public int averageGrade; | ||
- | <code java5> | + | // (1) constructor without parameters |
- | class Animal { | + | public Student() { |
- | public void eat() { | + | name = "Unknown"; |
- | System.out.println("Animal eating"); | + | averageGrade = 5; |
} | } | ||
- | | + | |
- | public void action() { | + | // (2) constructor with two parameters; used to set the name and the grade |
- | // avem nevoie de această metodă deoarece vom crea un vector | + | public Student(String n, int avg) { |
- | // cu instanțe Animal și vom apela această metodă pe ele | + | name = n; |
+ | averageGrade = avg; | ||
} | } | ||
- | } | ||
- | class Wolf extends Animal { | + | // (3) constructor with one parameter; used to set only the name |
- | public void action() { | + | public Student(String n) { |
- | System.out.println("Wolf howling"); | + | this(n, 5); // call the second constructor (2) |
} | } | ||
- | public void eat() { | + | // (4) setter for the field 'name' |
- | System.out.println("Wolf eating"); | + | public void setName(String n) { |
+ | name = n; | ||
} | } | ||
- | } | ||
- | class Snake extends Animal { | + | // (5) getter for the field 'name' |
- | public void action() { | + | public String getName() { |
- | System.out.println("Snake biting"); | + | return name; |
} | } | ||
} | } | ||
+ | </code> | ||
- | class Test { | + | Declararea unui obiect de tip ''Student'' se face astfel: |
- | public static void main(String[] args) { | + | |
- | Animal a [] = new Animal[2]; | + | |
- | + | ||
- | a[0] = new Wolf(); | + | |
- | a[1] = new Snake(); | + | |
- | for (int i = 0; i < a.length; i++) { | + | <code java5> |
- | a[i].eat(); | + | Student st; |
- | + | ||
- | // acum că ele sunt numite la fel, putem apela metoda action | + | |
- | // din clasa Animal (observați de ce a fost nevoie să definim | + | |
- | // metoda action în clasa Animal), iar metoda corespunzătoare | + | |
- | // va fi apelată pentru tipul specific al instanței a[i] | + | |
- | + | ||
- | a[i].action(); | + | |
- | } | + | |
- | } | + | |
- | } | + | |
</code> | </code> | ||
- | Codul va afișa: | + | Crearea unui obiect ''Student'' se face **obligatoriu** prin apel la unul din cei 3 constructori de mai sus: |
<code java5> | <code java5> | ||
- | Wolf eating | + | st = new Student(); // first constructor call (1) |
- | Wolf howling | + | st = new Student("Gigel", 6); // second constructor call (2) |
- | Animal eating | + | st = new Student("Gigel"); // third constructor call (3) |
- | Snake biting | + | |
</code> | </code> | ||
- | |||
- | ===De ce este instanceof considerat bad practice?=== | ||
- | * face codul repetitiv - downcasting și apelare de metodă la fiecare branch de if/else | ||
- | * face codul mai greu de întreținut pe termen lung - codul principal trebuie updatat de fiecare dată când se introduce o nouă subclasă (vezi exemplul de mai sus - introducerea unui noi clase derivate din Animal va determina un alt branch de if/else) | ||
- | * face codul mai dificil de citit - este mult mai ușor să ne uităm la metodele suprascrise cu tag-ul @Override decât să căutam fiecare metoda cu un nume diferit pe rând | ||
- | * distruge designul orientat-obiect din perspectiva polimorfismului | ||
- | * încalcă [[https://www.baeldung.com/java-open-closed-principle|open-closed principle]] | ||
- | * nu poate fi folosit cu tipuri generice, ''instanceof'' fiind un **type comparison operator** ce va compara la runtime, în schimb genericitatea fiind rezolvată la compile-time. Mai multe detalii [[https://www.baeldung.com/java-instanceof#generics|aici]] si in [[https://ocw.cs.pub.ro/courses/poo-ca-cd/laboratoare/genericitate|laboratorul 10]] despre genericitate. | ||
- | |||
- | **Soluții** pentru evitarea ''instanceof'': | ||
- | * polimorfism | ||
- | * [[https://ocw.cs.pub.ro/courses/poo-ca-cd/laboratoare/visitor|Visitor]] | ||
- | |||
<note important> | <note important> | ||
- | Când este necesar să folosim ''instanceof''? | + | **Atenție!** Dacă într-o clasă se definesc **doar** constructori cu parametri, constructorul default, fără parametri, **nu** va mai fi vizibil! Exemplul următor va genera eroare la compilare: |
- | + | ||
- | ''instanceof'' poate fi folosit când nu controlăm ierarhia claselor și suntem obligați să facem | + | |
- | testarea tipului deoarece nu putem aplica polimorfismul - clasele provin dintr-o librărie externă. | + | |
</note> | </note> | ||
- | | + | <code java5> |
- | ====Implicații ale moștenirii==== | + | class Student { |
- | În Java, clasele și membrii acestora (metode, variabile, clase interne) pot avea diverși specificatori de acces, prezentați pe wiki în [[:poo-ca-cd:laboratoare:organizare-acces | Organizarea surselor și controlul accesului]]. | + | |
- | * specificatorul de acces **''protected''** - specifică faptul că membrul sau metoda respectivă poate fi accesată doar din cadrul clasei înseși sau din clasele derivate din această clasă. Clasele nu pot avea acest specificator, doar membrii acestora! | + | |
- | * specificatorul de acces **''private''** - specifică faptul că membrul sau metoda respectivă poate fi accesată doar din cadrul **clasei** înseși, nu și din clasele derivate din această clasă. Clasele nu pot avea acest specificator, doar membrii acestora! | + | |
- | Constructorii **nu** se moștenesc și pot fi apelați doar în contextul unui constructor copil. Apelurile de constructor sunt înlănțuite, ceea ce înseamnă că înainte de a se inițializa obiectul copil, mai întâi se va inițializa obiectul părinte. În cazul în care părintele este copil la rândul lui, se va înițializa părintele lui (până se va ajunge la parintele suprem -- root). | + | private String name; |
+ | public int averageGrade; | ||
- | Pe lângă reutilizarea codului, moștenirea dă posibilitatea de a dezvolta pas cu pas o aplicație (procedeul poartă numele de //incremental development//). Astfel, putem folosi un cod deja funcțional și adaugă alt cod nou la acesta, în felul acesta izolându-se bug-urile în codul nou adăugat. Pentru mai multe informații citiți capitolul //Reusing Classes// din cartea //Thinking în Java (Bruce Eckel)// | + | public Student(String n, int avg) { |
- | + | name = n; | |
- | ==== Suprascrierea, supraîncărcarea si ascunderea metodelor statice ==== | + | averageGrade = avg; |
- | **Suprascrierea** (//overriding//) presupune __înlocuirea__ funcționalității din clasa/clasele părinte pentru instanța curentă. | + | |
- | **Supraîncărcarea** (//overloading//) presupune __furnizarea__ de funcționalitate în plus, fie pentru metodele din clasa curentă, fie pentru clasa/clasele părinte. | + | |
- | + | ||
- | <code java> | + | |
- | public class Car { | + | |
- | public void print() { | + | |
- | System.out.println("Car"); | + | |
} | } | ||
- | | ||
- | public void init() { | ||
- | System.out.println("Car"); | ||
- | } | ||
- | | ||
- | public void addGasoline() { | ||
- | // do something | ||
- | } | ||
- | } | ||
- | class Dacia extends Car { | + | public static void main(String[] args) { |
- | public void print() { | + | // ERROR: the implicit constructor is hidden by the constructor with parameters |
- | System.out.println("Dacia"); | + | Student s = new Student(); |
- | } | + | |
- | + | ||
- | public void init() { | + | |
- | System.out.println("Dacia"); | + | |
- | } | + | |
- | + | ||
- | // Exemplu de suprascriere | + | |
- | public void addGasoline() { | + | |
- | // do something | + | |
- | } | + | |
- | + | ||
- | // Exemplu de supraîncărcare | + | |
- | public void addGasoline(Integer gallons) { | + | |
- | // do something | + | |
} | } | ||
} | } | ||
</code> | </code> | ||
- | Metodele dependente de instanță sunt polimorfice (la runtime pot avea diferite implementări) deci ele pot fi suprascrise sau supraîncarcăte. Metoda ''print'' este **__suprascrisă__** în clasa ''Dacia'' ceea ce înseamnă că orice instanță, chiar dacă se face cast la tipul ''Car'' metoda ce se va apela va fi mereu metoda ''print'' din clasa ''Dacia''. Metoda ''addGasoline'' este **__supraîncărcată__** ceea ce înseamnă că putem executa metode cu semnături diferite dar același nume (cel mai folosit in crearea metodelor de conversie). | + | În Java, există conceptul de **copy constructor**, acesta reprezentând un constructor care ia ca parametru un obiect de același tip cu clasa în care se află constructorul respectiv. Cu ajutorul acestui constructor, putem să copiem obiecte, prin copierea membru cu membru în constructor. |
- | <code java> | + | <code java5> |
- | Car a = new Car(); | + | public class Student { |
- | Car b = new Dacia(); | + | private String name; |
- | Dacia c = new Dacia(); | + | private int averageGrade; |
- | Car d = null; | + | |
- | a.print(); // afișează Car | + | public Student(String name, int averageGrade) { |
- | b.print(); // afișează Dacia | + | this.name = name; |
- | c.print(); // afișează Dacia | + | this.averageGrade = averageGrade; |
- | d.print(); // aruncă NullPointerException | + | } |
- | </code> | + | |
- | + | ||
- | Suprascrierea nu se aplică și metodelor statice pentru că ele nu sunt dependente de instanță. Dacă în exemplul de mai sus facem metodele print din Car și din Dacia statice, rezultatul va fi următorul: | + | |
- | + | ||
- | <code java> | + | |
- | Car a = new Car(); | + | |
- | Car b = new Dacia(); | + | |
- | Dacia c = new Dacia(); | + | |
- | Car d = null; | + | |
- | + | ||
- | a.print(); // afișează Car | + | |
- | b.print(); // afișează Car pentru că tipul dat la inițializare al lui b este Car | + | |
- | c.print(); // afișează Dacia pentru că tipul dat la inițializare al lui c este Dacia | + | |
- | d.print(): // afișează Car pentru că tipul dat la inițializare al lui d este Car | + | |
- | </code> | + | |
- | + | ||
- | <note important>O să punem accent pe aceste concepte în [[:poo-ca-cd:laboratoare:visitor|laboratorul visitor]]</note> | + | |
- | + | ||
- | <note warning>Sintaxa Java permite apelarea metodelor statice pe instanțe (e.g. a.print în loc de Car.print), dar acest lucru este considerat bad practice pentru că poate îngreuna înțelegerea codului.</note> | + | |
- | + | ||
- | ===Suprascrierea corectă a metodei equals(Object o)=== | + | |
- | + | ||
- | Una din problemele cele mai des întâlnite este suprascrierea corectă a metodei //equals//. Mai jos putem vedea un exemplu de suprascriere incorectă a acestei metode. | + | |
- | <code java> | + | // copy constructor |
- | public class Car { | + | public Student(Student student) { |
- | public boolean equals(Car c) { | + | // name este camp privat, noi il putem accesa direct (student.name) |
- | System.out.println("Car"); | + | // deoarece ne aflam in interiorul clasei |
- | return true; | + | this.name = student.name; |
- | } | + | this.averageGrade = student.averageGrade; |
- | + | ||
- | public boolean equals(Object o) { | + | |
- | System.out.println("Object"); | + | |
- | return false; | + | |
} | } | ||
} | } | ||
</code> | </code> | ||
- | Prima metodă este o **supraîncărcare** a metodei equals iar a doua metodă este **suprascrierea** metodei equals. | + | ==== Referințe. Implicații în transferul de parametri ==== |
+ | |||
+ | Obiectele se alocă pe ''heap''. Pentru ca un obiect să poată fi folosit, este necesară cunoașterea adresei lui. Această adresă, așa cum știm din limbajul C, se reține într-un **pointer**. | ||
- | <code java> | + | Limbajul Java nu permite lucrul direct cu pointeri, deoarece s-a considerat că această facilitate introduce o complexitate prea mare, de care programatorul poate fi scutit. Totuși, în Java există noțiunea de **referinţe** care înlocuiesc pointerii, oferind un mecanism de gestiune transparent. |
- | Car a = new Car(); | + | |
- | Dacia b = new Dacia(); | + | |
- | int c = 10; | + | |
- | a.equals(a); // afișează Car | + | Astfel, declararea unui obiect: |
- | a.equals(b); // afișează Car deoarece se face upcasting de la Dacia la Car | + | |
- | a.equals(c); // afișează Object deoarece se face upcasting de la Int la Object | + | |
- | </code> | + | |
- | Problema care se poate observa este că putem pasa ca argumente metodei equals si tipuri de date diferite de ''Car'', lucru ce ar putea arunca excepții de cast sau când vrem să accesăm anumite proprietăți din instanță. Mai jos este modul corect de a suprascrie metoda equals. | + | <code java5> |
+ | Student st; | ||
+ | </code> | ||
- | <code java> | + | creează o referință care poate indica doar către o zonă de memorie inițializată cu patternul clasei ''Student'' fără ca memoria respectivă să conțină date utile. Astfel, dacă după declarație facem un acces la un câmp sau apelăm o funcție-membru, compilatorul va semnala o eroare, deoarece referința nu indică încă spre vreun obiect din memorie. Alocarea efectivă a memoriei și inițializarea acesteia se realizează prin apelul constructorului împreună cu cuvântul-cheie **new**. |
- | public class Car { | + | |
- | public boolean equals(Car c) | + | |
- | { | + | |
- | return true; | + | |
- | } | + | |
- | public boolean equals(Object o) | + | Managementul transparent al pointerilor implică un proces automat de alocare și eliberare a memoriei. Eliberarea automată poartă și numele de **Garbage Collection**, iar pentru Java există o componentă separată a JRE-ului care se ocupă cu eliberarea memoriei ce nu mai este utilizată. |
- | { | + | |
- | if (o == this) { | + | |
- | return true; | + | |
- | } | + | |
- | if (!(o instanceof Car)) { | + | Un fapt ce merită discutat este semnificația **atribuirii** de referințe. În exemplul de mai jos: |
- | return false; | + | |
- | } | + | |
- | return equals((Car) o); | + | <code java5> |
- | } | + | Student s1 = new Student("Bob", 6); |
- | } | + | |
- | </code> | + | |
- | De reținut că folosirea ''instanceof'' nu este recomandată, însă în acest caz este singurul mod prin care ne putem asigura ca instanța de obiect trimisă metodei este de tip ''Car''. | + | Student s2 = s1; |
+ | s2.averageGrade = 10; | ||
- | ====Cuvântul cheie super. Întrebuințări==== | + | System.out.println(s1.averageGrade); |
+ | </code> | ||
- | Cuvântul cheie ''super'' se referă la instanța părinte a clasei curente. Acesta poate fi folosit în două moduri: apelând o metoda suprascrisă (//overriden//) sau apelând constructorul părinte. | + | se va afișa **10**. |
- | === Apelând o metodă suprascrisă === | + | <note> |
+ | Motivul este că **s1** și **s2** sunt două referințe către **ACELASI** obiect din memorie. Orice modificare făcută asupra acestuia prin una din referințele sale va fi vizibilă în urma accesului prin orice altă referință către el. | ||
+ | </note> | ||
- | <code java5> | + | În concluzie, atribuirea de referințe **nu** creează o copie a obiectului, cum s-ar fi putut crede inițial. Efectul este asemănător cu cel al atribuirii de pointeri în C. |
- | public class Superclass { | + | |
- | public void printMethod() { | + | **Transferul parametrilor** la apelul de funcții este foarte important de înțeles. Astfel: |
- | System.out.println("Printed in Superclass."); | + | * pentru tipurile primitive se transfera prin **COPIERE** pe stivă: orice modificare în funcția apelată **NU VA FI VIZIBILĂ** în urma apelului. |
- | } | + | * pentru tipurile definite de utilizator și instanțe de clase în general, se **COPIAZĂ REFERINȚA** pe stivă: referința indică spre zona de memorie a obiectului, astfel că schimbările asupra câmpurilor **VOR FI VIZIBILE** după apel, dar reinstanțieri (expresii de tipul: ''st = new Student()'') în apelul funcției și modificările făcute după ele, **NU VOR FI VIZIBILE** după apel, deoarece ele modifică o copie a referinței originale. |
- | } | + | |
- | public class Subclass extends Superclass { | + | <code java5 TestParams.java> |
- | // overrides printMethod in Superclass | + | class TestParams { |
- | public void printMethod() { | + | |
- | super.printMethod(); // apelează metoda părinte | + | static void changeReference(Student st) { |
- | + | st = new Student("Bob", 10); | |
- | System.out.println("Printed in Subclass."); | + | |
} | } | ||
- | | ||
- | public static void main(String[] args) { | ||
- | Subclass s = new Subclass(); | ||
- | s.printMethod(); | ||
- | } | ||
- | } | ||
- | </code> | ||
- | Codul va afișa: | + | static void changeObject(Student st) { |
- | + | st.averageGrade = 10; | |
- | <code java5> | + | |
- | Printed in Superclass. | + | |
- | Printed in Subclass. | + | |
- | </code> | + | |
- | + | ||
- | === Apelând constructorul părinte === | + | |
- | + | ||
- | <code java5> | + | |
- | class Superclass { | + | |
- | public Superclass() { | + | |
- | System.out.println("Printed in Superclass constructor with no args."); | + | |
} | } | ||
- | | ||
- | public Superclass(int a) { | ||
- | System.out.println("Printed in Superclass constructor with one integer argument."); | ||
- | } | ||
- | } | ||
- | class Subclass extends Superclass { | ||
- | public Subclass() { | ||
- | super(); // apelează constructorul părinte | ||
- | // acest apel trebuie să fie pe prima linie a constructorului !! | ||
- | | ||
- | System.out.println("Printed in Subclass constructor with no args."); | ||
- | } | ||
- | | ||
- | public Subclass(int a) { | ||
- | super(a); // apelează constructorul părinte | ||
- | // acest apel trebuie să fie pe prima linie a constructorului !! | ||
- | | ||
- | System.out.println("Printed in Subclass constructor with one integer argument."); | ||
- | } | ||
- | | ||
public static void main(String[] args) { | public static void main(String[] args) { | ||
- | Subclass s1 = new Subclass(20); | + | Student s = new Student("Alice", 5); |
- | Subclass s2 = new Subclass(); | + | changeReference(s); // apel (1) |
+ | System.out.println(s.getName()); // "Alice" | ||
+ | |||
+ | changeObject(s); // apel (2) | ||
+ | System.out.println(s.averageGrade); // "10" | ||
} | } | ||
} | } | ||
</code> | </code> | ||
+ | | ||
+ | Astfel, apelul (1) nu are nici un efect în metoda ''main'' pentru că metoda ''changeReference'' are ca efect asignarea unei noi valori referinței ''s'', copiată pe stivă. Se va afișa textul: ''Alice''. | ||
+ | {{ poo-ca-cd:laboratoare:constructori-referinte:poza1.png?600 |}} | ||
- | Codul va afișa: | + | Apelul (2) metodei ''changeObject'' are ca efect modificarea structurii interne a obiectului referit de ''s'' prin schimbarea valorii atributului ''averageGrade''. Se va afișa textul: ''10''. |
+ | {{ poo-ca-cd:laboratoare:constructori-referinte:poza2.png?600 |}} | ||
- | <code java5> | + | ==== Cuvântul-cheie "this". Întrebuințări ==== |
- | Printed in Superclass constructor with one integer argument. | + | |
- | Printed in Subclass constructor with one integer argument. | + | |
- | Printed in Superclass constructor with no args. | + | |
- | Printed in Subclass constructor with no args. | + | |
- | </code> | + | |
- | <note important>Invocarea constructorului părinte **trebuie** să fie prima linie dintr-un constructor al unei subclase, dacă invocarea părintelui există (se poate foarte bine să nu apelăm ''super'' din constructor).</note> | + | Cuvântul cheie ''this'' se referă la instanța curentă a clasei și poate fi folosit de metodele, care nu sunt statice, ale unei clase pentru a referi obiectul curent. Apelurile de funcții membru din interiorul unei funcții aparținând aceleiași clase se fac direct prin nume. Apelul de funcții aparținând unui alt obiect se face prefixând apelul de funcție cu numele obiectului. Situația este aceeași pentru datele membru. |
+ | |||
+ | Totuși, unele funcții pot trimite un parametru cu același nume ca și un câmp membru. În aceste situații, se folosește cuvântul cheie ''this'' pentru //dezambiguizare//, el prefixând denumirea câmpului când se dorește utilizarea acestuia. Acest lucru este necesar pentru că în Java este comportament default ca un nume de parametru să ascundă numele unui câmp. | ||
- | <note important>Chiar dacă nu se specifică apelul metodei ''super()'', compilatorul va apela automat constructor-ul implicit al părintelui însă dacă se dorește apelarea altui constructor, apelul de ''super(args)'' respectiv este obligatoriu</note> | + | În general, cuvântul cheie ''this'' este utilizat pentru: |
- | ====Utilizarea clasei ArrayList. Exemple==== | + | * a se face ''diferența'' între câmpuri ale obiectului curent și argumente care au același nume |
+ | * a pasa ca ''argument'' unei metode o referință către obiectul curent (vezi linia (1) din exemplul următor) | ||
+ | * a facilita apelarea ''constructorilor'' din alți constructori, evitându-se astfel replicarea unor bucăți de cod (vezi exemplul de la constructori) | ||
- | Clasa ArrayList este un array redimensionabil, iar această clasă poate fi găsită în pachetul ''java.util''. | + | Exemplul următor, în care vom adăuga codului anterior (reprezentat de clasa ''Student''), o clasă nouă, va ilustra aceste concepte: |
- | Principalele metode ale acestei clase sunt: | + | |
- | * ''add()'' - adaugă un element la ArrayList. | + | |
- | * ''get()'' - accesează elementul de pe o anumită poziție din ArrayList. | + | |
- | * ''size()'' - returnează numărul de elemente din ArrayList. | + | |
- | * ''set()'' - modifică un element de pe o anumită poziție din ArrayList. | + | |
- | * ''remove()'' - șterge un element de pe o anumită poziție din ArrayList. | + | |
- | * ''clear()'' - șterge toate elementele din ArrayList. | + | |
- | Pentru a putea parcurge elementele unui ArrayList se poate folosit atât un for-each, cât și clasa Iterator. | + | <code java5 Group.java> |
- | Un exemplu de utilizare a metodelor clasei ArrayList și de parcurgere a elementelor stocate de această clasă este următorul: | + | class Group { |
- | <code java5> | + | private int numberStudents; |
- | import java.util.ArrayList; | + | private Student[] students; |
- | import java.util.Iterator; | + | |
- | public class Main { | + | public Group () { |
- | public static void main(String[] args) { | + | numberStudents = 0; |
- | | + | students = new Student[10]; |
- | ArrayList<String> animals = new ArrayList<>(); | + | } |
- | animals.add("Dog"); | + | |
- | animals.add("Cat"); | + | |
- | animals.add("Sheep"); | + | |
- | | + | |
- | System.out.println("First animal from the list is: " + animals.get(0)); | + | |
- | System.out.println("Number of animals from the list: " + animals.size()); | + | |
- | animals.set(0, "Lion"); | + | public boolean addStudent(String name, int grade) { |
- | System.out.println("First animal from the list is: " + animals.get(0)); | + | if (numberStudents < students.length) { |
- | + | students[numberStudents++] = new Student(this, name, grade); // (1) | |
- | animals.remove(0); | + | return true; |
- | + | ||
- | for (String animal : animals) { | + | |
- | System.out.println(animal); | + | |
} | } | ||
| | ||
- | Iterator iterator = animals.iterator(); | + | return false; |
- | while(iterator.hasNext()) { | + | |
- | System.out.println(iterator.next()); | + | |
- | } | + | |
- | + | ||
- | animals.clear(); | + | |
} | } | ||
} | } | ||
</code> | </code> | ||
- | <note important> | + | <code java5 Student.java> |
- | În exemplul de mai sus, la declararea unui ArrayList, se observă semnele "<>", utilizate pentru a specifica tipul de date al elementelor, pe care ArrayList le va conține. Acest lucru se numește genericitate și permite compilatorului să verifice în timpul compilării că se folosește tipul corect de date. Conceptul de genericitate se va studia mai mult în [[https://ocw.cs.pub.ro/courses/poo-ca-cd/laboratoare/genericitate|laboratorul 10]] . | + | class Student { |
- | </note> | + | |
- | + | ||
- | ====Summary==== | + | |
- | **Relații între obiecte:** | + | |
- | * Agregare - **has a** | + | |
- | * Moștenire - **is a** | + | |
- | **Upcasting:** | + | private String name; |
- | * convertire **copil** => **părinte** | + | private int averageGrade; |
- | * realizată automat | + | private Group group; |
- | **Downcasting:** | + | public Student(Group group, String name, int averageGrade) { |
- | * convertire **părinte** =>**copil** | + | this.group = group; // (2) |
- | * trebuie făcută explicit de către programator | + | this.name = name; |
- | * încercați să evitați folosirea operatorului **instanceof** | + | this.averageGrade = averageGrade; |
+ | } | ||
+ | } | ||
+ | </code> | ||
- | **Suprascrierea:** | + | ==== Metoda toString() ==== |
- | * înlocuirea funcționalității metodei din clasa de bază în clasa derivată | + | Cu ajutorul metodei [[https://docs.oracle.com/en/java/javase/14/docs/api/java.base/java/lang/Object.html#toString() | toString()]], care este deja implementată în mod predefinit pentru fiecare clasă în Java, aceasta întorcând un string, putem obține o reprezentare a unui obiect ca string. În cazurile claselor implementate de utilizator, este de recomandat să implementăm (mai precis, să suprascriem - detalii despre suprascrierea metodelor în următoarele laboratoare) metoda toString() în clasele definite de către utilizator. |
- | * păstreaza numele și semnătura metodei | + | |
- | **Supraîncărcarea:** | + | Un exemplu de implementare (de fapt suprascriere - mai multe detalii in laboratorului de [[poo-ca-cd:laboratoare:agregare-mostenire|agregare și moștenire]]) a metodei toString(): |
- | * în interiorul clasei pot exista mai multe metode cu același nume, cu condiția ca semnătura (tipul, argumentele) să fie diferită | + | <code java5 Student.java> |
+ | public class Student { | ||
+ | private String name; | ||
+ | private int averageGrade; | ||
- | **Cuvântul cheie super:** | + | public Student(String name, int averageGrade) { |
- | * instanța clasei părinte | + | this.name = name; |
- | * amintiți-vă din laboratorul anterior că **[[:poo-ca-cd:laboratoare:constructori-referinte#cuvantul_cheie_this_intrebuintari|this]]** se referă la instanța clasei curente | + | this.averageGrade = averageGrade; |
+ | } | ||
- | ====Exerciții==== | + | public String toString() { |
+ | return "Nume student: " + name + "\nMedia studentului: " + averageGrade; | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
- | <note important> | + | Folosirea metodei toString(): |
- | Veți încărca soluția voastră pe LambdaChecker, contest [[https://beta.lambdachecker.io/contest/54/problems?page=1 | POO - LAB3]]. | + | <code java5> |
+ | Student st1 = new Student("Ilie Popescu", 5); | ||
+ | /* | ||
+ | nu este neaparat sa scriem st1.toString() la apelul metodei println | ||
+ | println apeleaza in mod automat metoda toString in acest caz | ||
+ | */ | ||
+ | System.out.println(st1); | ||
+ | /* | ||
+ | se va afisa urmatorul string | ||
- | Încercați să accesați de aici contest-ul, altfel e posibil să vă ceară o parolă care nu există. De asemena, să fiți logați pe LambdaChecker înainte de accesa link-ul. m( | + | Nume student: Ilie Popescu |
- | </note> | + | Media studentului: 5 |
- | Schelet laborator: [[https://github.com/oop-pub/oop-labs/tree/master/src/lab3 | Laborator 3]] | + | */ |
+ | </code> | ||
+ | ====Exerciții==== | ||
- | **Task 1** [1p] | + | Scheletul se afla {{:poo-ca-cd:laboratoare:lab2.zip|aici}}. |
- | + | ||
- | Veți proiecta o clasă Form care va avea câmpul privat color (String). | + | |
- | + | ||
- | Clasa va avea, de asemenea: | + | |
- | * un constructor fără parametri, care va inițializa culoarea cu "white"; | + | |
- | * un constructor cu parametri; | + | |
- | * o metodă de tip float getArea(), care va întoarce valoarea 0; | + | |
- | * o metodă toString(): "This form has the color [color]". | + | |
- | + | ||
- | **Task 2** [2p] | + | |
- | + | ||
- | Din clasa Form derivați clasele Square, Triangle, Circle: | + | |
- | + | ||
- | * clasa Triangle va avea 2 membri height și base de tip float; | + | |
- | * clasa Circle va avea membrul radius de tip float; | + | |
- | * clasa Square va avea membrul side de tip float. | + | |
- | + | ||
- | Clasele vor avea: | + | |
- | * constructori fără parametri; | + | |
- | * constructori care permit inițializarea membrilor. Identificați o modalitate de reutilizare a codului existent; | + | |
- | * suprascrieți metoda getArea() pentru a întoarce aria specifică fiecărei figuri geometrice; | + | |
- | * suprascrieți metoda toString() în clasele derivate, astfel încât aceasta să utilizeze implementarea metodei toString() din clasa de baza. | + | |
- | **Task 3** [2p] | + | **Task 1** - 3 puncte |
- | + | ||
- | Adăugați o metodă equals() în clasa Triangle. | + | |
- | Justificați criteriul de echivalență ales. | + | Să se creeze o clasă numită //Complex//, care are doi membri de tip ''int'' (''real'' și ''imaginary''), care vor fi de tip ''private''. Realizați următoarele subpuncte: |
+ | * să se creeze trei constructori: primul primește doi parametri de tip ''int'' (primul desemnează partea reală a unui număr complex, al doilea partea imaginară), al doilea nu primește niciun parametru și apelează primul constructor cu valorile 0 și 0, iar al treilea reprezinta un copy constructor, care primește ca parametru un obiect de tip Complex, care este copiat în obiectul //this// | ||
+ | * să se scrie metode de tip getter și setter (remember primul laborator - proprietăți), prin care putem accesa membrii privați ai clasei | ||
+ | * să se scrie o metodă de tip void numită ''addWithComplex'', care primește ca parametru un obiect de tip Complex, prin care se adună numărul complex dat ca parametru la numărul care apelează funcția (adică la //this//) | ||
+ | * să se scrie o metodă de tip void numită ''showNumber'', prin care se afișează numărul complex astfel: | ||
+ | * a + i * b, daca b >0 | ||
+ | * a - i * b, daca b<0 | ||
+ | * a, daca b = 0 | ||
- | <note important>**Hint:** | ||
- | Puteți genera automat metoda, cu ajutorul IDE. Selectați câmpurile considerate și | ||
- | analizați în ce fel va fi suprascrisă metoda equals.</note> | ||
- | **Task 4** - //''Upcasting''// [2p] | + | **Task 2** - 2 puncte |
- | Creați un vector de obiecte Form și populați-l cu obiecte de tip Triangle, Circle și Square (upcasting). | + | Aveți in scheletul laboratorului un cod, care conține două greșeli legate de referințe. Rolul vostru este să corectați aceste greșeli încât codul să aibă comportamentul dorit (există comentarii în cod despre modul cum ar trebui să se comporte). |
- | Parcurgeți acest vector și apelați metoda toString() pentru elementele sale. Ce observați? | + | |
- | **Task 5** - //''Downcasting''// [2p] | ||
- | Adăugați clasei Triangle metoda printTriangleDimensions, clasei Circle metoda printCircleDimensions și clasei Square metoda printSquareDimensions. | + | ** Task 3** - 3 puncte |
- | Implementarea metodelor constă în afișarea bazei și înălțimii, razei, respectiv laturii. | + | |
- | Parcurgeți vectorul de la exercițiul anterior și, folosind downcasting la clasa corespunzătoare, apelați | + | Să se implementeze o clasă// Point// care să conțină: |
- | metodele specifice fiecărei clase (printTriangleDimensions pentru Triangle, printCircleDimensions pentru Circle și printSquareDimensions pentru Square). | + | * un constructor care să primească cele două numere reale (de tip float) ce reprezintă coordonatele. |
+ | * o metodă ''changeCoords'' ce primește două numere reale și modifică cele două coordonate ale punctului. | ||
+ | * o funcție de afișare a unui punct astfel: (x, y). Alternativ, în loc de o metodă de afișare puteți suprascrie toString(). | ||
- | Pentru a stabili tipul obiectului curent folosiți operatorul instanceof. | + | Să se implementeze o clasă //Polygon// cu următoarele: |
+ | * un constructor care preia ca parametru un singur număr n (reprezentând numărul de colțuri al poligonului) și alocă spațiu pentru puncte (un punct reprezentând o instanță a clasei Point). | ||
+ | * un constructor care preia ca parametru un vector cu 2N numere reale, adică N perechi de puncte ce reprezintă colțurile unui poligon. Acest constructor apelează constructorul de la punctul de mai sus și completează vectorul de puncte cu cele n instanțe ale clasei Point obținute din parametrii primiți. | ||
- | **Task 6** - //''Agregare''// [1p] | + | Testați codul afișând poligonul. |
- | Afișați dimensiunile formelor din vectorul creat fără a folosi operatorul instanceof. | + | ** Task 4** - 2 puncte |
- | ==== Resurse ==== | + | În scheletul de cod aveți definită clasa //Book//, în care trebuie să implementați metoda [[https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/lang/Object.html#toString()|toString()]], și o clasă //Main//, în care se testează metoda toString() din Book. |
- | * [[:poo-ca-cd:laboratoare:old-exercises#agregare_si_mostenire|Exerciții din alți ani]] | + | |
==== Referințe ==== | ==== Referințe ==== | ||
- | * [[https://www.visual-paradigm.com/guide/uml-unified-modeling-language/uml-class-diagram-tutorial/ | UML Diagrams]] | ||
- | * [[https://www.visual-paradigm.com/guide/uml-unified-modeling-language/uml-aggregation-vs-composition/ | Aggregation vs Composition]] | ||
- | * [[http://docs.oracle.com/javase/tutorial/java/IandI/subclasses.html | Inheritance JavaDoc]] | ||
- | * [[https://www.journaldev.com/1775/multiple-inheritance-in-java | Multiple Inheritance]] | ||
- | * [[http://forum.codecall.net/topic/50451-upcasting-downcasting/ | Upcasting and Downcasting]] | ||
+ | * [[http://docs.oracle.com/javase/tutorial/java/javaOO/constructors.html| Constructors in Java]] | ||
+ | * [[http://www.javaworld.com/article/2077424/learn-java/does-java-pass-by-reference-or-pass-by-value.html | Java pass by reference or pass by value]] | ||
+ | * [[http://docs.oracle.com/javase/tutorial/java/javaOO/thiskey.html | Using the "this" Keyword]] | ||
+ | * [[http://docs.oracle.com/javase/7/docs/api/java/lang/String.html | String Class]] |