duminică, 4 ianuarie 2026

Crearea ierarhiilor de clase (Principiul Moștenirii)

Moștenirea este unul din principiile POO şi reprezintă inima acesteia. Ideea de moștenire este una simplă, dar puternică: atunci când doriți să creați o clasă nouă și există deja o clasă care include o parte din codul dorit, puteți deriva noua clasă din clasa existentă. Făcând acest lucru, puteți reutiliza câmpurile și metodele clasei existente fără a fi nevoie să le scrieți singur, sau să le rescrieți astfel generând cod duplicat în proiectul dvs. Deci în moștenire avem: 
  1. Clasa părinte (superclass, supercalsă) – conține atribute și metode generale.
  2. Clasa copil (subclass, subclasă) – moștenește și poate extinde sau modifica comportamentul.
Fie că avem de creat clasele Dreptunghi, Patrat, Romb. Aceste clase au multe trăsături comune (număr de unghiuri, număr de laturi, culoare, ș.a). Folosind conceptul de moștenire vom putea implementa aceste clase ca să fie ușor de înțeles, menținut şi fără cod duplicat. Astfel vom proiecta o clasă generală (superclasă, clasă de bază, clasă părinte), de exemplu clasa Paralelogram, care să poată fi extinsă mai târziu pentru a crea clase specifice (subclase, clase copil, clase derivate), de exemplu clasele Dreptunghi, Romb, Triunghi. Clasa nouă va moșteni toate atributele și metodele clasei de bază, având posibilitatea de a avea metode și atribute proprii.

O subclasă moștenește toți membrii (câmpuri, metode și clase imbricate) din superclasa sa.

După cum am menționat la lecțiile anterioare în Java poate fi implementată doar moștenirea simplă, adică o clasă poate avea o singură superclasă sau clasă părinte.
Cu excepția clasei Object, care nu are superclasă, fiecare clasă are o singură superclasă, și numai una, directă (moștenire unică). În absența oricărei alte superclase explicite, fiecare clasă este implicit o subclasă a clasei Object.

Pentru a deriva o clasă se folosește cuvântul cheie extends în semnătura clasei copil, urmată de numele clasei părinte.

Să ne reamintim sintaxa generală de declarare a claselor:

[public][abstract][final] class nume_clasa
                               [extends nume_super_clasă]
[implements Interfata1,Interfata2,...,InterfataN]
{
     // atribute, metode, blocuri de cod, alte clase 
}

Sintaxa generală a moștenirii:

class SuperClasa
{
    // variabile ale superclasei
    // metode ale superclasei
    // metode ale superclasei
}
class SubClasa extends SuperClasa
{
    // variabile ale subclasei
    // metode ale subclasei
}

Exemplu: 
Definiți clasa Persoana caracterizată de următoarele atribute: nume. Din clasa Persoana derivați clasa Student caracterizată de atributele: nume și notă. 

class Persoana {
    String nume;

    void afiseazaNume() {
        System.out.println("Nume: " + nume);
    }
}

class Student extends Persoana {
    int nota;

    void afiseazaNota() {
        System.out.println("Nota: " + nota);
    }
}

public class Main{
public static void main(String [] args){
Student s = new Student();
s.nume = "Victoria";
s.nota = 9;
System.out.println(s.nume + " " + s.nota);
}}

Deci, Student moștenește variabila nume și metode metoda afiseazaNume(), adică are o poate apela ori de câte ori este necesar, dar adaugă și un atribut propriu care este nota.

Iată un alt exemplu de cod, pentru o posibilă implementare a unei clase Bicicleta:

public class Bicicleta {
public int cadenta;//numărul de rotații a pedalelor pe minut
public int angrenaj;
public int viteza;
public Bicicleta (int startCadenta, int startViteza, int startAngrenaj) {
angrenaj = startAngrenaj;
cadenta = startCadenta;
viteza = startViteza;
}
public void setCadenta(int valoareNoua) {
cadenta = valoareNoua;
}
public void setAngregaj(int valoareNoua) {
angrenaj = valoareNoua;
}
public void aplicaFrina(int decrementare) {
viteza-= decrementare;
}
public void maresteViteza(int incrementare) {
viteza += incrementare;
}}

O declarație de clasă pentru o clasă BicicletaMunte care este o subclasă de Bicicleta ar putea arăta astfel:

public class BicicletaMunte extends Bicicleta{
public int scaunGreutate;
public BicicletaMunte(int startGreuatate, int startCadenta, int startViteza, int startAngrenaj) {
super(startCadenta, startViteza, startAngrenaj);
scaunGreutate = startGreuatate;
}
public void setGreutate(int valoareNoua) {
scaunGreutate = valoareNoua;
}
}

BicicletaMunte moștenește toți membri clasei Bicicleta, dar în același timp adaugă câmpul scaunGreutate și o metodă pentru a-i seta valoarea.

Rețineți!
  1. O subclasă moștenește toți membrii public și protected ai superclasei, indiferent de pachetul în care se află subclasa. 
  2. Dacă subclasa se află în același pachet ca și părintele său, moștenește și membrii impliciți ai părintelui, adică cei care nu au nici un modificator de acces specificat. 
  3. Puteți folosi membrii moșteniți așa cum sunt, îi puteți înlocui, îi puteți ascunde sau îi puteți completa cu membri noi:
  4. Câmpurile moștenite pot fi utilizate direct, la fel ca orice alte câmpuri.
  5. Puteți declara un câmp din subclasă cu același nume cu cel din superclasă, astfel ascunzându-l. (NU este recomandat).
  6. Puteți declara câmpuri noi în subclasă care nu sunt în superclasă.
  7. Metodele moștenite pot fi utilizate direct așa cum sunt.
  8. Puteți scrie o nouă metodă de instanță în subclasă care are aceeași semnătură cu cea din superclasă, suprascriind-o astfel (overriding).
  9. Puteți scrie o nouă metodă statică în subclasă care are aceeași semnătură cu cea din superclasă, ascunzând-o astfel.
  10. Puteți declara metode noi în subclasă care nu sunt în superclasă.
  11. Puteți scrie un constructor de subclasă care invocă constructorul superclasei, fie implicit, fie folosind cuvântul cheie super.
  12. Nu puteți moșteni membrii privați ai clasei părinte. Totuși, dacă superclasa are metode public sau protected pentru accesarea câmpurilor sale private, acestea pot fi folosite și de subclasă.
În cadrul moștenirii poate fi folosită variabila predefinită super în următoarele cazuri:
1. pentru a referi o variabilă din clasa părinte: super.<nume_variabila>

class ClasaParinte{
int num=100;
}

class Subclasa extends ClasaParinte{
int num=110;
void printNumber(){
System.out.println(super.num);
}

public static void main(String args[]){
Subclasa obj= new Subclasa ();
obj.printNumber();
}}


Atenție!
În cadrul moștenirii, o subclasă poate avea câmpuri cu același nume ca și clasa părinte. În acest caz, câmpul din clasa părinte este "ascuns" în favoarea câmpului cu același nume din subclasă. În codul de mai sus, subclasa Subclasa definește un câmp num care ascunde câmpul cu același nume din clasa părinte ClasaParinte. În metoda printNumber(), se folosește super.num pentru a accesa câmpul num din clasa părinte, care este ascuns în subclasă.
Dacă omitem super.num prioritate va avea variabila din clasa copil:

class ClasaParinte{
int num=100;
}

class Subclasa extends ClasaParinte{
int num=110;
void printNumber(){
System.out.println(num);
}

public static void main(String args[]){
Subclasa obj= new Subclasa ();
obj.printNumber();
}} 


Este important de reținut că utilizarea ascunderii câmpurilor poate duce la confuzie și este bine să fiți atenți la acest aspect. În mod obișnuit, este recomandat să evitați ascunderea câmpurilor, iar dacă este necesar, să fiți conștienți de acest lucru și să folosiți cu atenție super pentru a accesa câmpurile din clasa părinte.

2. pentru a forța apelarea unei metode din cadrul unei clase părinte: 
super.<method_name>

3. pentru apelarea constructorului clasei părinte. Constructorii nu sunt membri, deci nu sunt moșteniți de subclase, dar constructorul superclasei poate fi invocat din subclasă folosind apelul: super([lista_argumente])

Exemplu: 

class Persoanaa{
protected String nume, prenume;
protected int virsta;
Persoanaa (String nume, String prenume, int virsta){
this.nume = nume;
this.prenume = prenume;
this.virsta = virsta;
}
void detalii() {
System.out.print("Nume "+nume+" \nPrenume "+ prenume+" \nVirsta "+virsta);
}}
class Elev extends Persoanaa{
private String adresa;
private double media;
Elev(String nume, String prenume, int virsta, String adresa, double media){
super(nume, prenume, virsta);
this.adresa = adresa;
this.media = media;
}
void detalii() {
super.detalii();
System.out.println(" \nAdresa "+adresa+" \nMedia "+ media);
}}
class Test_mostenire {
public static void main(String[] args) {
Elev e1 = new Elev("Creanga", "Ion", 16, "str. Sarmizegetusa 48", 10);
e1.detalii();
}}


Rețineți !
  1. Apelul super() trebuie să fie prima instrucțiune din corpul constructorului subclasei.
  2. Dacă constructorul subclasei nu va invoca explicit constructorul superclasei, compilatorul Java va însera automat un apel la constructorul clasei fără parametri.
  3. Dacă superclasa nu are constructor fără argumente veți obține eroare de compilare.
  4. O clasă declarată final nu poate fi extinsă.
Importanța moștenirii:
1. Moștenirea permite dezvoltatorilor să reutilizeze codul deja scris într-o clasă de bază, fără a-l rescrie în mod repetat în clasele derivate. Acest lucru duce la o eficiență sporită în dezvoltarea software-ului, deoarece codul poate fi scris și testat o singură dată.
2. Moștenirea permite extinderea funcționalității claselor de bază prin adăugarea de metode sau atribute noi în clasele derivate. Astfel, este ușor să adăugați funcționalități noi fără a afecta funcționalitatea existentă.
3. Moștenirea contribuie la organizarea și structurarea codului. Clasele de bază pot reprezenta concepte generale sau abstracte, iar clasele derivate pot să ofere implementări specifice sau detaliate ale acestor concepte.
4. Moștenirea facilitează implementarea polimorfismului, unde obiecte de tipuri diferite pot fi tratați în mod uniform. Acest lucru duce la o mai mare flexibilitate și modularitate în proiectare.
5. Prin intermediul moștenirii, modificările într-o clasă de bază pot avea impact în toate clasele derivate. Astfel, întreținerea și actualizarea codului pot fi mai ușoare, deoarece modificările trebuie făcute doar într-un singur loc.
6. Moștenirea reflectă relații de tip "este-un" între clase. De exemplu, dacă avem o clasă de bază "Animal" și o clasă derivată "Câine", putem spune că "Câinele este un Animal". 

În ansamblu, moștenirea în programare contribuie la creșterea eficienței, flexibilității și ușurinței în dezvoltarea și întreținerea software-ului. Dar...moștenirea o folosim doar când există o relație logică reală „este un”. 
Mulți începători o folosesc forțat, când de fapt ar fi mai corectă alte tipuri de relații despre care noi vom discuta. În proiecte reale, folosirea abuzivă a moștenirii duce deseori la ierarhii rigide și greu de întreținut.

Știați că...
Java 17 introduce câteva funcții noi și îmbunătățiri menite să îmbunătățească lizibilitatea codului, mentenața și securitatea? 
Una dintre cele mai notabile completări la Java 17 este introducerea claselor sealed care oferă mai mult control asupra ierarhiilor de clasă și ajută la aplicarea unei încapsulări mai puternice. Prin declararea unei clase ca sealed, dezvoltatorii pot specifica care clase au voie să o extindă. Acest lucru restricționează efectiv subclasele la un set predefinit, prevenind astfel extensiile neautorizate. Însă ... este suficient pentru această lecție .... vom discuta în altă postare această facilitate a limbajului. 
Dar dacă nu aveți răbdare ... 😍 ...pentru mai multe detalii accesați aici 👉 https://websparrow.org/java/java-17-sealed-classes 

Succes!
❤️