Un exemplu: clasa Persoana si subclasa Student

Studentul are toate atributele unei persoane, dar are și atribute și metode specifice. În consecință, clasa Student poate fi derivată din clasa Persoana. În continuare dăm exemple de declarare și de testare a celor două clase.

Declararea clasei Persoana

În fișierul Persoana.java, reprodus aici, este declarată clasa Persoana.
 
public class Persoana {
  private String nume, prenume;
  private int anNastere;

  public Persoana(String nume, String prenume, int anNastere) {
    this.nume=nume;
    this.prenume=prenume;
    this.anNastere=anNastere;
  }

  public Persoana(Persoana pers) {
    nume=pers.nume;
    prenume=pers.prenume;
    anNastere=pers.anNastere;
  }

  public String nume() { return nume; }

  public String prenume() { return prenume; }

  public int anNastere() { return anNastere; }

  public int varsta(int anCurent) { return anCurent-anNastere; }

  public String toString() {
    return nume+" "+prenume+" "+anNastere;
  }

  public int hashCode() {
    return nume.hashCode()+prenume.hashCode()/1000+anNastere%100;
  }
}

Clasa contine trei câmpuri private: două referințe la String (nume și prenume) și un câmp de tip int (anNaștere). Întrucât câmpurile sunt private, ele nu sunt vizibile din alte clase (nici chiar din subclasele clasei Persoana).

Clasa Persoana are  doi constructori. Primul constructor primește ca argumente valorile celor trei câmpuri: nume, prenume, anNaștere. Întrucât argumentele au aceleași nume cu câmpurile corespunzătoare, în corpul constructorului numele de câmpuri sunt calificate cu referința this. Aceasta se putea evita schimband numele argumentelor. Al doilea constructor are ca argument o instanță a clasei Persoana, fiind ceeace se numește un "constructor de copiere". Vom vedea utilitatea lui in continuare, la declararea clasei Student.

Metodele nume(), prenume() și anNastere() au rolul de a întoarce fiecare valoarea câmpului corespunzator. Fiind publice, ele pot fi utilizate în alte clase. Așa dar, este posibil să obținem în altă clasă valorile câmpurilor unui obiect Persoana, dar nu putem modifica aceste valori. Remarcăm că este posibil ca numele unei metode să coincidă cu cel al unui câmp. Nu are loc nici o confuzie, întrucât numele metodei este insoțit întotdeauna de paranteze, ca în expresia p1.nume(), care are ca valoare numele persoanei p1.

Metoda varsta(int anCurent) întoarce vârsta persoanei, fiind dat ca argument anul pentru care se calculează. Este un exemplu de metodă care întoarce un atribut al persoanei (vârsta), care nu este memorat într-un câmp, ci se obține prin calcul.

Metodele toString() și hashCode() redefinesc metodele corespunzătoare ale superclasei Object.

Declararea clasei Student

Clasa publică Student este declarată în fișierul Student.java și este reprodusă aici.
 
public class Student extends Persoana {
  private String fac, gr;
  private int anStudii;

  public Student(String nume, String prenume, int anNastere, 
      String facultate, int an, String grupa) {
    super(nume, prenume, anNastere);
    fac=facultate;
    anStudii=an;
    gr=grupa;
  }

  public Student(Persoana pers, String facultate, int an, 
        String grupa) {
    super(pers);
    fac=facultate;
    anStudii=an;
    gr=grupa;
  }

  public Student(Student stud) {
   super(stud.nume(), stud.prenume(), stud.anNastere());
    fac=stud.fac;
    anStudii=stud.anStudii;
    gr=stud.gr;
  }

  public String facultate() { return fac; }

  public int an() { return anStudii; }

  public String grupa() { return gr; }

  public String toString() { 
    return super.toString()+" fac: "+fac+" an: "+anStudii+
      " grupa: "+gr;
  }
}

Clasa Student moștenește câmpurile și metodele superclasei Persoana. În plus, conține următoarele câmpuri private: două referințe la String (fac - facultatea și gr - grupa) și un int (anStudii).

Au fost declarați trei constructori. Primul are ca argumente valorile tuturor câmpurilor, atât ale celor moștenite de la superclasă, cât și ale celor proprii. Întrucât câmpurile superclasei sunt private, nu a fost posibilă inițializarea lor directă, prin operații de atribuire, ci prin apelarea constructorului superclasei sub forma
    super(nume, prenume, anNastere);
Al doilea constructor are ca prim argument o referință la persoană, iar ultimele trei argumente sunt aceleași ca la constructorul precedent. Pentru inițializarea câmpurilor superclasei a fost invocat constructorul acesteia sub forma
    super(pers);
în care pers este referința la o Persoana. S-a utilizat aici constructorul de copiere Persoana(Persoana pers). Remarcăm că nu am fi putut utiliza instrucțiunea
    super(pers.nume, pers.prenume, pers.anNastere);
deoarece câmpurile clasei Persoana sunt private și nu sunt, deci, accesibile din clasa Student. În lipsa constructorului de copiere, am fi putut utiliza in schimb instructiunea
    super(pers.nume(), pers.prenume(), pers.anNastere());
deoarece metodele nume(), prenume() și anNastere() sunt publice.

Metodele publice facultate(), an() și grupa() întorc valorile câmpurilor specifice clasei Student, respectiv fac, anStudii și gr.

Metoda toString() redefinește metoda cu aceeași signatură a superclasei.

Utilizarea claselor Persoana și Student

Aplicația din fișierul TestStud.java are ca obiectiv testarea claselor Persoana și Student.
 
class TestStud {
  public static void main(String args[]) {
    Persoana p1=new Persoana("Vasiliu","George",1981), p2;
    Student s1=new Student(p1, "NIE",2,"2221a"),
 s2=new Student("Ionescu","Maria", 1980, "NIE", 2, "2322b"), s3;
    Object ob1, ob2, ob3;
    Persoana tabPers[]=new Persoana[3];
    ob1=s1;
    ob2=p2=s2;
    ob3=p1;
    System.out.println("p1="+p1);
    System.out.println("s1="+s1);
    System.out.println("s2="+s2);
    System.out.println("ob1="+ob1);
    System.out.println("p2="+p2);
    System.out.println("ob2="+ob2);
    System.out.println("ob3="+ob3);
    System.out.println("Numele lui p1: "+p1.nume());
    System.out.println("Numele lui s1: "+s1.nume());
    System.out.println("Numele lui ob1: "+((Persoana)ob1).nume());
    s3=(Student)ob2;
    System.out.println("s3="+s3);
    System.out.println("In anul 2000, s3 are "+s3.varsta(2000)+" ani");
    System.out.println("p2.hashCode()="+p2.hashCode());
    System.out.println("s1.hashCode()="+s1.hashCode());
    tabPers[0]=p1; tabPers[1]=s1; tabPers[2]=s2;
    System.out.println("Tabloul de persoane tabPers contine:");
    System.out.println("Clasa lui p1: "+p1.getClass().getName());
    System.out.println("Clasa lui ob2: "+ob2.getClass().getName());
    for(int i=0; i<tabPers.length; i++)
       System.out.println(tabPers[i]);
  }
}

Se testează funcționarea metodelor polimorfe toString() din clasele Persoană și Student, funcționarea castului de la Object la Student si funcționarea metodei hashCode(), redefinită în clasa Persoana și mostenită de clasa Student. Se testează, de asemenea, metoda getClass(), moștenita de la clasa Object. Se construiește și se utilizează un tablou de referințe la Persoana.

O variantă în care se utilizează câmpuri protejate

În fișierele Persoana1.java, Student1.java si TestStud1.java sunt date variante ale fișierelor comentate mai sus, în care sunt făcute următoarele modificări:
  - în clasa Persoana1 câmpurile nu mai sunt private, ci protejate;
  - în clasa Persoana1 s-a introdus în plus un constructor fără argumente. Acest constructor este necesar pentru a fi invocat implicit de constructorul clasei Student1, așa cum vom arăta mai jos;
  - în primul constructor al clasei Student1 nu a mai fost invocat explicit constructorul clasei Persoana. Aceasta înseamnă că, la construirea instanței Student1, a fost invocat implicit constructorul fără argumente Persoana1() al superclasei. Atribuirea de valori câmpurilor protejate ale superclasei se face, apoi, direct în corpul constructorului clasei Persoana1. S-au folosit, în acest scop, instrucțiuni de forma this.nume=nume, întrucât numele câmpului coincide cu cel al argumentului. Referința this este utilizată corect, intrucât câmpul superclasei Persoana1 a fost moștenit de clasa Student1, ne fiind acoperit în aceasta. Se putea folosi, însă, și referința super sub forma super.nume=nume, deoarece câmpul nume aparține, de fapt, superclasei;
  - în al doilea constructor al clasei Student1 nu a fost invocat explicit, de asemenea, constructorul superclasei. Atribuirea de valori câmpurilor protejate ale superclasei Persoana1 s-a făcut direct în corpul clasei Student1, prin instrucțiuni de forma nume=pers.nume, unde Pers este argumentul din clasa Persoana1;
  - s-au înlocuit peste tot, în cele trei fișiere, numele de clase Persoana și Student prin Persoana1 și Student1.

Complilând fișierul TestStud1.java și executând aplicația TestStud1 constatăm că se obțin aceleași rezultate ca în cazul aplicației TestStud. Întrucât clasele Persoana1 și Student1 sunt publice, compilarea explicită a fișierelor respective nu este necesară, deoarece ea se face automat când se compilează fișierul TestStud1.java.



© Copyright 2000 - Severin BUMBARU, Universitatea "Dunărea de Jos" din Galați