Differences

This shows you the differences between two versions of the page.

Link to this comparison view

poo-ca-cd:arhiva:laboratoare:2024:constructori-referinte [2025/09/27 10:39] (current)
florian_luis.micu created
Line 1: Line 1:
 +===== Laboratorul 2: Constructori,​ referințe, static =====
  
 +**Video introductiv:​** [[https://​www.youtube.com/​watch?​v=Cz3sNXVrYrM&​t|link]]
 +
 +==== Obiective ====
 +
 +
 +Scopul acestui laborator este familiarizarea voastră cu noțiunile de **constructori** și de **referințe** în limbajul Java.
 +
 +Aspectele urmărite sunt:
 +  * tipurile de contructori și crearea de instanţe ale claselor folosind acești constructori
 +  * utilizarea cuvântului-cheie **this**
 +
 +==== Constructori ====
 +
 +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.
 +
 +Crearea unui obiect se face cu sintaxa:
 +
 +<code java5>
 +class MyClass {
 +   ...
 +}
 +
 +...
 +
 +MyClass instanceObject;​
 +
 +// 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:
 +
 +<code java5>
 +// constructor call
 +MyClass instanceObject = new MyClass(param_1,​ param_2, ..., param_n); ​
 +</​code>​
 +
 +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''​. ​
 +
 +Să urmărim în continuare codul: ​
 +
 +<code java5> ​
 +String myFirstString,​ mySecondString; ​
 +
 +myFirstString = new String(); ​
 +mySecondString = "This is my second string"; ​
 +</​code> ​
 +
 +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. ​
 +
 +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: ​
 + * 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''​
 +
 +Pentru a exemplifica acest mecanism, să urmărim exemplul:
 +
 +<code java5 SomeClass.java>​
 +public class SomeClass {
 +
 +    private String name = "Some Class";​
 +
 +    public String getName() {
 +        return name;
 +    }
 +}
 +
 +class Test {
 +
 +    public static void main(String[] args) {
 +        SomeClass instance = new SomeClass();​
 +        System.out.println(instance.getName());​
 +    }
 +}
 +</​code>​
 +
 +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>
 +public SomeClass() {
 +    ... // variables initialization
 +}
 +</​code>​
 +
 +Să vedem acum un exemplu general:
 +
 +<code java5 Student.java>​
 +public class Student {
 +
 +    private String name;
 +    public int averageGrade;​
 +
 +    // (1) constructor without parameters
 +    public Student() {
 +        name = "​Unknown";​
 +        averageGrade = 5;
 +    }
 +
 +    // (2) constructor with two parameters; used to set the name and the grade
 +    public Student(String n, int avg) {
 +        name = n;
 +        averageGrade = avg;
 +    }
 +
 +    // (3) constructor with one parameter; used to set only the name
 +    public Student(String n) {
 +        this(n, 5); // call the second constructor (2)
 +    }
 +
 +    // (4) setter for the field '​name'​
 +    public void setName(String n) {
 +        name = n;
 +    }
 +
 +    // (5) getter for the field '​name'​
 +    public String getName() { 
 +        return name; 
 +    }
 +}
 +</​code>​
 +
 +Declararea unui obiect de tip ''​Student''​ se face astfel:
 +
 +<code java5>
 +Student st;
 +</​code>​
 +
 +Crearea unui obiect ''​Student''​ se face **obligatoriu** prin apel la unul din cei 3 constructori de mai sus:
 +
 +<code java5>
 +st = new Student(); ​          // first constructor call (1)
 +st = new Student("​Gigel",​ 6); // second constructor call (2)
 +st = new Student("​Gigel"​); ​   // third constructor call (3)
 +</​code>​
 +
 +<note important>​
 +**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: ​
 +</​note>​
 +
 +<code java5>
 +class Student {
 +
 +    private String name;
 +    public int averageGrade;​
 +
 +    public Student(String n, int avg) {
 +        name = n;
 +        averageGrade = avg;
 +    }
 +
 +    public static void main(String[] args) {
 +        // ERROR: the implicit constructor is hidden by the constructor with parameters
 +        Student s = new Student();
 +    }
 +}
 +</​code>​
 +
 +Î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 java5>
 +public class Student {
 +    private String name;
 +    private int averageGrade;​
 +
 +    public Student(String name, int averageGrade) {
 +        this.name = name;
 +        this.averageGrade = averageGrade;​
 +    }
 +
 +    // copy constructor
 +    public Student(Student student) {
 +        // name este camp privat, noi il putem accesa direct (student.name) ​
 +        // deoarece ne aflam in interiorul clasei
 +        this.name = student.name;​
 +        this.averageGrade = student.averageGrade;​
 +    }
 +}
 +</​code>​
 +
 +==== 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**.
 +
 +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. ​
 +
 +Astfel, declararea unui obiect: ​
 +
 +<code java5> ​
 +Student st; 
 +</​code> ​
 +
 +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**. ​
 +
 +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ă. ​
 +
 +Un fapt ce merită discutat este semnificația **atribuirii** de referințe. În exemplul de mai jos: 
 +
 +<code java5> ​
 +Student s1 = new Student("​Bob",​ 6);
 +
 +Student s2 = s1;
 +s2.averageGrade = 10;
 +
 +System.out.println(s1.averageGrade);​
 +</​code> ​
 +
 +se va afișa **10**.
 +
 +<​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>​
 +
 +Î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. 
 +
 +**Transferul parametrilor** la apelul de funcții este foarte important de înțeles. Astfel: ​
 +  * 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. ​
 +
 +<code java5 TestParams.java>​
 +
 +class TestParams {
 +
 +    static void changeReference(Student st) {
 +        st = new Student("​Bob",​ 10);
 +    }
 +
 +    static void changeObject(Student st) {
 +        st.averageGrade = 10;
 +    }
 +
 +    public static void main(String[] args) {
 +        Student s = new Student("​Alice",​ 5);
 +        changeReference(s); ​                // apel (1)
 +        System.out.println(s.getName()); ​   // "​Alice"​
 +
 +        changeObject(s); ​                   // apel (2)
 +        System.out.println(s.averageGrade);​ // "​10"​
 +    }
 +}
 +</​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 |}} 
 +
 +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 |}}
 +
 +==== Cuvântul-cheie "​this"​. Întrebuințări ==== 
 +
 +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. ​
 +
 +În general, cuvântul cheie ''​this''​ este utilizat pentru: ​
 +
 +  * 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) ​
 +
 +Exemplul următor, în care vom adăuga codului anterior (reprezentat de clasa ''​Student''​),​ o clasă nouă, va ilustra aceste concepte:
 +
 +<code java5 Group.java>​
 +class Group {
 +
 +    private int numberStudents;​
 +    private Student[] students;
 +
 +    public Group () {
 +        numberStudents = 0;
 +        students = new Student[10];​
 +    }
 +
 +    public boolean addStudent(String name, int grade) {
 +        if (numberStudents < students.length) {
 +            students[numberStudents++] = new Student(this,​ name, grade); // (1)
 +            return true;
 +        }
 +        ​
 +        return false;
 +    }
 +}
 +</​code>​
 +
 +<code java5 Student.java>​
 +class Student {
 +
 +    private String name;
 +    private int averageGrade;​
 +    private Group group;
 +
 +    public Student(Group group, String name, int averageGrade) {
 +        this.group ​       = group; // (2)
 +        this.name ​        = name;
 +        this.averageGrade = averageGrade;​
 +    }
 +}
 +</​code>​
 +
 +==== Metoda toString() ====
 +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.
 +
 +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():
 +<code java5 Student.java>​
 +public class Student {
 +    private String name;
 +    private int averageGrade;​
 +
 +    public Student(String name, int averageGrade) {
 +        this.name = name;
 +        this.averageGrade = averageGrade;​
 +    }
 +
 +
 +    public String toString() {
 +        return "Nume student: " + name + "​\nMedia studentului:​ " + averageGrade;​
 +    }
 +}
 +</​code>​
 +
 +Folosirea metodei toString():
 +<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
 +
 +Nume student: Ilie Popescu
 +Media studentului:​ 5
 +*/
 +</​code>​
 +
 +==== Cuvântul-cheie "​static"​ ====
 +
 +După cum am putut observa până acum, de fiecare dată când creăm o instanță a unei clase, valorile câmpurilor din cadrul instanței sunt unice pentru aceasta și pot fi utilizate fără pericolul ca instanţierile următoare să le modifice în mod implicit.
 +
 +Să exemplificăm aceasta: ​
 + 
 +<code java5>
 +Student instance1 = new Student("​Alice",​ 7);
 +Student instance2 = new Student("​Bob",​ 6);
 +</​code>​
 +
 +În urma acestor apeluri, ''​instance1''​ și ''​instance2''​ vor funcționa ca entități independente una de cealaltă, astfel că modificarea câmpului ''​nume''​ din ''​instance1''​ nu va avea nici un efect implicit și automat în ''​instance2''​. Există însă posibilitatea ca uneori, anumite câmpuri din cadrul unei clase să aibă valori independente de instanțele acelei clase (cum este cazul câmpului ''​UNIVERSITY_CODE''​),​ astfel că acestea nu trebuie memorate separat pentru fiecare instanță.
 +
 +Aceste câmpuri se declară cu atributul **static** și au o locație unică în memorie, care nu depinde de obiectele create din clasa respectivă. ​
 +
 +Pentru a accesa un câmp static al unei clase (presupunând că acesta nu are specificatorul ''​private''​),​ se face referire la clasa din care provine, nu la vreo instanță. Același mecanism este disponibil și în cazul metodelor, așa cum putem vedea în continuare: ​
 +
 +<code java5>
 +class ClassWithStatics {
 +
 +    static String className = "Class With Static Members";​
 +    private static boolean hasStaticFields = true;
 +
 +    public static boolean getStaticFields() {
 +         ​return hasStaticFields;​
 +    }
 +}
 +
 +class Test {
 +
 +    public static void main(String[] args) {
 +        System.out.println(ClassWithStatics.className);​
 +        System.out.println(ClassWithStatics.getStaticFields());​
 +    }
 +}  ​
 +</​code>​
 +Pentru a observa utilitatea variabilelor statice, vom crea o clasa care ține un contor static ce numără câte instanțe a produs clasa în total.
 +<code java5>
 +class ClassWithStatics {
 +
 +    static String className = "Class With Static Members";​
 +    private static int instanceCount = 0;
 +    ​
 +    public ClassWithStatics() {
 +        instanceCount++;​
 +    }
 +
 +    public static int getInstanceCount() {
 +        return instanceCount;​
 +    }
 +}
 +
 +class Test {
 +
 +    public static void main(String[] args) {
 +        System.out.println(ClassWithStatics.getInstanceCount()); ​      // 0
 +
 +        ClassWithStatics instance1 = new ClassWithStatics();​
 +        ClassWithStatics instance2 = new ClassWithStatics();​
 +        ClassWithStatics instance3 = new ClassWithStatics();​
 +
 +        System.out.println(ClassWithStatics.getInstanceCount()); ​      // 3
 +    }
 +}  ​
 +</​code>​
 +Deși am menționat anterior faptul că field-urile și metodele statice se accesează folosind sintaxa ''<​NUME_CLASA>​.<​NUME_METODA/​FIELD>''​ aceasta nu este singura abordare disponibilă în limbajul Java. Pentru a referi o entitate statică ne putem folosi și de o instanță a clasei în care se află metoda/​field-ul accesat.
 +<note important>​În acest caz nu este relevant dacă tipul obiectului folosit este diferit de cel al referinței în care e stocat(i.e. avem o referință a clasei Animal care referă un obiect de tipul Dog). Pentru apelul unei metode statice se va lua în considerare numai tipul referinței,​ nu și cel al instanței pe care o referă.</​note>​
 +<code java5>
 +class ClassWithStatics {
 +
 +    static String className = "Class With Static Members";​
 +    private static boolean hasStaticFields = true;
 +
 +    public static boolean getStaticFields() {
 +         ​return hasStaticFields;​
 +    }
 +}
 +
 +class Test {
 +
 +    public static void main(String[] args) {
 +        ClassWithStatics instance = new ClassWithStatic();​
 +        System.out.println(instance.className);​
 +        System.out.println(instance.getStaticFields());​
 +    }
 +}  ​
 +</​code>​
 +<note warning>​Deși putem accesa o entitate statică folosind o referință,​ acest lucru este contraindicat. Field-urile și metodele statice aparțin clasei și nu ar trebui să fie în nici un fel dependente de existența unei instanțe.</​note>​
 +
 +Pentru a facilita inițializarea field-urilor statice pe care o clasă le deține, limbajul Java pune la dispoziție posibilitatea de a folosi blocuri statice de cod. Aceste blocuri de cod sunt executate atunci când clasa în cauză este încărcată de către mașina virtuală de Java. Încărcarea unei clase se face în momentul în care aceasta este referită pentru prima dată în cod (se creează o instanță, se apelează o metodă statică etc.)
 +În consecință,​ blocul static de cod se va executa întotdeauna înainte că un obiect să fie creat.
 +
 +<code java5>
 +class TestStaticBlock { 
 +    static int staticInt; ​
 +    int objectFieldInt; ​
 +      ​
 +    static { 
 +        staticInt = 10; 
 +        System.out.println("​static block called"​); ​
 +    } 
 +
 +  ​
 +class Main { 
 +    public static void main(String args[]) { 
 +        // Even though we didn't create an instance of the TestStaticBlock class,
 +        // the static block of code is executed, and the output will be 10
 +        System.out.println(TestStaticBlock.staticInt);  ​
 +    } 
 +
 +</​code>​
 +
 +
 +==== Singleton Pattern ====
 +
 +Pattern-ul Singleton este utilizat pentru a restricționa numărul de instanțieri ale unei clase la un singur obiect. Astfel, acest design pattern asigură crearea unei singure instanțe a clasei, oferind un punct de acces global al acesteia. ​
 +
 +Cum ne asigurăm că o clasă are o singură instanță și că instanța este ușor accesibilă? ​                                                                                           ​
 +O variabilă globală face un obiect accesibil, dar nu restricționeaza de la instanțierea mai multor obiecte.
 +
 +{{ :​poo-ca-cd:​laboratoare:​singleton1.png?​350 |}}
 +=== Utilizări ===
 +
 +Pattern-ul Singleton este util în următoarele cazuri:
 +  * ca un subansamblu al altor pattern-uri:​
 +    *   ​împreună cu pattern-urile Abstract Factory, Builder, Prototype etc. De exemplu, în aplicație dorim un singur obiect factory pentru a crea obiecte de un anumit tip.
 +  * în locul variabilelor globale. Singleton este preferat variabilelor globale deoarece, printre altele, nu poluează namespace-ul global cu variabile care nu sunt necesare.
 +
 +Singleton este utilizat des în situații în care avem obiecte care trebuie accesate din mai multe locuri ale aplicației:​
 +  * obiecte de tip  [[https://​docs.oracle.com/​javase/​7/​docs/​api/​java/​util/​logging/​Logger.html|logger]]
 +  * obiecte care reprezintă resurse partajate (conexiuni, [[https://​docs.oracle.com/​javase/​tutorial/​networking/​sockets/​definition.html|sockets]] etc.) 
 +  * obiecte ce conțin configurații pentru aplicație
 +  * pentru obiecte de tip //Factory// (acest design pattern va fi prezentat în cadrul laboratorului 9).
 +
 +{{ :​poo-ca-cd:​laboratoare:​singleton2.png?​500 |}}
 +
 +Exemple din API-ul Java: [[http://​docs.oracle.com/​javase/​8/​docs/​api/​java/​lang/​Runtime.html | java.lang.Runtime]],​ [[http://​docs.oracle.com/​javase/​8/​docs/​api/​java/​awt/​Toolkit.html | java.awt.Toolkit]]
 +
 +Din punct de vedere al design-ului și testării unei aplicații de multe ori se evită folosirea acestui pattern, în test-driven development fiind considerat un **//​anti-pattern//​**. A avea un obiect Singleton a cărei referință o folosim peste tot prin aplicație introduce multe dependențe între clase și îngreunează testarea individuală a acestora. ​
 +
 +În general, codul care folosește stări globale este mai dificil de testat pentru că implică o cuplare mai strânsă a claselor, și împiedică izolarea unei componente și testarea ei individuală. Dacă o clasă testată folosește un obiect singleton, atunci trebuie testat și singleton-ul. Soluția este simularea //mock-up// a singleton-ului în teste. Încă o problemă a acestei cuplări mai strânse apare atunci când două teste depind unul de celălalt prin modificarea singleton-ului,​ deci trebuie impusă o anumită ordine a rulării testelor.
 +
 +<note tip>​Încercați să nu folosiți în exces metode statice și componente Singleton.</​note>​
 +
 +
 +=== Implementare ===
 +
 +Aplicarea pattern-ului Singleton constă în implementarea unei metode ce permite crearea unei noi instanțe a clasei dacă aceasta nu există, și întoarcerea unei referințe către aceasta dacă există deja. În Java, pentru a asigura o singură instanțiere a clasei, constructorul trebuie să fie //​private//,​ iar instanța să fie oferită printr-o metodă statică, publică.
 +
 +=== Lazy instantiation ===
 +
 +În cazul unei implementări Singleton care folosește //lazy instantiation//,​ clasa respectivă va fi instanțiată **lazy**, utilizând memoria doar în momentul în care acest lucru este necesar deoarece instanța se creează atunci când se apelează ''​getInstance()'',​ acest lucru putând fi un avantaj în unele cazuri, față de clasele non-singleton,​ pentru care se face //eager instantiation//,​ deci se alocă memorie încă de la început, chiar dacă instanța nu va fi folosită (mai multe detalii și exemplu în [[http://​www.javaworld.com/​article/​2077568/​learn-java/​java-tip-67--lazy-instantiation.html |acest articol]])
 +
 +
 +{{ .:​design-patterns:​singletonclassdiagram.png | Diagrama de clase pentru Singleton}}
 +
 +<code java>
 +public class SingletonLazy {
 +    private static SingletonLazy instance = null;
 +
 +    private SingletonLazy() {}
 +
 +    public static SingletonLazy getInstance() {
 +        if (instance == null) {
 +            instance = new SingletonLazy();​
 +        }
 +        return instance;
 +    }
 +}
 +</​code>​
 +
 +  * Instanța ''​instance''​ este //private//
 +  * Constructorul este privat ca să nu poată fi apelat decât din clasa respectivă
 +  * Instanța este inițial nulă
 +  * Instanța este creată la prima rulare a //​getInstance()//​
 +
 +=== Eager instantiation ===
 +
 +În ceea ce privește instanțierea **eager**, instanța clasei este creată la momentul [[https://​www.baeldung.com/​java-classloaders|class loading]], aceasta fiind cea mai ușoară metodă de a crea o clasă singleton. Are totusi un dezavantaj: instanța este creată chiar daca aceasta nu va fi folosita în cadrul aplicației.
 +
 +<code java>
 +public class SingletonEager {
 +    private final static SingletonEager instance = new SingletonEager();​
 +
 +    private SingletonEager() {}
 +
 +    public static SingletonEager getInstance() {
 +        return instance;
 +    }
 +}
 +</​code>​
 +
 +În cazul în care clasa Singleton nu folosește multe resurse, aceasta abordare este recomandata. În majoritatea scenariilor,​ clasele Singleton sunt create pentru resurse precum sistemul de fișiere, conexiunile la baza de date, iar instanțierea eager ar trebui evitată.
 +
 +=== Static block instantiation ===
 +
 +Implementarea este similară instanțierii //eager//, cu excepția faptului că instanța clasei este creată în blocul static care poate fi opțiune pentru gestionarea excepțiilor.
 +
 + Atât inițializarea eager, cât și cea cu blocuri statice creează instanța înainte de a fi utilizată - motiv pentru care aceasta nu este cea mai bună practică de utilizat.
 +
 +<code java>
 +public class SingletonStaticBlock {
 +    private static SingletonStaticBlock instance = null;
 +
 +    static {
 +        instance = new SingletonStaticBlock();​
 +    }
 +
 +    private SingletonStaticBlock() {}
 +
 +    public static SingletonStaticBlock getInstance() {
 +        return instance;
 +    }
 +}
 +</​code>​
 +
 +
 +Respectând cerințele pentru un singleton enunțate mai sus, în Java, putem implementa o componentă de acest tip în mai multe feluri, inclusiv folosind ''​enum''​-uri în loc de clase. Atunci când îl implementăm trebuie avut în vedere contextul în care îl folosim, astfel încât să alegem o soluție care să funcționeze corect în toate situațiile ce pot apărea în aplicație (unele implementări au probleme atunci când sunt accesate din mai multe thread-uri sau când trebuie serializate).
 +
 +//De ce Singleton și nu clase cu membri statici?//
 +
 +O clasă de tip Singleton poate fi extinsă, iar metodele ei suprascrise,​ însă într-o clasă cu metode statice acestea nu pot fi suprascrise (//​overriden//​) (o discuție pe aceasta temă puteți găsi [[http://​geekexplains.blogspot.ro/​2008/​06/​can-you-override-static-methods-in-java.html | aici]], și o comparație între static și dynamic binding [[http://​geekexplains.blogspot.ro/​2008/​06/​dynamic-binding-vs-static-binding-in.html | aici]]).
 +
 +====Exerciții====
 +
 +Scheletul se afla {{:​poo-ca-cd:​laboratoare:​lab2.zip|aici}}.
 +
 +**Task 1** - 3 puncte
 +
 +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
 +
 +
 +**Task 2** - 2 puncte
 +
 +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).
 +
 +
 +** Task 3** - 3 puncte
 +
 +Să se implementeze o clasă// Point// care să conțină:
 +  * 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().
 +
 +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. ​
 +
 +Testați codul afișând poligonul. ​
 +
 +** Task 4** - 2 puncte
 +
 +Î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.
 +
 +==== Referințe ====
 +
 +  * [[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]]
poo-ca-cd/arhiva/laboratoare/2024/constructori-referinte.txt · Last modified: 2025/09/27 10:39 by florian_luis.micu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0