Ciclul de viaţă al obiectelor

Toate obiectele în java au un ciclu de viţă. Acestea sunt create, îşi desfăşoară activitatea după care sunt şterse. Ştergerea obiectelor se realizează fie odată cu finalizarea aplicaţiei din cadrul căreia fac part, fie sunt şterse în timpul execuţiei când nu mai este nevoie de acestea de către Garbage Collector.

Initializarea obiectelor

Obiectele sunt construite în java folosind operatorul new. Un obiect este o instanţă a unei clase în memorie. După alocarea spaţiului necesar de memorie pentru obiect, acest trebuie iniţializat. Iniţializarea este procesul prin care un obiect este adus în starea dorită de programator după ce acesta a fost construit.

Pentru realizarea iniţializării java foloseste metode de tip constructor (concept de constructor este moştenit din limbajul C++). Rolul constructorului este de iniţializare a stării unui obiect. Câteva caracteristici importante ale constructorilor din java sunt următoarele:

  1. Constructorii sunt metode speciale a căror nume este identic cu numele clasei din care fac parte.
  2. Constructorii nu au nici un tip de return
  3. În cadrul unei clase pot fi definiţi unul sau mai mulţi constructori care trebuie să difere între ei prin numărul şi tipul parametrilor.
  4. Dacă în cadrul unei clase programator nu adaugă nici un constructor atunci la compilare automat se va adăuga constructorul default care nu are nici un parametru.
  5. Dacă programatorul adaugă în cadrul clasei cel puţin un constructor atunci construcorul default nu mai este automat adăugat.
  6. Constructorul este apelat automat după ce obiectul a fost construit în memorie.

Pentru a demonstra faptul ca masina virtuala java apeleaza automat constructorul in momentul crearii obiectelor vom implementa urmatorul exemplu:

public class Flower{
            int petal;
            Flower(){ 
            	System.out.println("Flower has been created!");            
            }
 
	public static void main(String[] args) {
		Flower[] garden = new Flower[5];
		for(int i =0;i<5;i++){
			Flower f = new Flower();
			garden[i] = f;
		}
	}
}

In urma rularii codului de mai sus se va afisa:

Flower has been created!
Flower has been created!
Flower has been created!
Flower has been created!
Flower has been created!

Am adaugat in exemplul precedent in cadrul clasei Flower constructorul implicit (default) ce nu primieste nici un argument. In momentul construirii obiectului masina virtuala java cauta in cadrul clasei un constructor care se potriveste ca si numar si tip de argumente cu cele care au fost specificate in linia de declarare si initializare a obiectului ( Flower f = new Flower(); ). Deoarece nu am specificat nici un argument masina virtuala java va apela constructorul implicit.

public class Flower{
            int petal;
            Flower(int p){       
              petal=p;
              System.out.println("New flower has been created!");
            }
 
	public static void main(String[] args) {
		Flower f1 = new Flower(4);
		Flower f2 = new Flower(6);
                Flower f3 = new Flower(); //eroare de compilare - stergeti linia pentru a putea rula 
	}
}

În exemplul precedent în cadrul clasei Flower a fost adăugat un constructor ce are un parametru de tip int ce este folosit pentru initializarea atributului petal din cadrul obiectului.

Flower f1 = new Flower(4);
Flower f2 = new Flower(6);
Flower f3 = new Flower(); //eroare de compilare

În secvenţa anterioară sunt construite 3 obiecte. In momentul construirii obiectului este specificat ca argument un intreg – aceasta pentru că în cadrul clasei Flower a fost definit un constructor care are un parametru de tip întreg. Crearea obiectului f3 va genera eroare de compilare ca o consecinţă a proprietăţilor 4 şi 5 prezentate mai sus.

Supraîncărcarea metodelor obişnuite şi a constructorilor

Supraîncărcarea unei metode (eng. owerloading) este procesul prin care în cadrul unei clase sunt adăugate două sau mai multe metode cu acelaşi nume dar cu număr diferit de parametri. În momentul apelării metodei, mediul de rulare va şti exact ce metodă să apeleze pe baza parametrilor specificaţi astfel încât nu exista posibilitate de confuzie si de apelare a unei metode greşite.

Acest concept de supraîncărcare se aplică şi constructorilor, astfel încât în cadrul unei clase se pot defini doi sau mai mulţi constructori. La fel ca şi în cazul metodelor, mediul de execuţie va decide ce constructor să apeleze în funcţie de numărul şi tipul parametrilor specificaţi în cadrul instrucţiunii de creare a obiectului.

În continuare este prezentat un exemplu ce exemplifică conceptul de supraîncărcare a metodelor şi funcţiilor.

public class Robot {
            int arms;
            int legs;
            int position;
 
            Robot(int a){
                        arms = a;
                        legs = a;
                        position =0;
            }
            Robot(int a, int l){
                        arms = a;
                        legs = l;
                        position=0;
            }
            void move(){
                        position++;
                        System.err.println("Robot is moving. [position="+position+"]");
            }
            void move(int increment){
                        position = position +increment;
                        System.err.println("Robot is moving. [position="+position+"]");
            }          
            public static void main(String[] args) {
                        Robot r1 = new Robot(2,2);
                        Robot r2 = new Robot(4);
                        //Robot r3 = new Robot(); //instructiunea nu este valida
                        r1.move();
                        r1.move(10);
                        r2.move();
            }
}

De retinut: Chiar si ordinea diferită a argumentelor funcţiilor permite realizarea distincţiei între doua metode cu acelaşi nume, dar pentru claritatea programelor nu se recomandă folosirea acestei tehnici.

De retinut: Supraîncărcarea metodelor se poate face doar pe baza argumentelor nu şi a tipului de return. Nu pot fi declarate în cadrul aceleiaşi clase două metode care au aceleaşi argumente dar se diferenţiază doar prin valoarea returnată.

Cuvântul cheie this

Cuvântul cheie this este folosit în cadrul metodelor atunci când se doreşte să se aibă acces la referinţa obiectului curent. Cuvântul cheie this este o referinţă către obiectul curent. Acest cuvânt cheie este folosit doar în cazurile speciale când este nevoie să se facă o referinţă explicită la obiectul curent. De exemplu în cazul unei metode care trebuie să returneze obiectul curent:

class Complex{
int real,imaginar;
Complex add(int i, int j){
            real = real+i;
            imaginar = imaginar + j;
            return this;
}
}

O altă utilitate a cuvântului cheie this este de apelare din cadrul unui constructor a altui constructor în scopul evitării duplicării codului.

public class Engine {
            String fuellType;
            long capacity;
            boolean active;
 
            Engine(int capacity,boolean active){
                        this.capacity = capacity;
                        this.active = active;
            }          
            Engine(int capacity,boolean active,String fuellType){
                        this(capacity,active);
                        this.fuellType = fuellType;
            }          
            Engine(){
                        this(2000,false,"diesel");
            }          
            void print(){
                        System.out.println("Engine: capacity="+this.capacity+" fuell="+fuellType+" active="+active);
            }
            public static void main(String[] args) {
                        Engine tdi = new Engine();
                        Engine i16 = new Engine(1600,false,"petrol");
                        Engine d30 = new Engine(3000,true,"diesel");
                        tdi.print();i16.print();d30.print();
            }
}

Dacă numele parametrului formal al unei metode este identic cu numele unui atribut din cadrul obiectului, atunci în cadrul metodei pentru a face distincţie între cele două se va folosi cuvântul cheie this pentru a specifica faptul că se doreşte folosirea atributului obiectului (a se vedea constructorii din exemplul de mai sus) .

Cuvântul cheie static

Cuvântul cheie „static” este folosit în java pentru a defini o variabilă sau o metodă care poate fi accesata prin intermediul numelui clasei, fără a fi nevoie să se construiască obiecte de tipul respectiv. În construirea unei aplicaţii orientate pe obiecte utilizarea atributelor sau metodelor statice trebuie să fie evitată şi acestea să fie folosite doar în cazuri de strictă necesitate.

Un exemplu ce demonstrează folosirea cuvântului cheie static este prezentat în continuare.

public class Point {
 
            int x;
            static int y;
 
            void displayPoint(){
                        System.out.println("x="+x);
                        System.out.println("y="+y);
                        System.out.println("---");
            }
 
            static void setY(int val){
                        y = val;
            }
 
            public static void main(String[] args) {
 
                        Point p1 = new Point();
                        Point p2 = new Point();
 
                        p1.x = 10;
                        p1.y =15;
                        p1.displayPoint();
 
                        p2.x = 256;
                        p2.y = 128;
                        p2.displayPoint();
                        p1.displayPoint();
 
                        Point.setY(333);
                        p1.displayPoint();
                        p2.displayPoint();
 
            }
}

Un atribut static al unei clase este comun tuturor obiectelor de tipul respectiv. Există o singură locaţie de memorie pentru un atribut static indiferent de numărul obiectelor construite. În exemplul anterior se observă faptul că indiferent de obiectul prin intermediul căruia este modificat atributul static y, ambele obiecte văd aceiaşi valoare. De asemenea un atribut sau o metodă statică pot fi accesate prin intermediul numelui clasei.

De retinut: Dintr-o metoda statica poate fi accest doar un membru static (nu poate fi accesata o metoda sau un atribut de instanta).

Initializarea variabilelor

în cazul variabilelor locale definite în cadrul unei metode limbajul java forţează programatorul să iniţializeze aceste variabile. În cazul în care este definită o variabilă locală şi aceasta nu este iniţializată va fi generată o eroare de compilare.

void myfunction(){
int i;
i++;
...
}

În cadrul secvenţei de cod anterioare se va genera eroare de compilare deoarece variabila locală i nu a fost iniţializată. Înainte de folosirea unei variabile programatorul va trebui să asigneze acesteia o valoare.

Lucruile stau diferit în cazul variabilelor membru ale unei clase (atributele unei clase. În cazul atributelor definite în cadrul unei clase java asigură iniţializarea automată a acestora la valorile default în momentul în care un obiect este creat.

public class InitializareAtribute {
 
             boolean t;
             char c;
             byte b;
             short s;
             int i;
             long l;
             float f;
             double d;
 
             void print(String s){System.out.println(s);}
 
             void test(){
                        print("Data type      Initial value");
                print("boolean        " + t);
                print("char           [" + c + "]");
                print("byte           " + b);
                print("short          " + s);
                print("int            " + i);
                print("long           " + l);
                print("float          " + f);
                print("double         " + d);
             }
 
            public static void main(String[] args) {
                        InitializareAtribute ia = new InitializareAtribute();
                        ia.test();
            }
}

De retinut: Doar tipurile primitive sunt automat iniţializate la valoarea default dacă acestea sunt definite ca variabile membru ale clasei. Tipurile referinţă nu sunt automat iniţializate. Dacă în cadrul unei clase avem definite unul sau mai multe obiecte acestea vor avea valoarea null. „null” este cuvânt cheie în java care desemnează faptul că o variabilă de tip referinţă nu pointează către nici un obiect in memorie.

Dacă în cadrul unei clase este definită o variabilă de tip static atunci aceasta va fi iniţializată o singură dată în momentul în care clasa respectivă este încărcată în cadrul maşinii virtuale. O clasă este încărcată în memorie atunci când este folosită pentru prima data: fie un obiect de tipul respectiv este construit fie un atribut sau o metodă statică a acelei clase este apelat.

Obiecte compuse

Un obiect poate contine pe langa atribute tipuri primitive si atribute de tip referinta. In exemplul de mai jos avem un obiect compus Human, care are definite ca si caracteristici (atribute) alte obiecte.

public class Human {
	Head head;
	Leg legL, legR;
	Arm armL, armR;
	Body body;
 
	Human(){
		head = new Head();
		legL = new Leg();
		legR = new Leg();
		armL = new Arm();
		armR = new Arm();
		body = new Body();
	}
 
	public static void main(String[] args) {
		Human h1 = new Human();
	}
 
}
 
class Leg{}
 
class Head{}
 
class Body{}
 
class Arm{}

Exemplu Coffee Maker

In exemplul de mai jos este realizata o aplicatie compusa din mai multe clase, fiecare dintre clase avand o functie bine determinata. In cadrul aplicatiei obiecte de tipuri diferite sunt construite si interactioneaza intre ele in scopul indeplinirii functiilor aplicatiei.

public class CoffeTest {
 
  public static void main(String[] args) {
                        CoffeMaker maker1 = new CoffeMaker();     
 
                        Coffee[] pachet = new Coffee[10];
                        for (int i = 0; i < pachet.length; i++) {
							pachet[i] = maker1.getCofee();
						}
 
                        for (int i = 0; i < pachet.length; i++) {
							pachet[i].drinkCofee();
						}
 
            }
}
 
class CoffeMaker{
            CaffeineTank ctank = new CaffeineTank();
            WaterTank wtank = new WaterTank();
            CoffeMaker(){
                        System.out.println("New cofee maker created.");
            }
            Coffee getCofee(){
                        int w = wtank.getIngredient();
                        int c = ctank.getIngredient();
                        return new Coffee(w,c);
            }
}
 
class  CaffeineTank{
            CaffeineTank(){
                        System.out.println("New coffeine tank created.");
            }
            int getIngredient(){
                        return (int)(Math.random()*10);
            }
}
 
class WaterTank{
            WaterTank(){
                        System.out.println("New water tank created.");
            }
            int getIngredient(){
                        return (int)(Math.random()*40);
            }
}
 
class Coffee {
            int water;
            int  caffeine;
            Coffee(int water, int caffeine){
                        this.water = water;this.caffeine= caffeine;
            }
            void drinkCofee(){
                        System.out.println("Drink cofee [water="+water+":coffe="+ caffeine+"]");
            }
}

Exemplu Smartphone

In exemplu de mai jos este modelat un telefon cu cateva caracteristici si comportament. Telefonul poate fi incarcat si poate fi utilizat (metodele incarca() si apeleaza()).

public class Smartphone {
	static int contor;
	String model;
	int pb;
	int k;
 
	Smartphone(){
		contor++;
		k = 1;
		model = "Samsung S4";
		System.out.println("Telefon " +model+" construit.");
		pb = 100;
		afiseazaBaterie();
	}
 
	Smartphone(String model){
		contor++;
		this.model = model;
		k = 1;
		System.out.println("Telefon " +model+" construit.");
		pb = 100;
		afiseazaBaterie();
	}
 
	Smartphone(String model, int k){
		contor++;
		this.model = model;		
		this.k = k;
		System.out.println("Telefon " +model+" construit.");
		pb = 100;
		afiseazaBaterie();
	}
 
	void afiseazaBaterie() {
		System.out.println("Baterie=" + pb + "% model="+model);
	}
 
	void incarca() {
		if (pb < 100)
			pb++;
		afiseazaBaterie();
		// System.out.println("Incarcat="+pb+"%");
	}
 
	void apeleaza() {
		if (pb <= k) {
			System.out.println("Telefon "+model+" descarcat!");
		} else {
			System.out.println("Telefonul "+model+" este utilizat.");
			pb = pb - k;
			afiseazaBaterie();
		}
	}
 
	public static void main(String[] args) {
		Smartphone t1 = new Smartphone();
		Smartphone t2 = new Smartphone();
 
		Smartphone t3 = new Smartphone("Nexus 4");
		Smartphone t4 = new Smartphone("Nexus 4",3);
 
 
		t1.apeleaza();
		t2.apeleaza();
		t3.apeleaza();
		t4.apeleaza();
 
		t1.apeleaza();
		t1.apeleaza();
		for(int i=0;i<5;i++)
			t1.incarca();
 
		t2.apeleaza();
		t1.apeleaza();
 
		for(int i=0;i<5;i++)
			t1.apeleaza();
 
		System.out.println("Telefone construite = "+Smartphone.contor);
	}
}

Exemplu Smartphone modificat

In exemplul de mai jos este modelat un telefon ce are ca si obiect inclus o baterie. Dupa cum se observa din cod, telefonul este construit initial fara nici o baterie atasata. Pentru a-l putea folosi trebuie construit un obiect baterie si atasat telefonului (prin apelarea metodei seteazaBaterie(…)). Acest exemplu modifica clasa anetrioara si muta atributele si comportamentul specifice bateriei intr-o clasa separata.

class Baterie{
	int pb;
	Baterie(){
		pb = 100;
	}
 
	boolean utilizeaza(int x){
		if(pb<=x){
			System.out.println("Baterie descarcata!");
			return false;
		}
		pb = pb - x;
		return true;
	}
 
	void incarca(){
		if(pb<100)
			pb++;
		afiseaza();
	}
 
	void afiseaza(){
		System.out.println("Baterie "+pb+"%");
	}
}
 
public class SmartphoneV2 {
	static int contor;
	String model;
	int k;
	Baterie b = null;
 
	SmartphoneV2(String model, int k){
		contor++;
		this.model = model;		
		this.k = k;
		System.out.println("Telefon " +model+" construit.");
		afiseazaBaterie();
	}
 
	void afiseazaBaterie() {
		if(b!=null)
			b.afiseaza();
		else
			System.out.println("Telefonul nu are baterie!");
	}
 
	void incarca() {
		if(b!=null)
			b.incarca();
		else
			System.out.println("Telefonul nu are baterie!");
	}
 
	void seteazaBaterie(Baterie b){
		if(b!=null){
			System.out.println("Se inlocuieste baterie!");
		}else{
			System.err.println("Se instaleaza baterie!");
		}
		this.b = b;
	}
 
	void apeleaza() {
		System.out.println("Apelare");
		if(b==null){
			System.out.println("Telefonul nu are baterie.");
			return;
		}
 
		if(b.utilizeaza(k)==false){
			System.out.println("Baterie telefon descarcata.");
		}else{
			System.out.println("Telefonul "+model+" este utilizat.");
		}
	}
 
	public static void main(String[] args) {
 
		SmartphoneV2 t4 = new SmartphoneV2("Nexus 4",3);
 
		t4.apeleaza();
 
		Baterie b1 = new Baterie();
 
		t4.seteazaBaterie(b1);
 
		t4.apeleaza();
 
		System.out.println("Telefone construite = "+SmartphoneV2.contor);
	}
}