Capitolul 3 POO
-
Upload
andrei-sofrone -
Category
Documents
-
view
173 -
download
4
Transcript of Capitolul 3 POO
1
CAPITOLUL 3. POLIMORFISM ŞI GENERICITATE 3
3.1 MOŞTENIRE ÎN STRUCTURA OBIECTELOR 4 3.1.1 MOŞTENIREA ATRIBUTELOR 4 3.1.2 SUPRA-SCRIERE ŞI SUPRA-ÎNCĂRCARE STRUCTURALĂ 6 3.2 MOŞTENIRE COMPORTAMENTALĂ 10 3.2.1 MOŞTENIREA OPERAŢIILOR ŞI SUBTIPIZAREA 10 3.2.2 SUPRA-SCRIERE ŞI SUPRA-ÎNCĂRCARE COMPORTAMENTALĂ 14 3.2.3 CLASE ŞI METODE ABSTRACTE 16 3.3 POLIMORFISM, SUBTIPIZARE ŞI INTERFEŢE 19 3.3.1 SUBTIPIZARE ŞI INTERFEŢE 20 3.3.2 MOŞTENIRE MULTIPLĂ 21 3.3.3 FORME DE POLIMORFISM 22 3.4 EGALITATEA ŞI COMPARABILITATEA OBIECTELOR 26 3.4.1 EGALITATEA ŞI IDENTITATEA OBIECTELOR. OPERAŢIA EQUALS DIN SUPERCLASA OBJECT
27 3.4.2 OBIECTE COMPARABILE ŞI OBIECTE COMPARATORI. INTERFEŢELE COMPARABLE ŞI
COMPARATOR 29 3.5 OBIECTE ÎN COLECŢII. COLECŢII GENERICE 31 3.5.1 API PENTRU COLECŢIILE DE OBIECTE ÎN MEDIUL JAVA. COLECŢII INDEXATE, SORTATE ŞI
TIPIZATE 32 3.5.2 COLECŢII TIPIZATE ŞI STRUCTURI DE CONTROL PENTRU PARCURGEREA COLECŢIILOR 38 3.5.3 ELEMENTE SPECIFICE CLASELELOR DE IMPLEMENTARE A INTERFEŢELOR STANDARD
PRIVIND COLECŢIILE 41
2
CAPITOLUL 3
Polimorfism şi genericitate
În capitolul anterior am arătat, în principal, cum pot fi construite şi
create obiectele. În acest context, au fost discutate conceptul fundamental de clasă şi două principii esenţiale: principiul încapsulării şi cel al reutilizării. Aceste elemente sunt definitorii în programarea orientată obiect, însă avantajul esenţial al acesteia este relevat de posibilitatea construirii componentelor software într-o manieră generică, astfel încât evoluţia acestora în funcţionalitate să se poată face cât mai natural şi eficient, fără improvizaţii şi costuri deosebite, asigurându-le o durată de utilizare „eficientă” cât mai îndelungată, iar înlocuirea lor să se poată face într-un mod cât mai neutru pentru angrenajul aplicaţiei din care fac parte. Acest lucru este posibil datorită a două aspecte specifice, în mod natural, paradigmei obiectelor: polimorfismul, ceea ce înseamnă că acelaşi element ar putea juca mai multe roluri sau ar putea fi interpretat în mai multe feluri, şi genericitate, ceea ce se traduce în posibilităţile de parametrizare a cât mai multor aspecte legate de componentele claselor, atribute și operații, dar şi de parametrizarea claselor în sine, ceea ce se numește, de fapt, parametrizarea tipurilor.
Capitolul 3
4
3.1 Moştenire în structura obiectelor Moştenirea, în special prin efectul ei concretizat prin sub-tipizare, este
factorul esenţial în manifestarea polimorfismului. Aşa cum deja am discutat, în principiu, în capitolul anterior,
moştenirea este înţeleasă, în general, ca mecanismul prin care o anumită clasă (subclasă) poate fi implicit definită având la bază atributele şi operaţiile altei clase (superclasa).
Modul în care structura, adică ansamblul acelor componente interne implementate ca variabile de instanţă, este moştenită, comportă totuşi câteva aspecte particulare.
3.1.1 Moştenirea atributelor După cum am afirmat deja, prin specializare în subclase obiectele
subclasate moştenesc atributele (variabilele de instanţă) care formează scheletul structural al claselor părinte.
Accesul din contextul subclaselor la variabilele de instanţă care provin
din superclase este dependent de specificatorii de vizibilitate: prin specificatorul public se desemnează faptul că atributul
(variabila de instanţă) va fi accesibil la nivelul subclaselor, la fel ca şi la nivelul oricărei alte clase care accesează clasa iniţială prin referenţiere obişnuită (compunere şi parametrizare);
vizibilitatea „friendly” sau internă la nivel de pachet desemnată, de fapt, prin lipsa unui specificator explicit are un efect asemănător cu folosirea specificatorului public restrâns însă numai la clasele din același spaţiu (sau pachet);
prin specificatorul private se desemnează faptul că, deşi făcând parte structural din obiectele subclasei, componentele interne implementate ca variabile de instanță nu mai sunt accesbile în mod direct, iniţializarea lor făcându-se cel mult prin apelarea unui constructor din clasa părinte, folosind cuvântul-cheie super;
prin specificatorul protected se desemnează faptul că membrul intern, materializat printr-o variabilă de instanță, este accesibil obiectelor subclaselor, domeniul de vizibilitate sau access fiind limitat însă numai la subclase.
Un lucru esenţial, care decurge din folosirea cuvântului-cheie super pe
care l-am subliniat mai sus, se referă la faptul că mecanismul de iniţializare a instanţelor din subclase trebuie să permită şi iniţializarea variabilelor de instanţă moştenite, eventual chiar la nivelul superclaselor.
Polimorfism și genericitate
5
// Listing 3.1: Iniţializare membri moşteniţi,
// prin invocare constructor clasă părinte – super
// Superclasa InregistrareContabilă
public class InregistrareContabila {
private Integer id;
private Integer nrOrdine;
protected String tip; // discriminator debit, credit
private Double suma;
private Cont cont;
private OperatiuneContabila operatiune;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
... ... ...
// constructori (nu se moştenesc)
public InregistrareContabila(Integer id, Cont cont,
Double suma){
this.id = id;
this.tip = this.getClass().getSimpleName();
this.suma = suma;
this.cont = cont;
}
public InregistrareContabila() {
}
}
// Subclase specializate InregistrareDebit şi InregistrareCredit -
// ----------------
public class InregistrareDebit extends InregistrareContabila{
private Integer nrOrdineDebit;
public int getNrOrdineDebit() {
return nrOrdineDebit;
}
... ... ...
// invocare constructor părinte – super
// acces (prin protected) la membru moştenit
public InregistrareDebit(Integer id, Cont cont,
Double suma) {
super(id, cont, suma);
Capitolul 3
6
this.tip = "Debit”;
}
}
//------------------------------------
public class InregistrareCredit extends InregistrareContabila{
private Integer nrOrdineCredit;
public Integer getNrOrdineCredit() {
return nrOrdineCredit;
}
... ... ...
public InregistrareCredit(Integer id, Cont cont,
Double suma) {
super(id, cont, suma);
this.tip = "Credit”;
}
}
3.1.2 Suprascriere şi supraîncărcare structurală Scopul subclasării ar putea fi motivat de efortul de adaptare a unei
clase existente, cu o funcţionalitate dată, la un nou context mai specific. Această nevoie de adaptare este legată și de faptul că unele aspecte moştenite nu corespund în totalitate cu noul context. Ceea ce duce la necesitatea sau, cel puţin, tentaţia redefinirii anumitor aspecte preluate de la clasele părinte.
În cazul membrilor-variabile de instanţă însă, subclasele nu pot
redefini atributele definite la nivelul superclaselor (chiar dacă le pot accesa). Este tutuși posibil ca la nivelul subclaselor numele atributelor moștenite din superclase să poată fi supraîncărcate cu cele ale unor atribute proprii, adică: pot fi definite în subclase atribute ale căror nume se regăsesc şi la
nivelul superclaselor; diferenţierea între atributele ale căror nume sunt supraîncărcate se
poate face prin cuvintele-cheie super şi this.
// Listing 3.2: Supraîncărcare structurală:
// importanţa cuvântului cheie this
// Superclasa InregistrareContabilă
public class InregistrareContabila {
private Integer id;
protected Integer nrOrdine;
Polimorfism și genericitate
7
protected String tip; // discriminator debit, credit
private Double suma;
private Cont cont;
private OperatiuneContabila operatiune;
... ... ...
// proprietatea ce încapsulează nrOrdine
public Integer getNrOrdine() {
return nrOrdine;
}
public void setNrOrdine(Integer nrOrdine) {
this.nrOrdine = nrOrdine;
}
... ... ...
}
// Subclase specializate InregistrareD şi InregistrareC --------
public class InregistrareC extends InregistrareContabila{
// membru-variabilă supraîncărcată
private Integer nrOrdine;
// proprietate care încapsulează
// membru-variabilă supraîncărcată
public Integer getNrOrdineCredit() {
return this.nrOrdine;
}
public void setNrOrdineCredit(Integer nrOrdineCredit) {
this.nrOrdine = nrOrdineCredit;
}
public InregistrareC() {
}
public InregistrareC(Integer id, Cont cont, Double suma) {
super(id, cont, suma);
}
}
//------------------------------------
public class InregistrareD extends InregistrareContabila{
// membru-variabilă supraîncărcată
private Integer nrOrdine;
// proprietate care încapsulează
// membru-variabilă supraîncărcată
public Integer getNrOrdineDebit() {
return this.nrOrdine;
}
public void setNrOrdineDebit(Integer nrOrdineDebit) {
Capitolul 3
8
this.nrOrdine = nrOrdineCredit;
}
public InregistrareD() {
}
public InregistrareD(Integer id, Cont cont, Double suma) {
super(id, cont, suma);
}
}
//------------------------------------
public class TestInregistrareContabilaSuprascriereStructurala {
public static void main(String[] args){
InregistrareContabila inregistrareDebit =
new InregistrareContabila();
inregistrareDebit.setTip("Debit");
inregistrareDebit.setNrOrdine(1);
InregistrareD iDebit = new InregistrareD();
InregistrareC iCredit = new InregistrareC();
iDebit.setNrOrdineDebit(1);
iCredit.setNrOrdineCredit(1);
System.out.println("iDebit.nrOrdineDebit: "
+ iDebit.getNrOrdineDebit());
System.out.println("iDebit.nrOrdine: "
+ iDebit.getNrOrdine());
}
}
Auroreferenţierea
Cuvântul-cheie this, a cărui semnificație ar putea fi înțeleasă ca „acest
obiect” sau „obiectul curent”, poate fi utilizat numai în cadrul unei metode non-statice şi produce referinţa către obiectul curent ale cărui metode ar putea urma a fi astfel apelate. Apelul unei anumite operaţii, în cadrul unei alte metode aparţinând aceluiaşi obiect, nu implică folosirea obligatorie a cuvântului-cheie this. Acest cuvânt este folosit, în special, în cazul supraîncărcării structurale sau pentru instrucţiunea return care trebuie să returneze referinţa obiectului curent.
De asemenea, this poate fi apelat din interiorul unui constructor, caz
în care este folosit sub forma unui apel cu argumente – this (arg) – şi va invoca în mod explicit constructorul corespunzător listei de argumente.
Polimorfism și genericitate
9
Există posibilitatea ca în interiorul unui constructor să fie apelat un alt constructor. În acestă situație cuvântul-cheie this este folosit pentru a apela ceilalţi constructori.
// Listing 3.3: Invocarea constructorilor prin super() şi this()
// Superclasa InregistrareContabilă
public class InregistrareContabila {
private Integer id;
private Integer nrOrdine;
protected String tip; // discriminator debit, credit
protected Double suma;
private Cont cont;
private OperatiuneContabila operatiune;
... ... ...
// constructori
public InregistrareContabila() {
}
public InregistrareContabila(Integer id, Cont cont){
this.id = id;
this.tip = this.getClass().getSimpleName();
this.cont = cont;
}
public InregistrareContabila(Integer id, Cont cont,
Double suma){
this(id, cont);
this.suma = suma;
}
}
// Subclase specializate InregistrareDebit şi InregistrareCredit –
public class InregistrareDebit extends InregistrareContabila{
private Integer nrOrdineDebit;
public int getNrOrdineDebit() {
return nrOrdineDebit;
}
... ... ...
// constructori
public InregistrareDebit() {
}
public InregistrareDebit(Integer id, Cont cont) {
super(id, cont);
}
public InregistrareDebit(Integer id, Cont cont, Double suma) {
Capitolul 3
10
this(id, cont);
this.suma = suma;
}
}
//------------------------------------
public class InregistrareCredit extends InregistrareContabila{
private Integer nrOrdineCredit;
public Integer getNrOrdineCredit() {
return nrOrdineCredit;
}
... ... ...
// constructori
public InregistrareCredit() {
}
public InregistrareCredit(Integer id, Cont cont) {
super(id, cont);
}
public InregistrareCredit(Integer id, Cont cont, Double suma) {
this(id, cont);
this.suma = suma;
}
}
3.2 Moştenire comportamentală Acest tip de moştenire are în vedere preluarea operaţiilor şi metodelor
de implementare în structura comportamentală a subclaselor. Esenţial este faptul că, în procesul de moştenire, operaţiile (specificaţiile) sunt considerate în mod obligatoriu preluate la nivelul subclaselor, metodele concrete de implementare ale acestora putând fi însă redefinite. Cu alte cuvinte, natura tipului este în mod sigur păstrată prin moştenire, acest lucru însemnând că se are în vedere subtipizarea, nu şi implementarea lui.
3.2.1 Moştenirea operaţiilor şi subtipizarea Prin specializare, obiectele subclaselor moştenesc operaţiile care
descriu comportamentul claselor părinte. Cu alte cuvinte, subclasele trebuie să respectate întocmai semnătura acestora: nume + tip + argument + specificator vizibilitate + tipuri excepţii.
Polimorfism și genericitate
11
Invocarea internă sau accesul din contextul subclaselor, a operaţiilor care provin din superclase este însă dependentă de specificatorii de vizibilitate: public; (package); protected;
cu aceeaşi semantică ca şi în cazul membrilor-variabile de instanţă (vezi paragraful 3.1.1).
Moştenirea are ca efect implicit subtipizarea, adică obiectele din subclase au aceleaşi caracteristici şi se comportă similar cu cele din superclase. Pentru ca relaţia de generalizare subclasă superclasă să fie semantic corectă, aserţiunea <o instanţă subclasă> [este gen sau fel de/is a kind of] <o instanţă superclasă> trebuie să fie, de asemenea, adevarată.
// Listing 3.4: Invocarea operaţiilor moştenite şi subtipizare
// Superclasa OperatiuneContabila
public class OperatiuneContabila {
private Integer idOperatiune;
private Date dataContabilizare;
protected Map<Integer, InregistrareContabila> inregistrari =
new TreeMap<Integer, InregistrareContabila>();
// operaţii ale proprietăţilor ce vor fi moştenite
public Integer getIdOperatiune() {
return idOperatiune;
}
public void setIdOperatiune(Integer idOperatiune) {
this.idOperatiune = idOperatiune;
}
public Date getDataContabilizare() {
return dataContabilizare;
}
public void setDataContabilizare(Date dataContabilizare) {
this.dataContabilizare = dataContabilizare;
}
public List<InregistrareContabila> getInregistrari() {
List<InregistrareContabila> result =
new ArrayList<InregistrareContabila>();
result.addAll(inregistrari.values());
return result;
}
Capitolul 3
12
// operaţii şi metode ce urmează a fi moştenite
public void addInregistrareContabila(
InregistrareContabila inregistrare){
TreeSet<Integer> cheiInregistrari =
new TreeSet<Integer>();
cheiInregistrari.addAll(inregistrari.keySet());
if (cheiInregistrari.size() > 0)
inregistrare.setNrOrdine(cheiInregistrari.last() + 1);
else
inregistrare.setNrOrdine(1);
inregistrare.setOperatiune(this);
this.inregistrari.put(inregistrare.getNrOrdine(),
inregistrare);
}
public Double getSold(){
return getDebit() - getCredit();
}
public Double getDebit(){
Double debit = 0.0;
for (InregistrareContabila i: this.getInregistrari()){
if (i instanceof InregistrareDebit)
debit += i.getSuma();
}
return debit;
}
public Double getCredit(){
Double credit = 0.0;
for (InregistrareContabila i: this.getInregistrari()){
if (i instanceof InregistrareCredit)
credit += i.getSuma();
}
return credit;
}
}
//----------------------------------------------------------------
//Subclase care vor moşteni
//----------------------------------------------------------------
public abstract class OperatiuneComerciala
extends OperatiuneContabila{
private String partener;
public String getPartener() {
return partener;
}
Polimorfism și genericitate
13
public void setPartener(String partener) {
this.partener = partener;
}
public OperatiuneComerciala() {}
public OperatiuneComerciala(Integer idOperatiune,
Date dataContabilizare) {
super(idOperatiune, dataContabilizare);
}
public OperatiuneComerciala(Integer idOperatiune,
Date dataContabilizare, String partener) {
super(idOperatiune, dataContabilizare);
this.partener = partener;
}
}
//----------------------------------------------------------------
public class Vinzare extends OperatiuneComerciala{
private Integer nrFacturaEmisa;
private Date dataFacturaEmisa;
public Vinzare() {
}
public Vinzare(Integer idOperatiune, Date dataContabilizare,
String partener) {
super(idOperatiune, dataContabilizare, partener);
}
public Vinzare(Integer idOperatiune,
Date dataContabilizare, String partener,
Integer nrFacturaEmisa, Date dataFacturaEmisa) {
super(idOperatiune, dataContabilizare, partener);
this.nrFacturaEmisa = nrFacturaEmisa;
this.dataFacturaEmisa = dataFacturaEmisa;
}
public Date getDataFacturaEmisa() {
return dataFacturaEmisa;
}
public void setDataFacturaEmisa(Date dataFacturaEmisa) {
this.dataFacturaEmisa = dataFacturaEmisa;
}
public Integer getNrFacturaEmisa() {
return nrFacturaEmisa;
}
public void setNrFacturaEmisa(Integer nrFacturaEmisa) {
this.nrFacturaEmisa = nrFacturaEmisa;
}
Capitolul 3
14
}
//----------------------------------------------------------------
// Test subtipizare
//----------------------------------------------------------------
public class TestSubtipizareOperatiuni {
public static void main(String[] args){
OperatiuneContabila op_1 =
new OperatiuneContabila(1,new Date());
Vinzare op_2 = new Vinzare(2, new Date(),"Alfa SRL");
// apel metoda proprie getSold()
System.out.println("" + op_1.getSold());
// apel metoda mostenita getSold()
System.out.println("" + op_2.getSold());
}
}
3.2.2 Suprascriere şi supraîncărcare comportamentală Deşi, în privinţa operaţiilor, nu se fac „compromisuri”, în privinţa
moştenirii metodelor concrete de implementare lucrurile sunt mai flexibile: la nivelul unei subclase, o metodă moştenită poate fi redefinită (sau suprascrisă): implementarea unei operaţii poate fi schimbată în subclase. Acest lucru este posibil pentru că modificarea metodelor de implementare nu afectează subtipizarea.
Un alt fenomen posibil ca urmare a moştenirii (însă nu exclusiv) este
supraîncărcarea: la nivelul unei subclase poate fi definită o operaţiune cu acelaşi nume ca al altei operaţiuni moştenite, dar mod de parametrizare diferit. Ca urmare, instanţele subclasei pot fi invocate prin acelaşi nume de operaţie, dar care, funcţie de numărul şi tipul argumentelor, vor fi mapate pe implementări comportamentale distincte. Regulile de invocare în contextul supraîncărcării operaţiilor le-am făcut deja cunoscute în capitolul anterior.
// Listing 3.5: Supraîncărcare operaţii moştenite:
// extindere exemplu din Listing 3.4
public class OperatiuneContabila {
... ... ...
public List<InregistrareContabila> getInregistrari() {
List<InregistrareContabila> result =
new ArrayList<InregistrareContabila>();
result.addAll(inregistrari.values());
Polimorfism și genericitate
15
return result;
}
public void addInregistrareContabila(
InregistrareContabila inregistrare){
TreeSet<Integer> cheiInregistrari =
new TreeSet<Integer>();
cheiInregistrari.addAll(inregistrari.keySet());
if (cheiInregistrari.size() > 0)
inregistrare.setNrOrdine(cheiInregistrari.last() + 1);
else
inregistrare.setNrOrdine(1);
inregistrare.setOperatiune(this);
this.inregistrari.put(inregistrare.getNrOrdine(),
inregistrare);
}
... ... ...
}
//----------------------------------------------------------------
//Subclase care vor moşteni
//----------------------------------------------------------------
public abstract class OperatiuneComerciala
extends OperatiuneContabila{
... ... ...
// supraincarcate getInregistrari()
// si invocare operatiune supraincarcata
public List<InregistrareContabila> getInregistrari(
String tip) {
List<InregistrareContabila> result =
new ArrayList<InregistrareContabila>();
if(tip.equals("Debit")){
for (InregistrareContabila i : getInregistrari())
if (i instanceof InregistrareDebit)
result.add(i);
}
if(tip.equals("Credit")){
for (InregistrareContabila i : getInregistrari())
if (i instanceof InregistrareCredit)
result.add(i);
}
return result;
}
Capitolul 3
16
... ... ...
}
//----------------------------------------------------------------
public class Vinzare extends OperatiuneComerciala{
... ... ...
// suprascriere
@Override
public void addInregistrareContabila(
InregistrareContabila inregistrare){
if (inregistrare instanceof InregistrareDebit &&
! inregistrare.getCont().getCod().startsWith("4"))
throw new AppException(
"Inregistrare incompatibila cu operatiune vinzare !");
super.addInregistrareContabila(inregistrare);
}
... ... ...
}
3.2.3 Clase şi metode abstracte Există posibilitatea ca la nivelul unor superclase (definite ca abstracte)
să fie doar definite anumite operaţii, subclasele concrete ale acestora având sarcina asocierii unei implementări corespunzătoare.
Există situaţii în care o clasă nu este creată în mod necesar pentru a fi
instanţiată direct, motiv pentru care ar trebui declarată ca abstractă. Prin urmare, scopul ei este de a servi drept fundaţie pentru construirea subclaselor, permiţându-se astfel ca obiecte diferite, dar care se aseamănă între ele în anumite privinţe, să fie manipulate într-o manieră unitară, prin intermediul specificaţiilor superclaselor comune. Invers, o clasă prin care sunt create nemijlocit instanţe este numită concretă.
În această privinţă, în limbajul Java se utilizează, pentru a face
diferenţa între clasele fără posibilităţi directe de instanţiere şi celelalte, cuvântul-cheie abstract :
public abstract class OperatiuneComerciala
extends OperatiuneContabila{ ... ... ...}
Polimorfism și genericitate
17
Operațiile definite, dar neimplementate, se mai numesc operații abstracte. Prin urmare, o operație abstractă este o operație specificată, dar neimplementată. Datorită faptului că o clasă concretă nu poate conţine nici o operație/metodă abstractă, înseamnă că orice clasă concretă trebuie să asigure implementarea operațiilor abstracte specificate în superclasele abstracte.
public abstract List<InregistrareContabila> getInregistrari(
String tip);
Se observă, în specificația operațiilor abstracte, lipsa simbolurilor de
marcare ale blocului de instrucţiuni executabile {...} care ar trebui să formeze corpul metodei, dar pe care (sub)clasele concrete sunt obligate să-l implementeze. // Listing 3.6: Implementare operaţii abstracte moştenite:
// modificare Listing 3.5
public class OperatiuneContabila {
... ... ...
public List<InregistrareContabila> getInregistrari() {
List<InregistrareContabila> result =
new ArrayList<InregistrareContabila>();
result.addAll(inregistrari.values());
return result;
}
public void addInregistrareContabila(
InregistrareContabila inregistrare){
TreeSet<Integer> cheiInregistrari =
new TreeSet<Integer>();
cheiInregistrari.addAll(inregistrari.keySet());
if (cheiInregistrari.size() > 0)
inregistrare.setNrOrdine(cheiInregistrari.last() + 1);
else
inregistrare.setNrOrdine(1);
inregistrare.setOperatiune(this);
this.inregistrari.put(
inregistrare.getNrOrdine(), inregistrare);
}
... ... ...
}
//----------------------------------------------------------------
//Subclase care vor moşteni
//----------------------------------------------------------------
// Subclasa abstractă
Capitolul 3
18
public abstract class OperatiuneComerciala
extends OperatiuneContabila{
... ... ...
// operatie abstractă
public abstract List<InregistrareContabila>
getInregistrari(String tip) ;
... ... ...
}
//----------------------------------------------------------------
// Subclasă concretă
public class Vinzare extends OperatiuneComerciala{
... ... ...
@Override
public void addInregistrareContabila(
InregistrareContabila inregistrare){
if (inregistrare instanceof InregistrareDebit &&
!inregistrare.getCont().getCod().startsWith("411"))
throw new AppException(
"Inregistrare incompatibila cu operatiune vinzare !");
super.addInregistrareContabila(inregistrare);
}
//implementare operaţie abstractă moştenită
public List<InregistrareContabila> getInregistrari(
String tip) {
List<InregistrareContabila> result =
new ArrayList<InregistrareContabila>();
if(tip.equals("Debit")){
for (InregistrareContabila i : getInregistrari())
if (i instanceof InregistrareDebit)
result.add(i);
}
if(tip.equals("Credit")){
for (InregistrareContabila i : getInregistrari())
if (i instanceof InregistrareCredit)
result.add(i);
}
return result;
}
... ... ...
}
//----------------------------------------------------------------
Polimorfism și genericitate
19
// Test invocare implementări operaţii abstracte
//----------------------------------------------------------------
public class TestImplOperatiuniAbstracte {
public static void main(String[] args){
Cont c_1 = new Cont("411.1", "Client Alfa SRL");
Cont c_2 = new Cont("371", "Marfuri");
OperatiuneContabila op_1 = new OperatiuneContabila(
1,new Date());
op_1.addInregistrareContabila(
new InregistrareDebit(1, c_1, 100.0));
op_1.addInregistrareContabila(
new InregistrareCredit(2, c_2, 100.0));
Vinzare op_2 = new Vinzare(2, new Date(),"Alfa SRL");
op_2.addInregistrareContabila(
new InregistrareDebit(3, c_1, 50.0));
op_2.addInregistrareContabila(
new InregistrareCredit(4, c_2, 50.0));
// apel metoda getInregistrari
// din clasa OperatiuneContabila
System.out.println("op_1: "
+ op_1.getInregistrari().size());
// apel metoda getInregistrari
// supraîncărcată în clasa Vinzare
System.out.println("op_2: "
+ op_2.getInregistrari("Debit").size());
}
}
3.3 Polimorfism, subtipizare şi interfeţe Până în acest moment, am discutat despre mecanismele prin care este
posibilă subtipizarea şi polimorfismul, pornind de la clase abstracte sau concrete. În continuare, ne vom concentra asupra fenomenului în sine, mai exact asupra naturii generice a componentelor software, construite folosind elemente polimorfice.
Mai întâi vom aduce în discuție conceptul de interfaţă ca mecanism
de separare a definirii tipului faţă de implementare. Apoi vom avea în vedere subtipizarea în contextul moştenirii aplicată interfeţelor. În fine,
Capitolul 3
20
vom trece în revistă formele de polimorfism cele mai relevante: al operaţiilor, al mesajelor şi al variabilelor.
3.3.1 Subtipizare şi interfeţe Interfeţele sunt asemănătoare claselor abstracte în privinţa faptului că
nu pot fi folosite pentru instanţiere directă. Definiţia lor se limitează doar la membri-metode, nu şi variabile de instanţă. Singurele componente structurate de natura „datelor”, care pot fi incluse în definiţia interfeţelor, sunt constantele.
Dacă în privinţa claselor abstracte pot fi omise anumite elemente de
implementare, în privinţa interfeţelor, în declaraţiile lor nu există nici o „urmă” de implementare: definiţia unei interfeţe poate conţine numai specificaţiile operaţiilor (la fel ca şi în cazul metodelor abstracte) şi definiţii de constante.
O clasă poate implementa o interfaţă în mare măsură la fel cum o
clasă abstractă este extinsă (extends) de subclasele sale concrete. Faptul că o clasă se conformează unei interfeţe trebuie declarat în mod explicit prin intermediul cuvântului-cheie implements.
// Listing 3.7: Definire interfeţe şi implementare
// Definire interfaţă
public interface IServiciuOperatiuni {
Double getCredit(OperatiuneContabila o);
Double getDebit(OperatiuneContabila o);
Double getSold(OperatiuneContabila o);
}
// Definite clasă de implementare
public class ServiciuOperatiuni implements IServiciuOperatiuni {
public Double getSold(OperatiuneContabila o){
return getDebit(o) - getCredit(o);
}
public Double getDebit(OperatiuneContabila o){
Double debit = 0.0;
for (InregistrareContabila i: o.getInregistrari()){
if (i instanceof InregistrareDebit)
debit += i.getSuma();
}
return debit;
}
Polimorfism și genericitate
21
public Double getCredit(OperatiuneContabila o){
Double credit = 0.0;
for (InregistrareContabila i: o.getInregistrari()){
if (i instanceof InregistrareCredit)
credit += i.getSuma();
}
return credit;
}
}
3.3.2 Moştenire multiplă Deosebirea esenţială dintre ierarhiile de subtipizare cu interfeţe şi
ierarhiile simple de clase rezidă în faptul că, în cazul primelor, o clasă poate extinde, de fapt, implementa, mai mult decât un singur părinte-interfaţă.
De asemenea, regulile de subtipizare care se aplică claselor se aplică în aceeaşi măsură şi interfeţelor.
// Listing 3.7: Implementare interfeţe multiple
//----------------------------------------------------------------
// Interfeţe ce urmează a fi implementate
//----------------------------------------------------------------
public interface Validatable {
boolean isValid();
}
//----------------------------------------------------------------
public interface Comparable {
public int compareTo(T o);
}
//----------------------------------------------------------------
// Clasă ce implementează mai multe interfeţe
//----------------------------------------------------------------
public class OperatiuneContabila implements
Comparable <OperatiuneContabila>, Validatable{
... ... ...
public int compareTo(OperatiuneContabila op) {
if (this.getDataContabilizare()
Capitolul 3
22
.after(op.getDataContabilizare()))
return 1;
if (this.getDataContabilizare()
.before(op.getDataContabilizare()))
return -1;
return 0;
}
public boolean isValid(){
if (getDebit().equals(getCredit()))
return true;
throw new ExceptieValidare(
"Operatiune dezechilibrata: debit # credit!");
}
... ... ...
}
3.3.3 Forme de polimorfism Cele mai relevante forme de polimorfism constau în: polimorfismul unei operaţii, care are în vedere setul de clase în
definiţia cărora este definită respectiva operaţie și se referă la fenomenul prin care aceeași semnătură a unei operații se regăsește în mai multe noduri ale unei ierarhii de moștenire însoțind metodele de suprascriere;
// Listing 3.8: Acelaşi nume de operaţie cu mai multe implementări
//----------------------------------------------------------------
// Ierarhia de moştenire
// operaţie polimorformă addInregistrareContabila
//----------------------------------------------------------------
public interface IOperatieContabila {
void addInregistrareContabila(
InregistrareContabila inregistrare);
Date getDataContabilizare();
List<InregistrareContabila> getInregistrari();
Double getSold();
void removeInregistrareContabila(
InregistrareContabila inregistrare);
Polimorfism și genericitate
23
}
//----------------------------------------------------------------
public class OperatiuneContabila implements
Comparable<OperatiuneContabila>,
Validatable, IOperatieContabila{
... ... ...
}
//----------------------------------------------------------------
public abstract class OperatiuneComerciala
extends OperatiuneContabila{
... ... ...
}
//----------------------------------------------------------------
// Două implementări diferite pentru
// operaţie polimorformă addInregistrareContabila
//----------------------------------------------------------------
public class Vinzare extends OperatiuneComerciala{
... ... ...
public void addInregistrareContabila(
InregistrareContabila inregistrare){
if (inregistrare instanceof InregistrareDebit
&&! inregistrare.getCont().getCod().startsWith("411"))
throw new AppException(
"Inregistrare incompatibila cu operatiune vinzare !");
super.addInregistrareContabila(inregistrare);
}
... ... ...
}
//----------------------------------------------------------------
public class Cumparare extends OperatiuneComerciala{
... ... ...
public void addInregistrareContabila(
InregistrareContabila inregistrare){
if (inregistrare instanceof InregistrareDebit
&& ! inregistrare.getCont().getCod().startsWith("401"))
throw new AppException(
"Inregistrare incompatibila cu operatiune vinzare !");
super.addInregistrareContabila(inregistrare);
}
... ... ...
}
polimorfismul unei variabile, care are în vedere setul de clase
ale căror instanţe pot fi manevrate/referenţiate prin intermediul
Capitolul 3
24
acelei variabile, ca urmare a inferenţei tipului asociat variabilei;
// Listing 3.9: Aceeaşi variabilă stocând referinţe
// către instanţe din clase diferite
//----------------------------------------------------------------
public class TestPolimorfismVariabileDeInstanta {
public static void main(String[] args){
Cont c_1 = new Cont("411.1", "Client Alfa SRL");
Cont c_2 = new Cont("371", "Marfuri");
// definire variabilă polimorfă
OperatiuneContabila op;
// prima iniţializare variabilă polimorfă
op = new OperatiuneContabila(1,new Date());
op.addInregistrareContabila(
new InregistrareDebit(1, c_1, 100.0));
op.addInregistrareContabila(
new InregistrareCredit(2, c_2, 100.0));
// invocare prima instanţă OperatiuneContabila
// prin referinţa stocată în var. polimorfă
System.out.println("i.ID op: " + op.getIdOperatiune());
// a doua iniţializare variabilă polimorfă
op = new Vinzare(2, new Date(),"Alfa SRL");
op.addInregistrareContabila(
new InregistrareDebit(3, c_1, 50.0));
op.addInregistrareContabila(
new InregistrareCredit(4, c_2, 50.0));
// invocarea celei de a doua instanţe OperatiuneContabila
// prin referinţa stocată în var. polimorfă
System.out.println("ii.ID op: " + op.getIdOperatiune());
}
}
polimorfismul mesajelor (sau adresabilităţii), care are în
vedere combinarea domeniilor celor două forme de polimorfism anterior: folosind aceeași variabilă sunt gestionate două obiecte diferite (două instanțe provenind din clase diferite) și se invocă același nume de operație, dar sunt executate două metode de implementare diferite, provenind din clase diferite.
// Listing 3.10: Variabilă prin care sunt invocate operaţii cu
// acelaşi nume către instanţe din clase diferite
//----------------------------------------------------------------
Polimorfism și genericitate
25
public class OperatiuneContabila
implements Comparable<OperatiuneContabila>,
Validatable, IOperatieContabila{
... ... ...
public void addInregistrareContabila(
InregistrareContabila inregistrare){ ... }
... ... ...
}
//----------------------------------------------------------------
public abstract class OperatiuneComerciala
extends OperatiuneContabila{
... ... ...
}
//----------------------------------------------------------------
// Două implementări diferite pentru
// operaţie polimorformă addInregistrareContabila
//----------------------------------------------------------------
public class Vinzare extends OperatiuneComerciala{
... ... ...
public void addInregistrareContabila(
InregistrareContabila inregistrare){
System.out.println("Apel metoda addInregistrareContabila"+
"din clasa Vinzare");
if (!inregistrare.getCont().getCod().startsWith("411"))
throw new AppException("Inregistrare incompatibila"
+ "cu operatiune vinzare !");
super.addInregistrareContabila(inregistrare);
}
... ... ...
}
//----------------------------------------------------------------
public class Cumparare extends OperatiuneComerciala{
... ... ...
public void addInregistrareContabila(
InregistrareContabila inregistrare){
System.out.println("Apel metoda addInregistrareContabila"
+ "din clasa Cumparare");
if (inregistrare instanceof InregistrareDebit
&& !inregistrare.getCont()
.getCod().startsWith("401"))
throw new AppException("Inregistrare incompatibila"
+ " cu operatiune vinzare !");
super.addInregistrareContabila(inregistrare);
}
Capitolul 3
26
... ... ...
}
//------------------------------------------------------------
// Test invocare implementări operaţii abstracte
//------------------------------------------------------------ public class TestPolimorfismMesaje {
public static void main(String[] args){
Cont c_1 = new Cont("411.1", "Client Alfa SRL");
Cont c_2 = new Cont("401.1", "Furnizor Beta SRL");
// definire variabilă polimorfă
OperatiuneContabila op;
// prima iniţializare variabilă polimorfă
op = new Cumparare(1,new Date());
op.addInregistrareContabila(
new InregistrareDebit(1, c_2, 100.0));
// invocare prima instanţă OperatiuneContabila
//prin referinţa stocată în var. polimorfă
System.out.println("i. cont op 1: "
+ op.getInregistrari().get(0).getCont().getCod());
// a doua iniţializare variabilă polimorfă
op = new Vinzare(2, new Date(),"Alfa SRL");
op.addInregistrareContabila(
new InregistrareDebit(3, c_1, 50.0));
// invocarea celei de a doua instanţe OperatiuneContabila
// prin referinţa stocată în var. polimorfă
System.out.println("ii. cont op 2: "
+ op.getInregistrari().get(0).getCont().getCod());
}
}
-------------- Rezultate test: -----------------------------------
Apel metoda addInregistrareContabila din clasa Cumparare
i. cont op 1: 401.1
Apel metoda addInregistrareContabila din clasa Vinzare
ii. cont op 2: 411.1
------------------------------------------------------------------
3.4 Egalitatea şi comparabilitatea obiectelor Paragraful de față, rămânând tot în contextul discuţiei despre
polimorfism, aduce în discuție subiectul egalității obiectelor și polimorfismul operaţiei equals(), dar abordează şi comparabilitatea
Polimorfism și genericitate
27
obiectelor în sensul ordonării, polimorfismul operaţiilor compareTo() sau compare() din interfeţele Comparable sau Comparator.
Prin urmare, în continuare, vom încerca să răspundem la întrebările: Când sunt egale obiectele și ce semnificație poate avea această
egalitate? Când un obiect poate fi considerat mai mic sau anterior, ori
mai mare sau posterior, într-o ordine dată?
3.4.1 Egalitatea şi identitatea obiectelor. Operaţia equals din superclasa Object
În limbajul Java, operatorul „==” verifică de fapt conţinutul concret al
variabilelor care, în realitate, stochează referinţele obiectelor instanţiate din clase.
Verificarea obiectelor (la care se poate face referire prin variabile) în sensul identităţii presupune verificarea stării interne a acestora sau cel puţin a atributelor care pot diferenţia întotdeauna obiectele între ele.
În Java, în vârful tuturor ierarhiilor de moştenire, se găseşte clasa Object de la care toate clasele vor moşteni metoda equals() ce va putea fi supra-scrisă în sensul codificării identităţii drept principiu de egalitate
Figura 3.1 Egalitatea obiectelor
Capitolul 3
28
În listingul următor este codificat un exemplu privind utilizarea
metodei equals pentru implementarea identității semantice.
// Listing 3.11: Implementare identitate semantică
// prin metoda equals suprascrisă
//----------------------------------------------------------------
// Identitatea conturilor se bazează pe atributul cod
//----------------------------------------------------------------
public class Cont{
... ... ...
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Cont other = (Cont) obj;
if (
(this.cod == null)?
(other.cod != null) :
!this.cod.equals(other.cod)) {
return false;
}
return true;
}
... ... ...
}
//----------------------------------------------------------------
//Identitatea operaţiunilor se bazează pe atributul idOperatiune
//----------------------------------------------------------------
public class OperatiuneContabila{
... ... ...
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final OperatiuneContabila other =
Polimorfism și genericitate
29
(OperatiuneContabila) obj;
if (this.idOperatiune != other.idOperatiune
&& (this.idOperatiune == null
|| !this.idOperatiune.equals(other.idOperatiune))) {
return false;
}
return true;
}
... ... ...
}
//----------------------------------------------------------------
// Identitatea înregistrărilor se bazează pe atributul id
//----------------------------------------------------------------
public class InregistrareContabila{
... ... ...
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final InregistrareContabila other =
(InregistrareContabila) obj;
if (this.id != other.id && (this.id == null ||
!this.id.equals(other.id))) {
return false;
}
return true;
}
... ... ...
}
3.4.2 Obiecte comparabile şi obiecte comparatori. Interfeţele Comparable şi Comparator
Asigurarea compatibilității obiectelor cu un criteriu de ordonare se
poate face prin forțarea claselor să implementeze interfaţa Comparable. În acest fel vor fi obligate să asigure o implementare corespunzătoare operaţiei compareTo(Object other) prin care pot fi ordonate instanţele acestora. Valoarea returnată a acestei operații având următoarea semnificație:
Capitolul 3
30
număr negativ – mai mic; 0 – egal; număr pozitiv – mai mare. Dacă nu există posibilitatea forțării claselor ale căror obiecte urmează
să fie ordonate să implementeze interfața Comparable, o cale alternativă constă la apelarea unui intermediar: o clasă suplimentară care să implementeze interfaţa Comparator. Această clasă va furniza un serviciu (sau un operator) de ordonare pentru instanţele altor clase prin operaţiunea compare(Object firstObject,Object secondObject) a cărei valoare returnată are următoarea semnificație: număr negativ înseamnă că primul obiect (desemnat prin primul
parametru) ar trebui să se găsească înaintea (este mai mic) celui (decât) de-al doilea;
0 înseamnă că cele două obiecte sunt egale; număr pozitiv înseamnă că primul obiect ar trebui să se găsească
în urma (este mai mare) celui (decât) de-al doilea.
// Listing 3.12: Implementare pentru conceptul generic
// de comparabilitate
//----------------------------------------------------------------
// Identitatea conturilor poate fi folosită drept
// criteriu de comparabilitate
//----------------------------------------------------------------
public class Cont implements Comparable<Cont>{
... ... ...
public int compareTo(Object obj) {
if (obj == null) {
throw new RuntimeException("Compare to null !!");
}
if (getClass() != obj.getClass()) {
throw new RuntimeException("Incomparable types !!");
}
final Cont other = (Cont) obj;
return this.getCod().compareTo(other.getCod());
}
... ... ...
}
//----------------------------------------------------------------
// Comparabilitatea operaţiunilor se poate baza
// pe atributul dataContabilizare
//----------------------------------------------------------------
public class OperatiuneContabila
implements Comparable< OperatiuneContabila >{
Polimorfism și genericitate
31
... ... ...
public int compareTo(OperatiuneContabila op) {
if (this.getDataContabilizare()
.after(op.getDataContabilizare()))
return 1;
if (this.getDataContabilizare()
.before(op.getDataContabilizare()))
return -1;
return 0;
}
... ... ...
}
//----------------------------------------------------------------
// Comparabilitatea înregistrărilor se poate baza
// pe atributul nrOrdine
//----------------------------------------------------------------
public class InregistrareContabila
implements Comparable< InregistrareContabila >{
... ... ...
public int compareTo(InregistrareContabila other) {
return this.nrOrdine.compareTo(other.getNrOrdine());
}
... ... ...
}
3.5 Obiecte în colecţii. Colecţii generice După ce am discutat în capitolul precedent despre anumite tipuri de
obiecte pe care le-am calificat drept fundamentale, în continuare vom dezbate acele structuri fără de care programarea orientată obiect nu ar putea avea sau nu ar putea implementa atributul multiplicităţii, decât folosind tablourile într-un mod limitat şi destul de inflexibil. Este vorba despre colecţii, simple sau generice, indexate natural sau prin chei specifice, cu şi fără duplicate, ordonate sau nu.
Structura bibliotecii (API) standard privind colecţiile din mediul Java este organizată pornind de la un sistem bazat pe următoarele interfeţe:
interfața Collection, pentru colecţiile simple, ale căror elemente
sunt accesibile prin intermediul unui obiect de tip Iterator, având ca specializări sub-interfeţele: List, pentru colecţii care permit duplicate şi ale căror elemente
sunt accesibile prin indecşi – valori întregi (int), asemănător tablourilor liniare (vectorilor);
Capitolul 3
32
Set, pentru colecţii care nu permit duplicate;
Map, pentru colecţii în care obiectele-valoare sunt stocate numai pe bază (sau în asociaţie) cu o cheie, ce poate fi conformă cu orice tip convențional.
Cele mai comune implementări concrete pentru aceste interfeţe,
disponibile implicit în mediul Java standard, constau în următoarele clase: ArrayList, LinkedList – pentru List; HashSet şi TreeSet – pentru Set; HashMap şi TreeMap – pentru Map.
Interfeţele standard pentru colecţiile Java convenționale au însă şi alte
implementări furnizate de alţi producători. Printre aceste implementări alternative cele mai cunoscute sunt Appache-Commons-Collections, şi Google-Collections.
3.5.1 API pentru colecţiile de obiecte în mediul Java. Colecţii indexate, sortate şi tipizate
Pentru a da un sens mai larg termenului „colecţii” unii autori folosesc
noţiunea de container în sensul de întreg format din mai multe părţi omogene sau neomogene în ceea ce priveşte tipologia lor.
Prima distincţie între aceste clase se face din punctul de vedere al
accesului, şi anume: pe de o parte există colecţii în care accesul la obiecte se face prin
index (asemănător array-urilor) sau secvenţial, parcurgând iterativ elementele (valorile). Din punctul de vedere al utilizatorului aceste colecţii sunt văzute ca fiind de tip Collection;
pe de altă parte există colecţii în care obiectelor le sunt asociate chei, o cheie putând face legătura cu un singur obiect. Evident, accesul se bazează pe aceste chei. Prin urmare, elementele unei astfel de colecţii sunt de fapt perechi de obiecte, dintre care unul serveşte drept cale de acces spre celălalt. Din punctul de vedere al utilizatorului, astfel de colecţii sunt văzute ca fiind de tip Map.
Polimorfism și genericitate
33
Collection Map
Set List
ArrayList LinkedList HashSet TreeSet
HashMap TreeMap
Iterator
ListIterator
Figura 3.2 O parte din clasele cele mai importante ale API-ului privind
colecţiile
Definiţia interfeţei Collection cuprinde, în esenţă, setul de operații
prezentat în tabelul următor.
Tabelul 3.1 Definiția operațiilor interfeței Collection
Operații ale
interfeței Collection
Explicații
public int size() Returnează numărul de elemente al
colecţiei.
public boolean isEmpty() Verifică dacă există cel puţin un
element iniţializat în colecţie.
public boolean containts(Object
element)
Verifică existenţa elementului
specificat în colecţie.
public void add(Object element) Adaugă un element în colecţie.
public void remove(Object
element)
Şterge un element din colecţie.
public Iterator iterator() Întoarce o instanţă Iterator prin ale
cărei metode next() şi hasNext() se
poate constitui o buclă iterativă
pentru parcurgerea colecţiei.
public boolean
addAll(Collection c)
Adaugă în colecţia curentă
elementele altei colecţii.
public boolean
removeAll(Collection c)
Şterge din colecţia curentă toate
elementele care se găsesc şi în
colecţia specificată (prin argument).
public boolean Şterge din colecţia curentă toate
Capitolul 3
34
retainAll(Collection c) elementele, cu excepţia celor care se
regăsesc în colecţia specificată.
public boolean
containsAll(Collection c)
Verifică dacă toate elementele din
colecţia specificată se găsesc şi în
colecţia curentă.
public void clear() Goleşte colecţia.
public Object[] toArray() Creează un array pe baza
elementelor colecţiei curente.
Prin urmare, indiferent de clasa care reprezintă materializarea fizică a
colecţiei, utilizatorii acesteia o pot accesa în orice situaţie folosind
metodele din definiţia interfeţei de mai sus.
Containerele de tip Colection sunt diferenţiate la rândul lor în două
categorii:
colecţii ordonate de elemente neduplicate, caz în care accesarea
acestora se face prin intermediul interfeţei Set;
colecţii ne-ordonate de elemente păstrate în ordinea adăugării,
interfaţa List fiind esenţială în acest caz.
Definiţia interfeţei List care, bineînţeles, extinde interfaţa Collection,
după cum rezultă şi din figura 3.2, cuprinde grupul de operații prezentat în
tabelul următor.
Tabelul 3.2 Definiția operațiilor interfeței List
Operații ale
interfeței List
Explicații
public boolean addAll(int,
colection)
Inserează toate elementele din
colecţia specificată (prin argument)
în colecţia iniţială începând la o
anumită poziţie.
public Object get(int) Returnează elementul de la poziţia
(indexul) specificat.
public Object set(int, Object) Înlocuieşte elementul de la poziţia
indicată cu obiectul specificat (prin
al doilea argument). Returnează
Polimorfism și genericitate
35
elementul care se găsea anterior pe
respectiva poziţie.
public Object remove(int) Îndepărtează din colecţie elementul
de la poziţia specificată. Returnează
elementul eliminat.
public int indexOf(Object) Returnează poziţia la care apare
prima dată în colecţie elementul
specificat sau –1 în cazul
insuccesului.
public int lastIndexOf(Object) Returnează poziţia la care apare
ultima dată în colecţie elementul
specificat sau –1 în cazul
insuccesului.
public ListIterator listIterator() Returnează o instanţă ListIterator
(subclasă a Iterator) pentru
parcurgerea elementelor din listă.
public ListIterator
listIterator(int)
Returnează o instanţă ListIterator
(subclasă a Iterator) pentru
parcurgerea elementelor din listă
începând cu poziţia specificată.
public List subList(int from, int
to)
Returnează o nouă colecţie de tip
List ale cărei elemente sunt preluate
din colecţia iniţială între poziţiile
specificate.
Faţă de interfaţa Collection, List adaugă, în special, metode destinate
accesului la elementele individuale, ţinând seama de faptul că indexul
fiecăruia este asociat consecutiv funcţie de adăugarea în listă. Parcurgerea
elementelor se poate face clasic, folosind indexul, sau poate fi realizată
într-o altă ordine dictată de o instanţă ListIterator.
În general, un obiect de tip Iterator trebuie să implementeze
următoarele metode:
public boolean hasNext();
public Object next();
public void remove();
Un ListIterator extinde interfaţa Iterator şi este asociat exclusiv List-
elor. El adăugă metode opţionale prin care poate gestiona elementele unei
liste, add(), set(), remove(), precum şi modalităţi de parcurgere
Capitolul 3
36
bidirecţională a acesteia, hasPrevious(), previous(), nextIndex(),
previousIndex().
Biblioteca privind colecţiile, plasată în pachetul java.util, oferă două
clase concrete pentru implementarea listelor:
LinkedList care asigură un acces secvenţial bine optimizat, o
implementare eficientă a operaţiilor de inserare şi ştergere din
interiorul listei. Accesul aleatoriu la elementele listei este însă
relativ ineficient din punctul de vedere al timpului.
ArrayList care se bazează pe un Array şi permite un acces
aleatoriu rapid la oricare element din colecţie. Inserarea şi
eliminarea elementelor din „mijlocul” listei este însă costisitoare.
Am menționat deja că un Set este o colecţie care nu prezintă elemente
duplicate. Ca urmare, interfaţa Set nu prezintă elemente deosebite faţă de
interfaţa Collection, cu excepţia caracteristicii amintită anterior. Prin
urmare, dubla adăugare, prin apelul operației add(Object), a unui element
într-un set nu va avea ca rezultat duplicarea referinței acestuia, al doilea
apel add(Object) neavând nici un efect.
Distribuţia standard Java oferă două căi de obţinere a unui Set,
folosind următoarele clase concrete:
HashSet care este utilă pentru seturile în care timpul de căutare
este esenţial, obiectele care formează elementele unui astfel de set
trebuie să definească corespunzător metoda hashCode();
TreeSet care este util în obținerea de un seturi ordonate organizate
pe structuri de tip arborescent, aşa încât elementele pot fi obţinute
într-o secvenţă ordonată.
Pe lângă Collection, am menționat și un alt mod de organizare a
obiectelor în colecții descrise prin prin interfaţa Map. O astfel de
structură de date se bazează pe configurarea elementelor ca perechi cheie-
valoare. Fiecare cheie are asociată cel mult o singură valoare.
Polimorfism și genericitate
37
Tabelul 3.2 Definiția operațiilor interfeței Map
Operații ale
interfeței Map
Explicații
public Set keySet() Returnează cheile ca un set.
public Collection values() Returnează valorile ca o colecţie
(Collection).
public Set entrySet() Returnează elementele cheie-valoare
(instanţele java.util.Map.Entry) ca un
Set obişnuit.
public boolean
containsKey(Object)
Verifică existenţa unei chei.
public boolean
containsValue(Object)
Verifică existenţa unei valori.
public Object get(Object key) Returnează o valoare cunoscându-i
cheia asociată.
public Object put(Object key,
Object value)
Asociază valoarea specificată cu cheia
precizată prin primul argument.
Returnează valoarea asociată anterior cu
respectiva cheie.
public void putAll(Map) Copiază toate mapările din containerul
Map specificat în cel curent.
public Object remove(Object
key)
Elimină valoarea asociată cu cheia
specificată prin argument. Returnează
elementul (valoarea) eliminată.
public boolean isEmpty() Verifică dacă există asocieri cheie-
valoare.
public int size() Numărul de asocieri chei-valoare
existente
public void clear() Șterge toate elementele-asocieri din
Map.
Fiecare element al acestui tip de colecţie sau fiecare pereche cheie-
valoare sau fiecare intrare reprezintă un obiect al cărui tip este definit prin
interfaţa java.util.Map.Entry:
public Object getKey();
public Object getValue();
public Object setValue(Object);
public boolean equals(Object);
public int hashCode();
Capitolul 3
38
După cum se observă din tabelul de mai sus, un container de tip Map
poate fi văzut din trei perspective, toate trei fiind de tip Collection: ca un
Set de chei, ca o Collection de valori, ca un Set de asociaţii cheie-valoare.
Prin urmare, parcurgerea unui Map se va realiza funcţie de iteratorul
colecţie sub care este văzut, printr-o structură de genul celei prezentate în
listingul următor.
// Listing 3.13: Obținerea unei interator pentru
// parcurgerea elementelor unui Map
Set s = map.entrySet();
Iterator i = s.Iterator();
while(i.hasNext(){ i.next; ...}
Distribuţia Java oferă pentru acest tip de container două implementări
concrete prin clasele:
HashMap, care se bazează pe o tabelă de tip hash şi furnizează o
performanţă uniformă pentru inserarea şi localizarea perechilor de
valori;
TreeMap, care se bazează pe o structură arborescentă, astfel încât
cele trei perspective ale containerului vor putea fi obţinute deja în
mod ordonat.
3.5.2 Colecţii tipizate şi structuri de control pentru parcurgerea colecţiilor
Sunt uşor sesizabile avantajele colecţiilor asupra tablourilor simple, o
mai simplă gestiune a accesului la elemente, o parcurgere mai facilă prin
iteratori, o mai simplă gestiune a capacităţii de memorare a elementelor,
posibilitatea schimbării modului de referenţiere a elementelor prin chei de
altă natură decât valorile Integer. Poate fi, însă, evidenţiat şi un major
dezavantaj faţă de tablourile native: netipizarea, adică elementele stocate
sunt retrogradate la stadiul de obiecte simple, astfel că re-tratarea lor ca
obiecte ale unor tipuri cunoscute implică nenumărate operaţii de casting.
Polimorfism și genericitate
39
Caracteristicile de genericitate introduse odată cu Java 5 elimină acest
dezavantaj, astfel că declararea colecţiilor poate fi acum însoţită şi de
declaraţia tipului concret al obiectelor stocate, specificat între paranteze
unghiulare, după cum se poate observa și în exemplul din listingul
următor.
// Listing 3.14: Obținerea unui iterator generic
List<Client> clienţi ...
Iterator<Client> i = clienti.iterator();
Parcurgerea unei Colecţii
După cum am văzut deja, un Iterator reprezintă o interfaţă care
specifică un mecanism de parcurgere a unei colecţii prin metodele:
boolean hasNext() ;
Object next() .
De asemenea, ListIterator extinde interfaţa Iterator şi este asociat
exclusiv List-elor, metodele adiționale fiind: add(), set(), remove(),
hasPrevious(), previous() ş.a.
Parcurgerea „clasică” a colecţiilor folosind un astfel de obiect ar
trebui să se realizeze după cum se prezintă în listingul următor.
// Listing 3.15: Parcurgerea unei colecții cu iterator și casting
Iterator i = colectieConturi.iterator();
Cont c;
while( i.hasNext() ){
c = (Cont)i.next(); // necesita operatie de casting
// proceseaza c
}
Parcurgerea colecţiilor tipizate (sau generică) elimină necesitatea
operaţiei de casting, după cum se poate observa în exemplul următor.
// Listing 3.16: Parcurgerea unei colecții cu iterator
// fără casting
Iterator<Cont> i = colectieConturi.iterator();
Capitolul 3
40
Cont c;
while( i.hasNext() ){
c = i.next(); // nu necesita operatie de casting
// proceseaza c
}
De asemenea, pentru colecţiile generice sau tipizate a fost introdusă
chiar şi o structură de control repetitivă de parcurgere a colecţiilor
specifică.
// Listing 3.17: Parcurgerea unei colecții fără iterator
for(Client c: Client){
// procesează c
}
// în loc de
Iterator<Client> i = clienti.iterator();
Client c;
while( i.hasNext()){
c = i.next();
// procează c
}
Importanța egalității obiectelor gestionate în colecţii
Polimorfismul metodei equals este esenţial în cazul colecţiilor.
în cazul colecțiilor de tip Collection, metoda equals este invocată
indirect elementelor individuale în mecanismele interne ale
claselor concrete de implementare pentru confirmarea existenţei
unui obiect prin apelul operației contains(Object o);
în cazul colecțiilor specializate de tip List, metoda equals este
invocată în cautarea (indexului) unui obiect prin operația
indexOf(Object o);
în cazul colecțiilor de tip Map, metoda equals este invocată în
căutarea unei chei prin operaţia get(Object key) sau la
Polimorfism și genericitate
41
confirmarea existenţei unei valori prin operația
containsValue(Object o).
3.5.3 Elemente specifice claselor de implementare a interfeţelor standard privind colecţiile
Până acum am discutat şi prezentat câteva secvenţe de cod privind
utilizarea API-ului standard privind colecţiile, mai exact a interfeţelor
care definesc această funcţionalitate. În continuare vom prezenta câteva
aspecte privind clasele care de fapt realizează serviciile (sau operaţiile)
specificate de aceste interfeţe.
Clasa ArrayList
Clasa ArrayList implementează, după cum am văzut anterior, interfaţa
List la care vor adăuga, bineînţeles, constructorii necesari obţinerii
concrete a unei astfel de colecţii. Definiţia generică (în sensul tipizării)
acestei clase este următoarea:
public ArrayList<T>();
public ArrayList<T>(int size);
public ArrayList<T>(Collection c);
public void trimToSize();
public void ensureCapacity(int minSize);
public Object clone();
Prin urmare, o astfel de „listă” poate fi obţinută specificându-i, însă nu
obligatoriu, dimensiunea iniţială (numărul iniţial de poziţii rezervate),
spre deosebire de cazul array-urilor unde era absolut necesară
cunoaşterea numărului de elemente al colecţiei. De asemenea, un
ArrayList se poate obţine şi pe baza unei colecţii deja existente. Dacă
dimensiunea iniţial rezervată a fost prea mare, existând un număr de
„rubrici” goale, elementele nefolosite pot fi eliminate prin trimToSize(),
Capitolul 3
42
iar dacă iniţial nu a fost specificată capacitatea listei sau cea iniţială
urmează a fi depăşită, se poate asigura rezervarea unui număr de elemente
prin metoda ensureCapacity().
În listingul de mai jos este prezentat un exemplu de declarare,
populare şi acces al unei colecţii ArrayList.
// Listing 3.18: Test colecţii instanţiate din ArrayList
public class TestArrayList {
public static void main(String[] args) {
List<String> list = new ArrayList<String>(2);
list.add("primul");
list.add("al doilea");
list.add("al treilea");
// s-a depasit deja capacitatea initiala, asa ca
// lista va final obligata sa-si redefineasca
// automat dimensiunea
list.add(3, "al patrulea");
// operatie validata fiindca urmeaza indexul 3
// list.add(5, "primul");
// operatie invalidata fiindca urmeaza indexul 4
list.add(4, "al cincilea");
list.add(2, "ultimul");
// Se parcurge lista in stil „clasic":
System.out.println("Iteratie clasica folosind for()");
for(int i=0; i<list.size(); i++)
System.out.println(list.get(i));
// Se parcurge lista prin interator in ordinea indexarii:
System.out.println(
"Iteratie cu iterator in ordinea indexarii");
// Se Obtine un iterator care porneste de la primul index
Iterator<String> iterator = list.listIterator();
while(iterator.hasNext())
System.out.println(iterator.next());
// sau
for(String e: list)
System.out.println(e);
Polimorfism și genericitate
43
// Parcurgem lista prin interator in ordine inversa:
System.out.println("Iteratie cu iterator in "
+ "ordinea inversa indexarii");
// Reconsider iteratorul anterior ca ListIterator, si care
// in iteratia de mai jos va porni de la ultimul element,
// la care a ajuns prin iteratia de msi sus
ListIterator<String> listIterator =
(ListIterator<String>)iterator;
while(listIterator.hasPrevious())
System.out.println(listIterator.previous());
}
}
Rezultatul va fi următorul:
Iteratie clasica folosind for()
primul
al doilea
ultimul
al treilea
al patrulea
al cincilea
Iteratie cu iterator in ordinea indexarii
primul
al doilea
ultimul
al treilea
al patrulea
al cincilea
Iteratie cu iterator in ordinea inversa indexarii
al cincilea
al patrulea
al treilea
ultimul
al doilea
primul
Clasa HashMap
Clasa HashMap implementează, după cum am arătat anterior, interfaţa
Map, oferind avantajul asocierii elementelor cu orice fel de tip de obiecte-
chei, nelimitându-se doar la valorile primitive întregi (int) specifice
Capitolul 3
44
indecşilor celorlalte tipuri de colecţii (Array-tablou clasic sau List). De
asemenea, accesul obiectelor-valori folosind obiectele-chei se realizează
la performanţe rezonabile, ceea ce face din acest tip de container unul
dintre cele mai folositoare.
În definiţia clasei HashMap se găsesc, pe lângă metodele
implementate din interfaţa Map, în primul rând metodele constructor
pentru obţinerea concretă a unei astfel de structuri.
public HashMap<T,T>();
public HashMap<T,T> (int size, float load);
public HashMap<T,T> (int size);
public HashMap<T,T> (Map<T,T>);
Parametrul size din constructorii de mai sus semnifică capacitatea
iniţială a tabelei, iar parametrul load specifică dimensiunea la care trebuie
să ajungă „mapa” pentru a fi relocalizată în altă zonă de memorie.
În listingul de mai jos este prezentat un exemplu concret de folosire a
clasei HashMap.
// Listing 3.19: Test HashMap
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class TestHashMapConturi {
public static void main(String[] args){
String[] coduri = {"301", "401", "101", "212", "531"};
Cont[] conturi = {
new Cont("301", "Materii prime"),
new Cont("401", "Furnizori"),
new Cont("101", "Capital"),
new Cont("212", "Constructii"),
new Cont("531", "Casa")
};
Map<String, Cont> mapConturi =
new HashMap<String, Cont>();
// populez indicative
Polimorfism și genericitate
45
for (int i=0; i<coduri.length; i++)
mapConturi.put(coduri[i], conturi[i]);
// parcurg cele trei perspective ale containerului
// mai intai cheile
Set<String> chei = mapConturi.keySet();
Iterator<String> iteratorChei = chei.iterator();
System.out.println("Cheile:");
while(iteratorChei.hasNext())
System.out.println(iteratorChei.next());
// apoi valorile
Collection<Cont> valori = mapConturi.values();
Iterator<Cont> iteratorValori = valori.iterator();
System.out.println("Valorile:");
while(iteratorValori.hasNext())
System.out.println(iteratorValori.next());
// sau
for(Cont v: valori)
System.out.println(v);
// in fine setul de perechi chei-valoare
Set<Entry<String, Cont>> intrari = mapConturi.entrySet();
Iterator<Entry<String, Cont>> iteratorIntrari =
intrari.iterator();
System.out.println("Perechile cheie-valoare:");
while(iteratorIntrari.hasNext()){
java.util.Map.Entry<String, Cont> intrare =
iteratorIntrari.next();
System.out.println(intrare.getKey() + " -- "
+ intrare.getValue());
}
// sau
for(java.util.Map.Entry<String, Cont> intrare: intrari)
System.out.println(intrare.getKey() + "-"
+ intrare.getValue());
// extrag o valoare cunoscindu-i cheia
String id = "401";
System.out.println("Codul " + id +
" corespunde contului " + mapConturi.get(id));
}
}
Rezultatul va fi următorul:
Capitolul 3
46
Cheile:
531
212
301
401
101
Valorile:
Cont 531 - Casa
Cont 212 - Constructii
Cont 301 - Materii prime
Cont 401 - Furnizori
Cont 101 - Capital
Cont 531 - Casa
Cont 212 - Constructii
Cont 301 - Materii prime
Cont 401 - Furnizori
Cont 101 – Capital
Perechile cheie-valoare:
531 -- Cont 531 - Casa
212 -- Cont 212 - Constructii
301 -- Cont 301 - Materii prime
401 -- Cont 401 - Furnizori
101 -- Cont 101 – Capital
531-Cont 531 - Casa
212-Cont 212 - Constructii
301-Cont 301 - Materii prime
401-Cont 401 - Furnizori
101-Cont 101 – Capital
Codul 401 corespunde contului Cont 401 - Furnizori
Colecţii sortate
Aranjarea elementelor în colecţii se poate dovedi de foarte multe ori
utilă aplicaţiilor care îşi gestionează obiectele cu ajutorul acestora.
Ordonarea elementelor în colecţii, folosind criterii specifice, se poate
realiza în două feluri:
Polimorfism și genericitate
47
extrinsec, adică după ce elementele au fost adăugate în colecţii,
ele vor fi rearanjate folosind o clasă utilitară,
java.util.Collections, furnizată tot de biblioteca privind colecţiile.
Criteriul de sortare este furnizat sub forma unui obiect de tip
Comparator (vezi exemplul următor);
// Listing 3.20: Test ordonare colecţii
public class TestOrdonareListOperatiuni {
public static void main(String[] args) throws ParseException{
SimpleDateFormat format =
new SimpleDateFormat("dd/MM/yyyy");
List<OperatiuneContabila> operatiuni =
new ArrayList<OperatiuneContabila>();
operatiuni.add(new OperatiuneContabila(1,
format.parse("01/06/2009")));
operatiuni.add(new OperatiuneContabila(2,
format.parse("01/03/2009")));
operatiuni.add(new OperatiuneContabila(3,
format.parse("01/02/2009")));
operatiuni.add(new OperatiuneContabila(4,
format.parse("01/04/2009")));
Collections.sort(operatiuni,
new ComparatorOperatiuniDupaData());
System.out.println("Ordonare operatiuni dupa data :");
for (OperatiuneContabila o : operatiuni)
System.out.println(o.getIdOperatiune() + " -- " +
format.format(o.getDataContabilizare()));
Collections.sort(operatiuni,
new ComparatorOperatiuniDupaId());
System.out.println("Ordonare operatiuni dupa id :");
for (OperatiuneContabila o : operatiuni)
System.out.println(o.getIdOperatiune() + " -- " +
format.format(o.getDataContabilizare()));
}
static class ComparatorOperatiuniDupaData
implements Comparator<OperatiuneContabila>{
public int compare(OperatiuneContabila o1,
OperatiuneContabila o2) {
Capitolul 3
48
return o1.getDataContabilizare()
.compareTo(o2.getDataContabilizare());
}
}
static class ComparatorOperatiuniDupaId
implements Comparator<OperatiuneContabila>{
public int compare(OperatiuneContabila o1,
OperatiuneContabila o2) {
return o1.getIdOperatiune()
.compareTo(o2.getIdOperatiune());
}
}
}
Rezultat:
Ordonare operatiuni dupa data :
3 -- 01/02/2009
2 -- 01/03/2009
4 -- 01/04/2009
1 -- 01/06/2009
Ordonare operatiuni dupa id :
1 -- 01/06/2009
2 -- 01/03/2009
3 -- 01/02/2009
4 -- 01/04/2009
intrinsec, adică elementele sunt aranjate corespunzător pe măsură
ce sunt adăugate în colecţii de tip TreeSet sau TreeMap (în cazul
mapelor, de fapt cheile sunt sortate). Criteriul de selecţie este
dedus din modul de implementare a interfeţei Comparable aplicată
clasei din care sunt instanţiate obiectele sau cheile, ce urmează a fi
ordonate (vezi exemplul următor).
// Listing 3.21: Test ordonare în TreeSet
public class OperatiuneContabila implements
Comparable<OperatiuneContabila>{
... ... ...
Polimorfism și genericitate
49
public int compareTo(OperatiuneContabila o) {
OperatiuneContabila op = (OperatiuneContabila) o;
if (this.getDataContabilizare()
.after(op.getDataContabilizare()))
return 1;
if (this.getDataContabilizare()
.before(op.getDataContabilizare()))
return -1;
return 0;
}
... ... ...
}
public class TestTreeSetOperatiuni {
public static void main(String[] args) throws ParseException {
SimpleDateFormat format =
new SimpleDateFormat("dd/MM/yyyy");
Collection<OperatiuneContabila> operatiuni =
new TreeSet<OperatiuneContabila>();
operatiuni.add(new OperatiuneContabila(1,
format.parse("01/06/2009")));
operatiuni.add(new OperatiuneContabila(2,
format.parse("01/03/2009")));
operatiuni.add(new OperatiuneContabila(3,
format.parse("01/02/2009")));
operatiuni.add(new OperatiuneContabila(4,
format.parse("01/04/2009")));
System.out.println("Ordonare operatiuni dupa data :");
for (OperatiuneContabila o : operatiuni) {
System.out.println(o.getIdOperatiune() + " -- " +
format.format(o.getDataContabilizare()));
}
}
}
Rezultat:
Ordonare operatiuni dupa data :
3 -- 01/02/2009
2 -- 01/03/2009
Capitolul 3
50
4 -- 01/04/2009
1 -- 01/06/2009
Dacă în exemplul anterior s-a demonstrat cum se poate ordona o colecție
simplă, în exemplul următor se urmărește ordonarea unei colecții-map de
corespondențe.
// Listing 3.22: Test ordonare chei în TreeMap
public class TestTreeMapConturi {
public static void main(String[] args){
Cont[] conturi = {
new Cont("301", "Materii prime"),
new Cont("401", "Furnizori"),
new Cont("101", "Capital"),
new Cont("212", "Constructii"),
new Cont("531", "Casa")
};
Map<String, Cont> mapConturiOrdonateDupaCod =
new TreeMap<String, Cont>();
for (Cont c : conturi){
mapConturiOrdonateDupaCod.put(c.getCod(), c);
}
// Ordonare conturi dupa cod
System.out.println("Ordonare conturi dupa cod: ");
for (String cod : mapConturiOrdonateDupaCod.keySet()){
System.out.println(mapConturiOrdonateDupaCod.get(cod));
}
Map<String, Cont> mapConturiOrdonateDupaDenumire =
new TreeMap<String, Cont>();
for (Cont c : conturi){
mapConturiOrdonateDupaDenumire
.put(c.getDenumire(), c);
}
// Ordonare conturi dupa denumire
System.out.println("Ordonare conturi dupa denumire: ");
for (String denumire :
Polimorfism și genericitate
51
mapConturiOrdonateDupaDenumire.keySet()){
System.out.println(mapConturiOrdonateDupaDenumire
.get(denumire));
}
}
}
Rezultat:
Ordonare conturi dupa cod:
Cont 101 - Capital
Cont 212 - Constructii
Cont 301 - Materii prime
Cont 401 - Furnizori
Cont 531 - Casa
Ordonare conturi dupa denumire:
Cont 101 - Capital
Cont 531 - Casa
Cont 212 - Constructii
Cont 401 - Furnizori
Cont 301 - Materii prime