Interfetele Java reprezinta colectii de metode (fara corp) si constante. O interfata este declarata prin cuvântul cheie interface. O interfata Java poate mosteni alte interfete Java utilizând cuvântul cheie extends. Spre deosebire de clase, interfetele suporta mostenire multipla. La fel ca si o clasa abstracta, o interfata Java nu poate fi instantiata. O interfata java poate fi implementata insa de o clasa utilizand cuvântul cheie implements. O cla sa poate implementa mai multe interfete. O clasa care implementeaza o interfata trebuie sa defineasca toate metodele acelei interfete.

Colectii de obiecte

Java dispune de mai multe metode de pastrare a obiectelor. O prima metoda o reprezinta tablourile, care sunt tipuri predefinite (built-in) ale limbajului, care au fost studiate în lucrarea precedenta. Pe lânga tablouri, exista librarii (pachete) care contin mai multe categorii de clase de colectii (clase container) care ofera o mare varietate de modele de date si structuri de reprezentare a acestora. Într-o colectie, numarul de elemente poate varia prin introducerea unor elemente noi sau sau prin extragerea unor elemente. Modelele de date cele mai folosite (si implementate în arhitectura colectiilor Java – Java Collections Framework) sunt: lista (list - o secventa de elemente, memorate într-o anumita ordine), multimea (set – o grupare de elemente în care nu exista doua sau mai multe elemente de acelasi fel) si vectorul asociativ (map – o grupare de elemente, în care fiecare element contine doua parti asociate, cheia si valoarea). La rândul lui, fiecare model de date (forma colectiei) poate fi realizat prin diferite structuri de date. De exemplu, o lista poate fi realizata printr-un tablou sau printr-o lista înlantuita; o multime poate fi realizata printr-o lista înlantuita, printr-un arbore sau printr-o tabela de dispersie etc.

Arhitectura colectiilor Java consta din mai multe interfete, clase abstracte si clase instantiabile în pachetul java.util, prin care se diferentiaza doua categorii de containere, în functie de numarul de valori pe care îl contine fiecare element al containerului:

  • Tipul Collection defineste câte o valoare în fie care element (denumirea este putin confuza, dat fiind ca întraga colectie de clase se numeste colectii). Aceast tip cuprinde mai multe subtipuri (prin interfete derivate), în functie de restrictiile impuse. 

  • Tipul List (List este o interfata care extinde interfata Collection) defineste modelul de date lista, adica un container care contine o secventa de elemente aflate într-o anumita ordine;  tipul Set (Set este o interfata care extinde Collection) defineste modelul de date multime, adica un container în care nu exista elemente duplicat

  • Tipul Map defineste modelul de date vector asociativ, adica un container în care fiecare element contine doua parti asociate (cheie, valoare).

Forma unui container (modelul de date) este impusa prin interfata (List, Set, Map) pe care o implementeaza clasa containerului, iar structura de date a containerului este definita în clasa, obtinându-se astfel mai multe clase de colectii care difera atât prin modul de organizare cât si prin structura de date folosita.

Denumirile claselor de colectii sunt formate din doua parti, prima parte reprezinta structura de date folosita, iar cea de a doua parte reprezinta forma colectiei (interfata implementata).

De exemplu, clasa ArrayList (care este o lista reprezentata printr-un tablou de elemente) si clasa LinkedList (care este o lista reprezentata printr-o lista înlantuita de elemente) implementeaza interfata List.

Interfata Collection este extinsa de interfetele List si Set; clasele ArrayList si LinkedList implementeaza interfata List; clasele TreeSet si HashSet implementeaza interfata Set; clasele TreeMap si HashMap implementeaza interfata Map. Interfata Collection contine mai multe declaratii de metode care sunt definite în toate clasele care implementeaza interfetele List si Set (derivate din interfata Collection). Dintre acestea, metodele add(Object o) si remove(Object o) permit adaugarea unui nou element, respectiv eliminarea unui element existent în colectie. Metoda int size() returneaza numarul de elemente al colectiei.

O caracteristica importanta a claselor de colectii îl reprezinta faptul acestea contin referinte la Object (orice obie ct poate fi referit printr-o astfel de referinta). Acest lucru înseamna ca în aceeasi colectie se pot introduce obiecte de orice clasa (dat fiind ca toate clasele sunt derivate din clasa Object) si nu se pot introduce date de tipuri primitive (care nu sunt derivate din nici o clasa). Restrictia colectiilor de a nu admite date primitive se rezolva utilizând clasele echivalente tipurilor primitive (Character, Integer etc). Acesta caracteristica a containerelor Java (de a stoca referinte de tip Object) are totusi un dezavantaj, deoarece, dupa introducerea unui obiect într-o colectie (memorarea referintei acestuia într-un element al colectiei) se pierde tipul exact al referintei (care se memoreaza ca referinta la Object). Deoarece informatia de tip a referinte i se pierde, trebuie sa fie facuta o conversie cast atunci când se utilizeaza elementele din colectie si, daca conversia nu se face pentru tipul exact al obiectului, atunci poate sa apara exceptii în timpul executiei.

Clasa ArrayList

Clasa ArrayList implementeaza interfata List si, indirect, interfata Collection, definind toate metodele prevazute în aceste interfete. Metodele de inserare permit adaugarea unui element la sfârsitul listei (add (Object o)) sau într-o pozitie dorita (add(int index, Object o)). Metodele de stergere permit eliminarea unui element de la o pozitie data (remove(int index)), sau eliminarea tuturor elementelor dintr-un interval dat de pozitii (remove(int fromIndex, int toIndex)). Elementele se pot citi (fara a fi eliminate din lista) cu metodele get() si get(int index). Exemplul urmator (Colectie.java) evidentiaza modul de utilizare a unei colectii de tipul ArrayList, precum si problemele care pot sa apara în cazul conversiilor incorecte.

Colectie.java - Exemplu de container ArrayList

import java.util.*;
class Mar
{
    private int nr;
    Mar(int i) { nr = i; }
    void print()
    { 
        System.out.println("Mar #" + nr); 
    }
}
class Para {
    private int nr;
    Para(int i) { nr = i; }
    void print()
    {
        System.out.println("Para #" + nr); 
    }
}
public class Colectie
{
    public static void main(String[] args)
    {
        ArrayList mere = new ArrayList();
        for(int i = 0; i < 7; i++)
            mere.add(new Mar(i));
        // Se adauga pere la mere
        mere.add(new Para(7));
        for(int i = 0; i < mere.size(); i++)
            ((Mar)mere.get(i)).print();
// Eroarea este detectata in timpul executiei
    }
}

Colectia este creata ca o instanta a clasei ArrayList. În aceasta colectie se intoduc mai multe referinte de tip Mar si mai multe referinte de tip Par folosind metoda add. Dupa citirea elementelor din colectie (cu functia get()), trebuie sa fie facuta conversia referintei din referinta la tipul Object (asa cum a fost memorata) la tipul obiectului. Daca aceasta conversie se face incorect (ca în exemplul de mai sus, când referinta la un obiect de clasa Para este convertita în referinta de clasa Mar), apare o exceptie in cursul executiei.

Iteratori

Un iterator este un obiect dintr-o clasa care implementeaza interfata Iterator si care permite parcurgerea elementelor unei colectii. Interfata Iterator prevede trei metode care se pot folosi pentru parcurgerea colectiilor: hasNext(), next() si remove(). -

  • boolean hasNext() returneaza valoarea true daca mai exista elemente de parcurs. Object next() returneaza referinta la urmatorul element din colectie. o void remove() sterge din colectie ultimul element returnat de iterator.

Un iterator se creeaza cu ajutorul metodei Iterator iterator() apelata pentru un obiect container. Acesta metoda este declarata în interfata Collection si implementata în fiecare clasa de colectii. Parcurgerea unei colectii folosind un iterator în locul functiei get()de citire a elementelor este, în general mai usoara, deoarece nu mai este necesar sa se compare numarul de elemente extrase cu numarul de elemente disponibile în container (aceasta operatie o face metoda hasNext() a iteratorului). În plus, interfata Iterator este aceeasi indiferent de tipul 4 containerului, si deci metodele folosite pentru parcurgerea elementelor colectiei ramân aceleasi chiar daca se schimba tipul containerului. În exemplul urmator (Iteratori.java) se foloseste un iterator pentru parcurgerea si afisarea elementelor unei colectii ArrayList.

Iteratori.java


import java.util.*;
class Numar{
    int n;
    public Numar(int k){ n = k; }
    public String toString()
    { 
        return "n = " + n; 
    }
}
public class Iteratori
{
    public static void main(String[] args)
    {
        ArrayList numere = new ArrayList();
        for(int i = 0; i < 7; i++)
            numere.add(new Numar(i));
        // Se listeaza elementele folosind un iterator
        Iterator it = numere.iterator();
        while (it.hasNext())
        {
            Numar nr = (Numar)it.next();
            System.out.println(nr);
        }
    }
}

Clasa LinkedList

Clasa LinkedList modeleaza o lista (secventa de elemente memorate într-o anumita ordine) printr-o structura de date de tip lista înlantuita. Pe lânga metodele de inserare, stergere si citire definite de interfata List (descrise la clasa ArrayList), clasa LinkedList mai defineste câteva metode utile de acces la elementele colectiei:

  • adaugarea unui element la începutul listei (void addFirst(Object o) sau la sfârsitul listei (void addLast(Object o)),

  • eliminarea primului element (Object removeFirst()) sau a ultimului element din lista (Object removeLast()),

  • citirea primului element (Object getFirst()) si a ultimului element (Object getLast()).

Clasa LinkedList poate fi folosita pentru crearea altor modele de date mai speciale, cum sunt stivele (stacks) si cozile (queues).

Implementarea unei stive folosind clasa LinkedList.

O stiva (stack) este un container de tipul ultimul introdus, primul extras – LIFO: Last In, First Out) în care ultimul element (introdus cu operatia push) este extras cu operatia pop. O stiva se poate realiza foarte simplu folosind clasa LinkedList, asa cum se poate vedea în exemplul de mai jos (Stiva.java).

Stiva.java

import java.util.*;
public class Stiva
{
    private LinkedList list = new LinkedList();
    public void push(Object v)
    {
        list.addFirst(v); 
    }
    public Object top()
    {
        return list.getFirst(); 
    }
    public Object pop()
    {
        return list.removeFirst();
    }

    public static void main(String[] args)
    {
        Stiva st = new Stiva();
        for(int i = 0; i < 5; i++){st.push(new Integer(i));}
        System.out.println(st.pop());
        System.out.println(st.pop());
        System.out.println(st.pop());
        System.out.println(st.pop());
        System.out.println(st.pop());
    }
}

Implementarea unei cozi folosind clasa LinkedList

O coada (queue) este un container de tipul primul introdus, primul extras – FIFO: First In, First Out) în care primul element (introdus cu operatia put) este extras cu operatia get. La fel ca si o stiva, o coada se poate realiza foarte simplu folosind clasa LinkedList, asa cum se poate vedea în exemplul de mai jos (Coada.java).

Coada.java

import java.util.*;
public class Coada
{
    private LinkedList list = new LinkedList();
    public void put(Object v)
    { 
        list.addFirst(v);
    }
    public Object get()
    { 
        return list.removeLast(); 
    }

    public static void main(String[] args)
    {
        Coada cd = new Coada();
        for(int i = 0; i < 5; i++)
        { 
            cd.put(new Integer(i));
        }
        System.out.println(cd.get());
        System.out.println(cd.get());
        System.out.println(cd.get());
        System.out.println(cd.get());
        System.out.println(cd.get());
    }
}

Probleme propuse spre rezolvare

1. Sa se creeze o clasa care sa contina ca data membra privata un vector (tablou unidimensional) de numere întregi de dimensiune variabila, care se stabileste pentru fiecare obiect la constructia acestuia. În aceasta clasa redefiniti functia toString(), care sa afiseze numerele continute. În functia main() a clasei principale construiti un obiect din clasa creata care sa contina numerele 2, 9, 4, 5, 7, 8 si afisati continutul acestuia folosind functia toString() a clasei respective.

2. Sa se creeze o clasa Complex, pentru definirea unui numar complex, cu partea reala si imaginara ca numere de tip double. Definiti constructorii, functia de afisare (toString()), functia de testare a egalitatii a doua obiecte (equals()), o functie care realizeaza adunarea a doua numere complexe. În functia main()creati doua obiecte din clasa Complex, cu valorile 2, 4, respectiv 5, 6 a partilor reale si imaginare. Verificati daca cele doua obiecte sunt egale si afisati rezultatul la consola. Calculati si afisati suma celor doua numere complexe.

3. Sa se creeze un program care sa contina urmatoarele clase si interfete: - Clasa Persoana care contine variabila membra private String nume, functiile necesare pentru citirea si scrierea acestei variabile, constructori, etc. Interfata Adresa care declara functiile abstracte String getAdresa() si void setAdresa(String s); Clasa Student care extinde clasa Persoana si implementeaza interfata Adresa. Clasa Profesor care extinde clasa Persoana si implementeaza interfata Adresa. În functia main() a programului sa se creeze un obiect Student, cu numele Marinescu si adresa Cluj si un obiect Profesor cu numele Popescu si adresa Tg-Jiu. Afisati la consola datele celor doua obiecte (numele si adresa).

4. Introduceti si executati programele de colectii prezentate în laborator (Colectie.java, Iteratori.java, Stiva.java, Coada.java). Modificati programul Colectie.java, astfel încât elementele din lista sa fie parcurse folosind un iterator.

Back to Top