Description: Description: Description: Description: Description: Description: Description: Description: Description: java-cup                     Catedra de Telecomunicatii                    Description: Description: Description: Description: Description: Description: Description: Description: Description: logo ETTI

 

                      Programare Orientata spre Obiecte (POO)  

 

11/11/2010          http://discipline.elcom.pub.ro/POO-Java

 

 

Laborator 3

Metode si constructori. Supraincarcarea numelor. Relatii intre clase: asocierea si utilizarea

3.1. Descrierea laboratorului

 

In aceasta lucrare de laborator vor fi acoperite urmatoarele probleme:

- Specificarea comportamentului claselor (metode si constructori):

- Semnaturile metodelor si returnarea valorilor (in Java)

            - Constructorii - functiile pentru initializarea obiectelor

            - Supraincarcarea numelor metodelor si constructorilor polimorfismul static

 

- Relatii intre clase: Asocierea si utilizarea

 

- Studiu de caz: Clasele Mesaj si Pachet

            - Structura de baza: campuri, constructori, metode

            - Supraincarcarea numelor. Relatii intre clase

 

- Studiu de caz: Clasele Grupa, DatePersonale si SituatieCurs si clasa Student actualizata

 

- Exercitii in laborator

 

 

3.2. Metode si constructori. Supraincarcarea numelor

3.2.1. Semnatura metodei. Returnarea valorilor

 

Dupa invocare (apelare) metodele obiectelor efectueaza sarcini (in general utilizand argumentele si valorile campurilor obiectului) care se pot finaliza prin returnarea unei valori.

Definitia unei metode contine 2 parti: semnatura (antetul, declaratia) si corpul (blocul, segmentul, secventa de instructiuni a implementarii).

Semnatura specifica:

- numele metodei,

- lista de parametri formali (numarul, ordinea, tipul si numele lor),

- tipul valorii returnate,

- specificatori ai unor proprietati explicite (modificatori ai proprietatilor implicite).

Daca metoda nu returneaza nici o valoare, tipul valorii returnate este declarat void. Tipul valorii returnate poate fi unul dintre cele 8 tipuri primitive Java (byte, short, int, long, float, double, boolean si char), sau unul dintre cele 3 tipuri referinta (tablourile, clasele si interfetele Java).

Corpul metodei contine secventa de instructiuni care specifica pasii necesari indeplinirii sarcinilor (evaluari expresii, atribuiri, decizii, iteratii, apeluri metode). Returnarea valorilor este specificata in codul metodelor prin instructiunea return urmata de o expresie care poate fi evaluata la o valoare de tipul declarat in semnatura.

 

In laborator: Pentru exemplul de mai jos:

1. Identificati numele metodelor.

2. Incercati sa determinati metodele definite de programator si metodele bibliotecilor Java.

3. Identificati metodele care sunt definite (cu semnatura si corp) si metodele care sunt apelate.

4. Identificati numele si tipul parametrilor si valorile argumentelor in fiecare caz.

5. Identificati tipul valorilor returnate in fiecare caz.

6. Identificati instructiunile return si comparati tipul expresiilor cu tipul declarat.

 

 import javax.swing.JOptionPane;            // clasa de biblioteca (package) Java, externa
                                            // dar accesibila codului care urmeaza
 public class DialogUtilizator01 {          // clasa definita de utilizator (declaratia)
                                            // corpul clasei:
    public String nextLine(String text) {                // metode Java (operatii) 
        return JOptionPane.showInputDialog(text);        // returneaza o valoare tip String
    } 
    public int nextInt(String text) {                    // returneaza o valoare tip int
        return Integer.parseInt(JOptionPane.showInputDialog(text));   
    } 
    public void println(String text) {                 // nu returneaza nici o valoare
        JOptionPane.showMessageDialog(null, text);
    } 
 } 

 

In documentatia (API-ul) claselor Java pot fi gasite detalii privind clasa JOptionPane.

 

In laborator:

1. Lansati mediul BlueJ.   Inchideti proiectele anterioare (cu Ctrl+W sau Project si Close).

2. Creati un nou proiect numit dialog (cu Project, apoi New Project…, selectati D:/,

         apoi POO2007, apoi numarul grupei, apoi scrieti dialog).

3. Creati o noua clasa, numita DialogUtilizator01, cu New Class…

4. Double-click pe noua clasa (deschideti editorul) si inlocuiti codul cu cel de sus.

5. Compilati codul apoi creati un obiect din noua clasa.

 

In laborator:

1. Executati metoda nextLine() dandu-i ca parametru “Introduceti numele dumneavoastra”.

 

    

 

2. Inspectati valoarea returnata.    

3. Executati si metodele nextInt() si println() si urmariti efectul lor.

 

Atentie! Nu uitati: Daca bara de stare a executiei este activa () verificati cu Alt+Tab daca a aparut o fereastra Java (in spatele ferestrelor vizibile).

 

Clasa Java pentru testarea clasei anterior definite:

 public class RunDialogUtilizator01 {   
    public static void main(String[] args) { 
        DialogUtilizator01 d01 = new DialogUtilizator01();                // obiect testat
        String linie = d01.nextLine("Introduceti numele dumneavoastra");  // metoda testata
        d01.println("Buna ziua " + linie + ".  Bine ai venit in lumea Java!"); 
    } 
 }

 

 

In laborator:

1. Tot in proiectul dialog, creati o noua clasa numita RunDialogUtilizator01 

2. Double-click pe noua clasa (deschideti editorul) si inlocuiti codul cu cel de sus.

3. Compilati codul si executati metoda main() a noii clase (right-click pe clasa si selectare main()).

 

3.2.2. Constructorii

 

Constructorul Java este un tip special de functie, care

- are acelasi nume cu numele clasei in care este declarat,

- este utilizat pentru a initializa orice nou obiect de acel tip (stabilind valorile campurilor/ atributelor obiectului, in momentul crearii lui dinamice),

- nu returnează nici o valoare,

- are aceleasi niveluri de accesibilitate, reguli de implementare a corpului si reguli de supraincarcare a numelui ca si metodele obisnuite.

 

In Java nu este neaparat necesara scrierea unor constructori pentru clase, deoarece un constructor implicit este generat automat de sistemul de executie (DOAR) pentru o clasa care nu declara explicit constructori. Acest constructor nu face nimic (nici o initializare, implementarea lui continand un bloc de cod vid: { }). De aceea, orice initializare dorita explicit impune scrierea unor constructori.

 

Un exemplu de clasa similara celei anterioare, dar care defineste explicit un constructor:

 

 import java.util.Scanner;             // clasa de biblioteca (package) Java
 public class DialogUtilizator02 {     // clasa definita de utilizator
    private Scanner sc;                        // camp Java (atribut) 
    private String prompt; 
    public DialogUtilizator02(String nume) {    // constructor (initializator) 
        this.sc = new Scanner(System.in); 
        this.prompt = nume + "> ";
    } 
    public String nextLine(String text) {      // metode Java (operatii) 
        System.out.print(this.prompt + text);
        return this.sc.nextLine(); 
    } 
    public int nextInt(String text) { 
        System.out.print(this.prompt + text);
        return this.sc.nextInt(); 
    }
    public void println(String text) {   System.out.println(text);   } 
 } 

 

In documentatia (API-ul) claselor Java pot fi gasite detalii privind clasa Scanner.

 

In laborator:

1. Tot in proiectul dialog, creati o noua clasa numita DialogUtilizator02  

2. Intrati in codul clasei (in editor), inlocuiti-i codul cu cel dat, apoi compilati-l.

3. Creati un obiect nou, numit d02,  pasandu-i constructorului valoarea "test".

     

4. Executati metoda nextLine() dandu-i ca parametru "Grupa : ". Ce apare in Terminal Window?

5. Inspectati valoarea returnata.    

6. Executati si metodele nextInt() si println() si urmariti efectul lor.

 

3.2.3. Supraincarcarea numelor metodelor si constructorilor

 

Java suporta supraincarcarea numelor (name overloading) pentru metode si constructori. Astfel, o clasa poate avea orice numar de metode cu acelasi nume cu conditia ca listele lor de parametri sa fie diferite.

 

In mod similar, o clasa poate avea orice numar de constructori (acestia avand toti acelasi nume - identic cu numele clasei) cu conditia ca listele lor de parametri sa fie diferite. De exemplu, codul clasei anterioare poate fi completat cu constructorul:

 

     public DialogUtilizator02() {        // constructor (initializator) 
        this.sc = new Scanner(System.in); 
        this.prompt = "IMPLICIT" + "> ";  // echivalent cu: this.prompt = this("IMPLICIT ");    
    } 

 

In laborator:

1. Intrati in codul clasei DialogUtilizator02 (in editor), adaugati constructorul, apoi recompilati.

2. Creati un obiect nou folosind noul constructor. Ce observati?

3. Executati-i metoda println() dandu-i ca parametru "POO". Ce apare in Terminal Window?

4. Creati un obiect nou folosind primul constructor, caruia ii pasati "EXPLICIT".

5. Executati-i metoda println() dandu-i ca parametru "POO". Ce apare in Terminal Window?

 

In laborator:

1. Concepeti si editati codul unei metode noi a clasei DialogUtilizator02, cu semnatura:

         public void println()

    care nu primeste parametru si afiseaza in Terminal Window (folosind System.out.println())

           textul: "Nu am primit nici un parametru".

2. Recompilati clasa si creati un obiect nou folosind noul constructor.

3. Executati noua metoda println(). Ce apare in Terminal Window?

4. Executati din nou vechea metoda println(), cu parametru "x". Ce apare in Terminal Window?

 

3.2.4. Relatii intre clase: asocierea si utilizarea

 

Legătura este o cale între obiectele care se cunosc (văd) unul pe altul (îşi pot transmite mesaje – apelurile de metode), pentru aceasta avand referinte unul către celălalt.

 

Fie clasele Java:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

 public class Point {
    private int x;     
    private int y;
 
    public Point(int abscisa, int ordonata) {
       x = abscisa;
       y = ordonata;
    }
    public void moveTo(int abscisaNoua, int ordonataNoua) {
       x = abscisaNoua;
       y = ordonataNoua;
    }
    public void moveWith(int deplasareAbsc, int deplasareOrd) {
       x = x + deplasareAbsc;
       y = y + deplasareOrd;
    }
    public int getX() {   return x;  }
    public int getY() {   return y;  }
 }

 

1

2

3

4

5

6

7

8

9

 public class UtilizarePoint {
    private static Point punctA;     // referinta, legatura catre un obiect Point
 
    public static void main(String[] args) {            
       punctA = new Point(3, 4);     // alocare si initializare atribut punctA
       punctA.moveTo(3, 5);          // trimitere mesaj moveTo() catre punctA
       punctA.moveWith(3, 5);        // trimitere mesaj moveWith() catre punctA
    }
 }

 

Un obiect sau o clasa “vede” un alt obiect daca are o referinta catre el, si astfel ii poate apela metodele. Se spune ca exista o legatura intre obiectul care are referinta catre obiectul referit.

De exemplu, clasa UtilizarePoint are o referinta punctA  catre un obiect al clasei Point.

 

Fiecărei familii de legături între obiecte ale aceleiasi clase ii corespunde o relaţie între clasele acelor obiecte.

 

Asocierea este o relatie care exprimă un cuplaj (o dependenta) redus între clase (clasele asociate rămânând relativ independente). Clasele Point si UtilizarePoint sunt de exemplu intr-o relatie de asociere (cu navigabilitate) unidirectionala:

 

Clasa UtilizarePoint are un atribut punctA de tip Point care permite clasei UtilizarePoint sa trimita mesaje unui obiect (pointA) al clasei Point.

 

 

    private Point punctA;                               // atribut de tip Point

 

Clasa Point in schimb nu are nici o referinta catre clasa UtilizarePoint care sa ii permita trimiterea de mesaje (invocari de metode).

 

Asocierile unidirectionale pot fi considerate relatii de utilizare. Ele se reprezinta prin sageti indreptate pe directia catre care exista referinta (catre care se pot trimite mesaje). Clasa RunDialogUtilizator01 utilizeaza un obiect al clasei DialogUtilizator01:

 

 

 

3.3. Studiu de caz: clasele Mesaj si Pachet

3.3.1. Structura de baza a clasei Mesaj: campuri, constructori, metode

 

Clasa Mesaj01 incapsuleaza un obiect de tip String care reprezinta un mesaj de la utilizatorul curent (regrupand textul mesajului cu metodele prin care este controlat accesul la acesta):

 

 public class Mesaj01 {
    private String text;                                 
    public Mesaj01(String text) {         // constructor cu parametru
        this.text = text; 
    }    
    public String getText() {             // obtinerea valorii campului
        return this.text; 
    }        
    public String toString() { 
        return ("Mesaj: " + this.text); 
    } 
    public void display() { 
        System.out.println(this.toString()); 
    } 
    public boolean equals(Object obj) { 
        return this.text.equals(((Mesaj01)obj).text); 
    } 
 }

 

 

3.3.2. Supraincarcarea numelor in cazul clasei Mesaj

 

In cazul clasei Mesaj01, supraincarcarea numelui constructorului ar insemna crearea unui constructor suplimentar, de exemplu unul care nu primeste nici un parametru:

 

    public Mesaj01() { this(""); }    // corpul este echivalent cu:  { this.text = ""; }

 

3.3.3. Relatii intre clase: cazul claselor Mesaj si Pachet

 

Pentru a exemplifica relatia de utilizare intre clase va fi creata o clasa Pachet02 care incapsuleaza un obiect Mesaj02 (regrupand mesajul si sursa lui cu metodele prin care este controlat accesul la acestea):

 

 public class Pachet02 {
    private Mesaj02 mesaj;                                 
    private String sursa;                                 
    public Pachet02(Mesaj02 mesaj, String sursa) { 
        this.mesaj = mesaj; 
        this.sursa = sursa; 
    }    
    public Mesaj02 getMesaj() { return this.mesaj; }        
    public String getSursa()  { return this.sursa; }        
    public String toString() { 
        return ("Pachetul de la " + this.sursa + " contine: " + this.mesaj); 
    } 
    public boolean equals(Object obj) { 
        return (this.mesaj.equals(((Pachet02)obj).mesaj)) &&
               (this.sursa.equals(((Pachet02)obj).sursa)); 
    } 
 }

 

In cazul clasei Pachet02, supraincarcarea numelui constructorului ar insemna crearea unui constructor suplimentar, de exemplu unul cu semnatura:

 

    public Pachet02(Mesaj02 mesaj) 

 

 

 

 

 

3.4. Studiu de caz: clasele Grupa, Student, DatePersonale si SituatieCurs

3.4.1. Clasele DatePersonale si SituatieCurs si actualizarea clasei Student

 

Sa presupunem ca dorim sa modificam codul clasei Student care abstractiza un student real, introducand detalii suplimentare referitoare la Student ca persoana, pe langa campul nume.

De exemplu, putem inlocui campul nume de tip String cu un camp date de un tip nou, DatePersonale, care va fi o noua clasa ce va contine pe langa un camp nume de tip String si campurile initiale si prenume de tip String si anNastere de tip int. Codul noii clasei va fi:

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

public class DatePersonale {

   // Campuri ascunse

   private String nume;

   private String initiale;

   private String prenume;

   private int anNastere;

 

   // Constructori

   public DatePersonale(String n, String i, String p, int an) {

      nume = new String(n);      // copiere „hard” a obiectelor primite ca parametri,

      initiale = new String(i);  // adica se copiaza obiectul camp cu camp,

      prenume = new String(p);   // nu doar referintele ca pana acum

      anNastere = an;

   }

   // Interfata publica si implementarea ascunsa

   public String getNume() {      return (nume);   }

   public String getPrenume() {   return (prenume);   }

   public int getAnNastere() {    return (anNastere);   }

   public String toString() {    // forma „String” a campurilor obiectului

      return (nume + " " + initiale + " " + prenume + " (" + anNastere + ")");

   }

}

 

De asemenea, presupunem ca dorim sa modificam codul clasei Student regrupand elementele pereche ale campurilor cursuri si rezultate (care sunt tablouri) in obiecte ale unei clase noi, SituatieCurs. Vom inlocui in clasa Student tablourile cursuri cu elemente de tip String si rezultate cu elemente de tip int, cu un singur tablou, cursuri, cu elemente de tip SituatieCurs.

Cele doua clase noi pot fi reprezentate in UML ( - = acces private, + = acces public) astfel:

 

 

Codul Java al noii clasei va fi:                

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

public class SituatieCurs {

   // Campuri ascunse

   private int nota = 0;                            // initializare implicita

   private String denumire;

 

   // Constructor

   public SituatieCurs(String d) { denumire = new String(d); }     // copiere „hard”

                                                   // se initializeaza doar denumire

   // Interfata publica si implementarea ascunsa

   public void notare(int n) {     nota = n; }     // se adauga nota

   public int nota() {             return(nota); } // se returneaza nota

   public String toString() {                      // forma „String” a campurilor

      if (nota==0) return ("Disciplina " + denumire + " nu a fost notata");

      else return("Rezultat la disciplina " + denumire + ": " + nota);

   }

}

 

 

 

 

In cele doua clase, se observa ca se foloseste copierea “hard” a obiectelor primite ca parametri, adica crearea unor copii ale obiectelor argument, camp cu camp, nu doar copierea referintelor.

In clasa SituatieCurs nu am utilizat metode de tip getCamp() si setCamp(). Metoda nota()ar fi putut fi denumita getNota() iar metoda notare() ar fi putut fi denumita setNota().

Printre metodele declarate regasim si toString(), cu scopul de a returna sub forma de String informatiile pe care le incapsuleaza obiectul caruia i se aplica. Nota initiala 0 inseamna notei (notarea poate incepe doar cu 1). De aceea toString() returneaza diferit pentru valori nule/nenule.

 

Vom rescrie acum codul Java al clasei Student pentru a incorpora schimbarile anuntate.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

/**
   * Incapsuleaza informatiile despre un Student. Permite testarea locala.
   * @version 1.3
   */
  public class Student {

   // Campuri ascunse

   private DatePersonale date;

   private SituatieCurs[] cursuri;

   private int numarCursuri = 0;             // initializare implicita

  

   // Constructori

   public Student(String nume, String initiale, String prenume, int anNastere) {

      date = new DatePersonale(nume, initiale, prenume, anNastere); // copiere „hard”

      cursuri = new SituatieCurs[10];        // se initializeaza doar date si cursuri

   }

 

   // Interfata publica si implementarea ascunsa (include punct intrare program)

   public void addCurs(String nume) {        // se adauga un nou curs

      cursuri[numarCursuri++] = new SituatieCurs(nume);

   }

   public void notare(int numarCurs, int nota) {

      cursuri[numarCurs].notare(nota);       // se adauga nota cursului specificat

   }

   public String toString() {                // forma „String” a campurilor

      String s = "Studentul " + date + " are urmatoarele rezultate:\n";

      for (int i=0; i<numarCursuri; i++)   s = s + cursuri[i].toString() + "\n";

      return (s);

   }

   public static void main(String[] args) {

      // Crearea unui nou Student, initializarea campurilor noului obiect

      Student st1 = new Student("Xulescu", "Ygrec", "Z.", 1987);

      st1.addCurs("CID");

      st1.addCurs("MN");

      st1.notare(0, 8);

      // Utilizarea informatiilor privind Studentul

      System.out.println(st1.toString());    // afisarea formei „String” a campurilor

   }

 

Regasim copierea “hard” a obiectelor si metoda toString(). Tabloul cursurilor este initial gol, rand pe rand fiind adaugate cursuri noi (si e incrementat numarul lor).

 

In UML avem asocierile (clasa Student utilizeaza DatePersonale si SituatieCurs):

 

           

 

Clasa are o metoda main() cu rolul de a testa lucrul cu obiectele Student. Executia ei conduce la:

     Studentul Xulescu A. Ygrec (1987) are urmatoarele rezultate:

     Rezultat la disciplina CID: 8

     Disciplina MN nu a fost notata

 

 

 

3.4.2. Clasa Grupa

 

Sa presupunem ca dorim sa scriem codul unei clase noi numita Grupa care sa abstractizeze o grupa de studenti in cadrul programului care gestioneaza informatii intr-o universitate, facultate, etc.

 

Clasa va avea campuri separate pentru serie, de care tine, si numar, care o identifica (cele doua ar putea forma o alta clasa, de exemplu InfoGrupa) si un tablou pentru referinte spre studenti.

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

public class Grupa {

   // Campuri ascunse

   private int numar;

   private String serie;

   private Student[] studenti;

  

   // Constructori

   public Grupa(int nr, String sr) {         // se initializeaza doar serie si numar

      numar = nr; serie = new String(sr);    // copiere „hard” pentru serie

   }

 

   // Interfata publica si implementarea ascunsa (include punct intrare program)

   public void addStudenti(Student[] st) {   // se adauga tabloul de studenti

      studenti = new Student[st.length];

      System.arraycopy (st, 0, studenti, 0, st.length);  // copiere „hard” a tabloului

   }

   public String toString() {

      String g = "\nRezultatele grupei " + numar + serie + ":\n";

      for (int i=0; i<studenti.length; i++)

        g = g + studenti[i].toString() + "\n";

      return (g);

   }

   public static void main(String[] args) {

      // Crearea unui nou Student, initializarea campurilor noului obiect

      Student st1 = new Student("Xulescu", "A.", "Ygrec", 1987);

      st1.addCurs("CID");

      st1.addCurs("MN");

      st1.notare(0, 8);

      // Crearea unui nou Student, initializarea campurilor noului obiect

      Student st2 = new Student("Zulescu", "B.", "Ics", 1988);

      st2.addCurs("CID");

      st2.addCurs("MN");

      st2.notare(1, 9);

      // Crearea unei noi Grupe, initializarea campurilor noului obiect

      Grupa g1 = new Grupa(424, "A");

      Student[] st = {st1, st2};

      g1.addStudenti(st);

      // Utilizarea informatiilor privind Grupa

      System.out.println(g1.toString());     

   }

}

 

Asocierile in UML (clasa Grupa utilizeaza Student):

 

                  

 

Pentru copiere „hard” a tablourilor (element cu element), clasa System ofera metoda arraycopy(), care copiaza un subtablou din tabloul sursa src, de lungime length, incepand de la index srcPos, in tabloul destinatie dest, la index destPos.

Semnatura arraycopy() este:            

 

    static void arraycopy(Object src, int srcPos, Object dest, int destPos, int length)

 

Metoda main() testeaza lucrul cu obiectele Grupa si Student. Executia ei are ca efect:                     

 

     Rezultatele grupei 424A:

 

     Studentul Xulescu A. Ygrec (1987) are urmatoarele rezultate:

     Rezultat la disciplina CID: 8

     Disciplina MN nu a fost notata

 

     Studentul Zulescu B. Ics (1988) are urmatoarele rezultate:

     Disciplina CID nu a fost notata

     Rezultat la disciplina MN: 9

 

 

 

In laborator:

1. Se deschide BlueJ.

2. Se creeaza un proiect Java numit ModelareStudenti.

3. Se creeaza in acest proiect clasele cu numele DatePersonale, SituatieCurs, Student si Grupa.

4. Se copiaza in interiorul lor codurile din sectiunea 3.4.

5. Se testeaza executia metodelor main() din clasele Student si Grupa.

 

3.4.3. Exercitii in laborator

 

- modificare metoda notare() (in clasa SituatieCurs)

     pentru a nu permite notarea in afara gamei 1-10

     avertizand utilizatorul in cazul unei note in afara gamei

 

- adaugare in clasa Student a unei metode nota()

     care returneaza nota pentru indexul primit cursului ca parametru

 

- adaugare in clasa Student a unei metode medieCurenta()

     care returneaza media notelor curente

 

- ...

 

- bonusuri pentru propuneri interesante de exercitii in laborator!