Clasa Object
Introducere în Clasa Object
Object (java.lang.Object
) reprezintă fundamentul întregului sistem de clase în Java. Este superclasa tuturor claselor, ceea ce înseamnă că orice clasă Java moștenește implicit această clasă dacă nu extinde explicit o altă clasă (care la randul ei mosteneste Object, deci in cele din urma in varful ierarhiei vom gasi clasa Object).
// Aceste două declarații sunt echivalente
public class ExempluClasa { }
public class ExempluClasa extends Object { }
Această moștenire universală asigură că toate obiectele din Java au un set minim de funcționalități comune.
Metodele Fundamentale ale Clasei Object
1. toString()
Convertește obiectul într-o reprezentare textuală.
@Override
public String toString() {
return "ExempluClasa{" +
"atribut1=" + atribut1 +
", atribut2='" + atribut2 + '\'' +
'}';
}
Utilizare: Afișarea informațiilor despre obiect în mod lizibil. Când să suprascrii: Când ai nevoie de o reprezentare personalizată a obiectului tău.
2. equals(Object obj)
Determină dacă obiectul curent este egal cu alt obiect.
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
ExempluClasa that = (ExempluClasa) obj;
return atribut1 == that.atribut1 &&
Objects.equals(atribut2, that.atribut2);
}
Utilizare: Compararea obiectelor pentru egalitate logică, nu doar referințială. Când să suprascrii: Când dorești să definești reguli proprii de egalitate între obiecte.
Contractul metodei equals()
Implementarea metodei equals() trebuie să respecte următoarele proprietăți:
- Reflexivitate: Pentru orice referință non-null x, x.equals(x) trebuie să returneze true.
- Simetrie: Pentru orice referințe non-null x și y, x.equals(y) trebuie să returneze true dacă și numai dacă y.equals(x) returnează true.
- Tranzitivitate: Pentru orice referințe non-null x, y și z, dacă x.equals(y) returnează true și y.equals(z) returnează true, atunci x.equals(z) trebuie să returneze true.
- Consistență: Pentru orice referințe non-null x și y, apeluri multiple ale x.equals(y) trebuie să returneze consistent true sau false, atât timp cât nu s-a modificat nicio informație folosită în comparația equals.
- Comportament cu null: Pentru orice referință non-null x, x.equals(null) trebuie să returneze false.
Pași pentru implementarea corectă a equals()
- Verifică dacă referințele obiectelor sunt identice folosind operatorul == (optimizare)
- Verifică dacă parametrul este null
- Verifică dacă obiectele sunt de același tip (folosind getClass() sau instanceof)
- Convertește parametrul la tipul clasei tale
- Compară fiecare câmp relevant pentru egalitate:
- Pentru primitive, folosește operatorul ==
- Pentru obiecte, folosește Objects.equals() sau apelează equals() pe obiectul non-null
- Pentru arrays, folosește Arrays.equals() sau Arrays.deepEquals() pentru array-uri multidimensionale
Exemple de erori comune în implementarea equals()
// GREȘIT: Nu verifică tipul obiectului
public boolean equals(Object obj) {
Student other = (Student) obj; // Poate genera ClassCastException
return this.id == other.id;
}
// GREȘIT: Semnătură incorectă (suprascrie metoda)
public boolean equals(Student obj) { // Aceasta este o metodă nouă, nu o suprascriere!
return this.id == obj.id;
}
3. hashCode()
Generează un cod hash pentru obiect, esențial pentru utilizarea în colecții.
@Override
public int hashCode() {
return Objects.hash(atribut1, atribut2);
}
Utilizare: Necesar pentru funcționarea corectă în HashMap, HashSet etc. Regulă importantă: Trebuie suprascrisa împreună cu equals().
Contractul metodei hashCode()
Implementarea metodei hashCode() trebuie să respecte următoarele reguli:
- Consistență internă: Dacă nu s-a modificat nicio informație folosită în equals(), mai multe apeluri ale hashCode() pe același obiect trebuie să returneze consecvent aceeași valoare.
- Concordanță cu equals(): Dacă două obiecte sunt egale conform metodei equals(), atunci apelând hashCode() pe fiecare dintre ele trebuie să se obțină aceeași valoare hash.
- Coliziuni: Deși nu este obligatoriu, este de dorit ca obiecte diferite (conform equals()) să returneze valori hash diferite, pentru a îmbunătăți performanța structurilor de date bazate pe hash.
Implementări ale hashCode()
- Folosind Objects.hash() (Java 7+)
@Override public int hashCode() { return Objects.hash(atribut1, atribut2, atribut3); }
- Implementare manuală
@Override public int hashCode() { int result = 17; // număr prim inițial result = 31 * result + atribut1; // înmulțire cu un alt număr prim result = 31 * result + (atribut2 != null ? atribut2.hashCode() : 0); // se continuă pentru toate atributele relevante return result; }
- Pentru array-uri
@Override public int hashCode() { int result = 17; result = 31 * result + Arrays.hashCode(arrayAtribut); return result; }
Probleme comune cu hashCode()
- Utilizarea atributelor diferite: Folosirea unor atribute în hashCode() diferite de cele folosite în equals().
- Ignorarea relației cu equals(): Dacă suprascrii equals(), trebuie să suprascrii și hashCode().
- Hash ineficient: Generarea aceluiași hash pentru prea multe obiecte diferite, ceea ce reduce performanța colecțiilor.
Impactul în colecții bazate pe hash
Implementarea incorectă a hashCode() poate duce la probleme grave când obiectele sunt stocate în colecții precum:
- HashMap
- HashSet
- LinkedHashMap
- LinkedHashSet
- Hashtable
Exemplu de problemă:
Student s1 = new Student(1, "Ana");
Student s2 = new Student(1, "Ana");
// Dacă equals() returnează true dar hashCode() nu este suprascris corect:
HashSet<Student> set = new HashSet<>();
set.add(s1);
set.add(s2);
System.out.println(set.size()); // Ar putea afișa 2 în loc de 1 (duplicat)!
4. getClass()
Returnează un obiect de tip Class care reprezintă clasa obiectului la runtime.
public void afisareTipObiect() {
System.out.println("Tipul obiectului: " + this.getClass().getName());
}
Utilizare: Identificarea tipului real al unui obiect, reflecție. Notă: Nu poate fi suprascrisă (metodă finală).
5. clone()
Creează o copie a obiectului.
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
Utilizare: Crearea unei copii a obiectului. Cerințe: Clasa trebuie să implementeze interfața Cloneable.
6. finalize()
Invocată de garbage collector înainte de a elibera memoria obiectului.
@Override
protected void finalize() throws Throwable {
// Eliberare resurse
System.out.println("Obiect finalizat");
super.finalize();
}
Utilizare: Eliberarea resurselor externe. Notă: Depreciat în versiunile recente de Java în favoarea try-with-resources.
Relația dintre equals() și hashCode()
Este esențial să respecți contractul dintre aceste două metode:
- Dacă două obiecte sunt egale prin equals(), trebuie să aibă același hashCode()
- Dacă două obiecte au același hashCode(), nu înseamnă neapărat că sunt egale prin equals()
Implicații practice ale relației equals-hashCode
1. Comportamentul în colecții
Map<Student, String> noteStudenti = new HashMap<>();
Student s1 = new Student(1, "Ana");
Student s2 = new Student(1, "Ana"); // Același ID, ar trebui să fie "egal" cu s1
noteStudenti.put(s1, "10");
String nota = noteStudenti.get(s2);
System.out.println(nota); // Should print "10" if equals and hashCode are implemented correctly
2. Procesul de căutare în HashMap/HashSet
- Se calculează hashCode() pentru cheia căutată
- Se identifică “bucket-ul” corespunzător în structura internă
- Pentru fiecare element din bucket:
- Se verifică dacă hashCode-urile sunt egale
- Dacă da, se apelează equals() pentru a confirma egalitatea
3. Consecințe ale implementării incorecte
Situație | Consecință |
---|---|
equals() suprascris, hashCode() nu | Obiectele egale pot ajunge în bucket-uri diferite, ducând la duplicate aparente în HashSet |
Ambele metode suprascrise incorect | Comportament imprevizibil în colecții, căutări eșuate, duplicare |
hashCode() returnează constant | Toate obiectele ajung în același bucket, transformând HashMap într-o listă liniară (ineficient) |
Exemplu complet cu testare
public class Student {
private int id;
private String nume;
public Student(int id, String nume) {
this.id = id;
this.nume = nume;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Student student = (Student) obj;
return id == student.id; // Considerăm că ID-ul este unicul identificator
}
@Override
public int hashCode() {
return Objects.hash(id); // Folosim același câmp ca în equals()
}
// Test funcționalitate
public static void main(String[] args) {
Student s1 = new Student(1, "Ana");
Student s2 = new Student(1, "Ana");
Student s3 = new Student(2, "Ion");
System.out.println("s1 equals s2: " + s1.equals(s2)); // true
System.out.println("s1 hashCode == s2 hashCode: " + (s1.hashCode() == s2.hashCode())); // true
HashSet<Student> students = new HashSet<>();
students.add(s1);
students.add(s2);
students.add(s3);
System.out.println("Set size: " + students.size()); // 2, nu 3 (s1 și s2 considerate duplicate)
}
}
Exemplu Practic
public class Student {
private int id;
private String nume;
public Student(int id, String nume) {
this.id = id;
this.nume = nume;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Student student = (Student) obj;
return id == student.id;
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public String toString() {
return "Student{id=" + id + ", nume='" + nume + "'}";
}
}
Importanța Clasei Object în Java
- Polimorfism: Orice obiect poate fi tratat ca un Object
- Funcționalitate comună: Toate obiectele au aceleași metode de bază
- Interoperabilitate: Facilitează lucrul cu diverse tipuri de obiecte în colecții și APIs