Mòdul 4

Pràctica 1: Més agrupaments de dades   
Tornar presentació tema
    Pràctica 1 Pràctica 2 Pràctica 3 Pràctica 4 Pràctica 5  
     
 

En el mòdul anterior has pogut descobrir algunes de les capacitats d'agrupament de Java. Les matrius i els vectors et dónen una bona idea de la flexibilitat amb què pots emmagatzemar i cridar ordenadament la informació. Estrictament parlant, les matrius i els vectors et permetran enfrontar gairebé qualsevol situació en la que sigui necessari crear col·lecciones de dades.

Tanmateix el Java disposa d'algunes eines més que poden ser una solució adequada al problema de l'agrupament d'objectes. En aquesta pràctica te'n presentem dues:

  • ArrayList
  • HashMap

Amb això no esgotem les capacitats d'agregació de Java. Et deixem com a exercici d'ampliació que descobreixis les classes que implementen els quatre tipus d'agrupament de dades: Collection, Set, List i Map.

 
     
  Inventariar una botiga de motos  
     
 

Estudiaràs els objectes ArrayList i HashMap amb una aplicació d'inventari de motos que implementi aquestes funcions bàsiques:

  • Enregistrar motos que entren a la botiga.
  • Imprimir dades de cadascuna de les motos de la botiga.
  • Eliminar motos de la llista.
  • Recomptar el número de motos que hi ha a l'inventari.

Primer estudiaràs com pots fer un programa així amb objectes ArrayList. Quan tinguis clares les capacitats d'aquesta classe, revisaràs el codi del programa i el faràs treballar amb objectes HashTable.

Obre el i crea un projecte nou amb el nom motos. Dins del projecte crea una nova classe que es digui GasGas i que contingui el codi següent:

 
     
 
import java.util.ArrayList;

public void GasGas {
    private static String BARRA = "--------------------";
    private ArrayList inventari;

    public GasGas(){
         inventari = new ArrayList();
     }
 
     
 

De moment només fem un parell de declaracions i creem un constructor per a la classe on s'instancia un objecte de la classe ArrayList amb el nom inventari.

Observa la part superior del programa. Apareix una sentència d'importació. Les importacions les explicarem en aquest mòdul amb una mica de detall. La idea general és que la classe ArrayList està al paquet java.util. Per a poder utilitzar-la al nostre programa hem de dir al compil·lador on pot trobar-la. Aquesta és la raó de fer l'import sobre java.util.ArrayList.

 
     
  private static  
     
 

Com sempre, després de la sentència de declaració de la classe, especifiquem els seus camps. Recorda que, preferentment, els camps comencen amb private perquè no siguin directament visibles des de l'exterior de la classe.

Observà, però, aquest camp

private static String BARRA = "-------------------";

Ens servirà per a pintar una barra que emmarqui les sortides amb System.out.println() i fer-les més agradables a la vista.

Té dues peculiaritats:

  • Després de private hem escrit static.
  • El nom del camp l'hem escrit completament en majúscules.

Ho hem escrit així per què el camp BARRA tindrà sempre el mateix valor en tots els objectes de la classe GasGas. Quan es crea un objecte d'aquesta classe el valor del camp BARRA es comparteix entre tots els objectes de la classe, és unic per a tothom. En altres llenguatges aquests tipus de camps són coneguts com a Constants.

Què creus que passaria si un usuari pogués modificar el valor del camp static BARRA? Pots experimetar-ho si vols.

  • Crea un mètode transformador setBarra(String barra) i dóna al camp BARRA un contingut diferent, per exemple "+++++++++++++++++".
  • Després crea dues instàncies de la classe GasGas. Contràriament al que has vist fins ara, si des d'una classe es canvia el valor de BARRA, aquest canvi es propaga als altres objectes.

Com que no ens fa gràcia que un objecte concret de la classe canvïi el contingut de camps estàtics, habitualment no deixarem que els usuaris disposin de mètodes transformadors sobre aquests tipus de camps.

Si ho vols assegurar completament, encara pots fer que el valor que assignes a BARRA quan el declares no pugui ser modificat en cap circumstància, ni en el propi codi de la classe. Declara el camp així:

private static final String
         BARRA = "------------------------------";

i el valor de BARRA ja no es pot modificar mai des d'enlloc.

 
     
  La feina de l'ArrayList  
     
 

De totes les coses que pot fer un ArrayList, aprofitaràs els mètodes add(), size(), get() i remove(). Amb això en tens prou per a fer-te una idea de la funcionalitat de l'objecte i també de les seves limitacions.

 
     
Afegeix, com sempre per sota d'on has escrit els constructors, el mètode següent:
 
     
 

public void afegeixMoto(String model) {
    inventari.add(model);
}

 
     
 

Aquest mètode permet anar afegint objectes a la llista. En el nostre cas afegim cadenes (String) però podríem posar qualsevol tipus d'objecte -però no tipus primitius-.

Convé que sàpigues que l'ArrayList funciona com una cua de cinema (si no es cola ningú): L'objecte que arriba es posa al final de la cua.

Si el teu objecte inventari té aquests dos valors:

Objecte 0 = "HONDA CBR1000RR FIREBLADE"
Objecte 1 = "YAMAHA XV1700 ROAD STAR WARRIOR"

i afegeixo una tercera moto, la llista em queda així:

Objecte 0 = "HONDA CBR1000RR FIREBLADE"
Objecte 1 = "YAMAHA XV1700 ROAD STAR WARRIOR"
Objecte 2 = "HARLEY-DAVIDSON ULTRA CLASSIC ELECTRA GLIDE"

Escriu ara els mètodes següents:

 
     
  public int numeroMotos() {
    return inventari.size();
}

public void eliminaMoto(int moto) {
    if ((moto>0) || (moto < numeroMotos())) {
        inventari.remove(moto);
    } else {
        System.out.println(BARRA);
        System.out.println("Aquesta moto no existeix");
        System.out.println(BARRA);
    }
}
 
     
 
  • El primer mètode, numeroMotos() retorna el número d'elements de la llista.

  • El segon, eliminaMoto(int moto) n'esborra una entrada. Per a esborrar una moto de la llista has de saber el lloc que ocupa.

    Aquesta dada, el lloc de l'objecte a la llista, és la única forma que té l'objecte ArrayList per a localitzar elements.

    Pensa també que si esborres un element de la llista, fas córrer el lloc de la resta d'elements que hi ha per sota. Si a la llista de motos anterior

    Objecte 0 = "HONDA CBR1000RR FIREBLADE"
    Objecte 1 = "YAMAHA XV1700 ROAD STAR WARRIOR"
    Objecte 2 = "HARLEY-DAVIDSON ULTRA CLASSIC ELECTRA GLIDE"

    esborro l'objecte 1: eliminaMoto(1), el contingut de l'ArrayList queda així

    Objecte 0 = "HONDA CBR1000RR FIREBLADE"
    Objecte 1 = "HARLEY-DAVIDSON ULTRA CLASSIC ELECTRA GLIDE"

    La Harley passa de la posició índex 2 a la posició índex 1. I recorda que aquesta posició és l'únic instrument de l'ArrayList per a localitzar individualment elements a la llista.

Com pots observar, l'ArrayList no és la millor elecció per a desar grups d'objectes on sigui crític localitzar elements individuals, però si és una bona opció si hem de recórrer la llista sencera d'una punta a l'altra.

Finalment, afegirem al programa la capacitat de llistar l'inventari. Escriu aquest nou mètode:

 
     
  public void llistaMotos() {
    System.out.println(BARRA);
    System.out.println("Inventari de GAS / GAS Motor");
    System.out.println(BARRA);
    for (int n=0;n<inventari.size();n++) {
        System.out.println(BARRA);
        System.out.println(inventari.get(n));
    }
}
 
     
 

Per a generar la llista sumem dues de les capacitats de l'ArrayList:

  • sabem el número d'elements (mètode size()) i
  • podem accedir a cada element pel lloc que ocupa a la cua amb el mètode get(n), on n és la posició de l'objecte a la llista.

 
  Bucles for i bucles while  
     
 

Per a recórrer la llista des del primer element a darrer has utilitzat una sentència for. Com ja saps, aquesta estructra va iterant les accions indicades en el cos del bucle mentre es compleixi una condició definida a la capçalera del bucle.

En el teu cas, el for es pot traduir en pseudocodi així:

  • Crea una variable que es digui n, dóna-li el valor 0 i executa el codi que hi ha entre les claus.
  • Cada vegada que tornes a començar el bucle, incrementa n en una unitat (n++).
  • No surtis del bucle mentre el valor de n sigui inferior al número d'elements de l'inventari (n<inventari.size()).
 
     

Quan estiguis pensant un programa complex, una bona estratègia per a resoldre'l consisteix en plantejar el problema en pseudocodi. És molt útil per a simplificar les idees.

 
     
 

Podries aconseguir el mateix efecte utilitzant una altra estructura: un bucle while. Prova de substituir el bucle for per aquest codi:

 
     
  int n=0;
while (n<inventari.size()) {
    System.out.println(BARRA);
    System.out.println(inventari.get(n));
    n++;
}
 
     
 

El mètode llistaMotos() et funcionarà exactament igual.

En qualsevol cas, el que has de vigilar sempre quan recorres una llista, ja sigui un ArrayList o una matriu, és a no sortir fóra del límit de la llista. Sinó, obtindràs un dels errors preferits de java: et saltarà una excepció del tipus IndexOutOfBoundsException.

  • Intenta-ho, fes saltar el programa modificant el bucle d'impressió d'inventari per a què accepti números més alts que la longitud de la llista. Observa l'excepció -error- que et comunica la màquina de Java.

Si volem publicar tots els elements de la llista sense por a equivocar-nos, en lloc d'un bucle podem utilitzar un objecte: un iterador.

 
     
  Iteradors    
     
 

Els objectes de tipus llista (ArrayList) estan pensats per emmagatzemar grups d'objectes als qui, en algun moment o altre, se'ls "passarà llista", de la mateixa manera que un professor passa llista a classe.

Per a facilitar aquesta tasca, Java disposa d'uns objectes especials que només serveixen per a fer fàcil aquesta tasca. Són els iteradors.

Tots els objectes de llista estan preparats per treballar conjuntament amb iteradors, que són objectes que s'encarreguen de prendre els valors de la llista i passar-los a qui els necessiti. Per a utilitzar un iterador hem de:

  • Crear un objecte Iterator que està a la llibreria java.util.
  • Assignar les dades de l'ArrayList a l'Iterator.
  • Dir-li a l'Iterator que recorri tota la llista i ens vagi donant els valors.

Tot plegat és una mica més senzill i segur que utilitzant només bucles. Prova-ho:

Afegeix al programa la sentència següent d'importació:

 
     
  import java.util.Iterator;  
     
  Substitueix el bucle for o while per aquest codi:  
     
  Iterator iterador = inventari.iterator();
while (iterador.hasNext()) System.out.println(iterador.next());
 
 


Així de simple.

Procura no oblidar els iteradors. Potser ara et poden semblar una mica rebuscats si estàs acostumat a treballar amb bucles, però són una eficient eina per al tractament de les coleccions en Java.

 
     
  Què pot i no pot fer un ArrayList  
     
 

En resum, el nostre programa d'inventari de motos té les principals característiques dels ArrayList:

  • Pots incrementar el número d'elements de l'ArrayList al teu gust. No has de predefinir el número d'objectes de la llista.
  • Pots accedir en qualsevol moment al número d'elements de la llista.
  • Pots accedir al contingut individual d'un element de la llista si en saps la posició que ocupa.
  • Si elimines un element de la llista, fas córrer la posició de la resta d'elements que queden per sota. Un ArrayList funciona com si apilés els objectes un damunt de l'altre.

No està mal. Està clar, però, que aquest tipus de llista no ens soluciona una de les necessitats més importants quan agrupem objectes: la necessitat de localitzar objectes individuals de la llista a partir d'una clau, ja sigui un nom, un codi d'inventari o qualsevol altra cosa.

Per a atendre aquestes necessitats, Java disposa d'altres tipus d'objectes. En aquesta pràctica n'estudiaràs les HashMap.

 
     
  Les HashMap  
     

Escriuràs una nova versió del projecte motos. Crea una nova classe en el projecte motos amb el nom de GasHash:

 
     
 

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;

/**
* @author Angel Solans
* @version 10-mar-2004
*
*/

public class GasHash {
    private static final String
        BARRA= "------------------------";
    private HashMap inventari;

    public GasHash() {
        inventari=new HashMap();
    }

    public void afegeixMoto(String clau, String descripcio) {
        inventari.put(clau,descripcio);
    }

    public int numeroMotos() {
        return inventari.size();
    }

    public void eliminaMoto(String clau) {
        inventari.remove(clau);
    }

    public String getMoto(String clau) {
        return (String)inventari.get(clau);
    }

    public void llistaMotos() {
        Collection valors = inventari.values();
        Iterator iterador=valors.iterator();
        System.out.println(BARRA);
        System.out.println("Inventari de GAS / GAS Motor");
        System.out.println(BARRA);
        while (iterador.hasNext()) {
            System.out.println(BARRA);
            System.out.println(iterador.next());
        }
    }
}

 
     
 

Crea una instància de la classe i afegeix algun element a la llista amb el mètode afegeixMoto(). Has d'entrar dues cadenes per moto: primer la clau i després la descripció de la màquina.

  • Estaria bé que les claus fossin curtes, clares i descriptives, per exemple "honda", "yamaha" i "harley".
  • El camp descripció pot contenir perfectament el detall del model de moto, per exemple "CBR1000RR FIREBLADE".

Observa com els mètodes d'entrada i sortida de dades del HashMap són get() i put(). A diferència de l'ArrayList, quan fem una crida al mètode get(), no estem demanant l'objecte que ocupa la posició n a la llista d'objectes, sinó que estem buscant l'objecte la clau del qual sigui n.

Aquesta diferència és fonamental entre un tipus d'objecte d'agrupament i l'altre i el que t'ha de fer decidir, en cada situació de programació, quina aproximació has de triar: si només has de llistar objectes consecutius, un ArrayList va molt bé, si has de buscar dins una llista d'objectes a partir d'un camp clau, has de crear un objecte HashMap.

 
     

Si has treballat amb bases de dades ja estaràs familiaritzats amb el concepte de clau. Les bones taules tenen sempre un camp que es diu clau primàriaque identifica de manera única un registre de la taula. Des d'aquest punt de vista, les HashMap treballen bé amb taules, i resulta senzill convertir la clau primària de la taula en una clau de HashMap.

 
     
 

Una altra peculiaritat de la classe que acabes d'escriure està en el mètode llistaMotos().

Recorda que es tracta de la utilitat que ens fa la impressió de tots els elements de la llista. Això, que és evident i transparent en un ArrayList, no és directe en un HashMap (perquè no és un tipus d'objecte pensat per a llistar). Els objectes HashMap no poden passar directament els seus valors a un iterador. Hem de fer una mica de trampa: passarem primer del HashMap a una colecció genèrica i d'aquesta a un iterador. Es fa així: