Mòdul 3

Pràctica 3: Matrius, vectors i d'altres cucs (III)   
Tornar presentació tema
Pràctica 1 Pràctica 2 Pràctica 3 Pràctica 4 Pràctica 5 Pràctica 6  
     
 

Una matriu és un tipus d'estructura molt senzill, massa. Des del punt de vista del programador novell, les matrius tenen un inconvenient fonamental que consisteix en què, abans de poder fer res amb una matriu, ni tan sols emmagatzemar-hi valors, cal declarar-ne la longitud:

 
 
    int[] matriuDEnters=new int[15];
    // La matriu tindrà 15 nombres
 
  o bé  
 
    String[] matriuDeParaules=new String[8];
    // Contindrà 8 paraules
 
  o bé  
 
    Object[] matriuDObjectes=new Object[12];
    // Contindrà 12 objectes
 
  o bé...  
  Això implica que el programador, en declarar la longitud de la matriu, ha d'estar molt segur que no necessitarà afegir-li més elements, que en cap punt de la resta del programa, un índex s'escaparà i voldrà afegir elements a una matriu que ja és plena, que la Màquina Virtual no li escopirà cap ArrayIndexOutOfBoundsException!  
     
 

El SDK de Java proporciona diverses solucions a aquest problema. La primera resulta d'un ús eficaç dels objectes de la classe java.util.Vector.

En aquesta pràctica analitzaràs els recursos disponibles en aquesta classe i els avantatges que t'ofereix enfront de les matrius.

  • Començaràs amb dos projectes senzills que construiran, de dues maneres diferents, la llista de divisors d'un nombre enter i et permetran veure que, a diferència del que passa amb una matriu, el nombre d'objectes que constitueixen un Vector es pot estirar o escurçar a voluntat sense invertir massa esforç de codificació: la gent de Sun ja ha fet la feina per a nosaltres!

  • La pràctica es completa amb un projecte més complex que et mostrarà un exemple d'ús dels mètodes principals del treball amb vectors de Java.
 
     
  Vectors:  
     
  Un Vector és un objecte del paquet java.util, que ve a ser una matriu que podem estirar o escursar a voluntat, sense invertir massa esforç de codificació: la gent de Sun ja ha fet la feina per a nosaltres!  
  L'objecte Vector conté, com a element bàsic, una matriu:  
     
 
protected Object[] elementData;
// No és accessible al programador!
 
     
  i, a més, un munt de mètodes, aquests sí public, per mirar, triar i remenar els membres de la matriu elementData.  
     
  Els més elementals són:  
   
 
  • public Vector(). És el mètode constructor de la classe. Construeix un vector amb capacitat inicial per a 10 objectes.

  • public boolean add(Object objecte). Afegeix l'Object objecte a la cua del Vector. Si la capacitat s'ha esgotat, el Vector redimensiona automàticament la matriu elementData per tal que el nou element hi càpiga.

  • public void addElement(Object objecte). El mateix que l'anterior, excepte que no informa de l'èxit o fracàs de l'operació.

  • public Object elementAt(int index). Retorna l'objecte que és a la posició index (el primer element és a l'índex 0).

  • public void insertElementAt(Object obj, int index). Insereix l'Object objecte a la posició index. Els elements que tenien índex més gran o igual que index corren un lloc cap amunt.

  • public Object remove(int index). Exactament el contrari de l'anterior.

  • public void removeAllElements(). Buida el Vector.

  • public void setElementAt(Object obj, int index). Posa l'Object objecte a la posició index. L'objecte que hi havia abans en aquesta posició es perd.

  • public int size(). La quantitat d'objectes efectius que conté el Vector, no pas la longitud de la matriu elementData!
 
  La resta de mètodes els pots trobar a la documentació de la classe java.util.Vector.  
     
  Vectors en marxa!  
     
Crea el projecte Vectors i, en ell, la classe FaDivisors. De moment, pots començar amb aquest codi:  
     
 
import java.util.*;

/**
 * La classe FaDivisors inclourà dos mètodes diferents
 * per a construir la llista de divisors d'un nombre enter
 *
 * @author SGTI. FIE.
 * @version 2004/08/10
 */

public class FaDivisors {     

   /**
   * Definim un Vector
   */

    Vector vectorEnters;     

   /**
   * Mètode constructor per a objectes de la classe FaDivisors.
   * El vector vectorEnters de moment no té cap objecte.
   */

    public FaDivisors ( ) { // constructor
        vectorEnters=new Vector();
    }

}
 
     
  Abans de continuar, paga la pena inspeccionar el codi i entendre què hi ha escrit:  
     
 
  • La primera línia és
    import java.util.*;

    Efectivament, com que la classe Vector és del paquet java.util, propi del SDK de Java, per poder fer servir els objectes i els mètodes que inclou en la classe que ara estem escrivint, primer de tot cal importar aquest paquet.
 
 
  • Després es declara com a variable d'instància un objecte del tipus Vector, que designem com vectorEnters i en diem així perquè els objectes que hi posarem
        Vector vectorEnters;
 
 
  • I tot seguit teniu el mètode constructor: es construeix el Vector vectorEnters:
            vectorEnters=new Vector();

    En el mateix mètode constructor es podria omplir el vector (com veureu més endavant) però ara de moment el deixem sense cap objecte.
 
  Com podem comprovar si anem pel bon camí? Compilem la classe, construïm una instància de FaDivisors i inspeccionem-la.  
     
 
 
     
  et mostra l'única variable d'instància que hi has definit, això és, el Vector vectorEnters. També el pots inspeccionar:  
     
 
 
     
  Ara li estàs veient les tripes al Vector vectorEnters: protected Object[] elementData és la matriu que conté els objectes que hi has posat i protected int elementCount n'és el nombre. De moment tot està a zero.  
     
  Segueix la inspecció: mira ara protected Object[] elementData:  
     
 
 
     
  La matriu protected Object[] elementData té longitud 10 (ja hem dit que aquesta era la dimensió que es donava per defecte a un Vector)... però cap dels forats està ocupats per un objecte, tots estan buits (nul).  
     
  Posem objectes en un vector    
     
  Ara ja podem anar per omplir el vector amb la llista de divisors. Ho farem amb el mètode public void posaDivisors (int n), que anirà col·locant successivament en "forats" del vector els divisors del nombre enter n que donem com a paràmetre.  
     
 

import java.util.*;
/**
 * La classe FaDivisors inclourà dos mètodes diferents
 * per a construir la llista de divisors d'un nombre enter
 *
 * @author SGTI. FIE.
 * @version 2004/08/10
 */
public class FaDivisors {

    /**
     * Un Vector que contindrà objectes de la classe Integer.
     */
    Vector vectorEnters;

    /**
     * Mètode constructor per a objectes de la classe Divisors.
     * Inicializa el vector vectorEnters de moment sense cap objecte.
     */
    public FaDivisors ( ) { // constructor
        vectorEnters=new Vector();
    }

    /**
     * Mètode que afegeix al vector els nombres que s'escauen
     * @param n el nombre del qual vols en fer la llista de
     * divisors 

     */

    public void posaDivisors (int n) {    
            if (vectorEnters==null) {
                return;
            }
        vectorEnters.removeAllElements();
            for (int i=1;i<=n/2;i++) {
                   if (n%i==0) {
                       vectorEnters.add(new Integer(i));
                   }
            }
        vectorEnters.add(new Integer(n));
    }

}

 
     
  El codi del mètode es descriu a continuació.  
     
 
  • El mètode public void posaDivisors (int n) comença per preguntar-se si el Vector vectorEnters ja ha estat construït (precaució molt recomanable!)

                if (vectorEnters==null) {
                    return;
                }

    i en cas que no ho hagués estat (llavors serà certament null) la instrucció return aturaria l'execució del mètode.

  • Després de la comprovació, en cas que ja estigui creat, el buida completament per començar una nova llista de divisors...

            vectorEnters.removeAllElements();

    ...no fos cas que el nostre Vector vectorEnters ja tingués una llista de divisors d'un altre nombre; si fos així i no tinguéssim aquesta precaució, aniríem afegint una llista de divisors darrere d'una altra!

  • Vist això, els divisors del nombre int n que hem passat com a paràmetre es van afegint al vector amb una estructura for () {...}.

                for (int i=1;i<=n/2;i++) {
                       if (n%i==0) {
                           vectorEnters.add(new Integer(i));
                       }
                }

    Per als valors de la variable comptador i des de l'1 fins a n/2 es comprova si són o no divisors propis de n (el més gran d'aquests pot ser n/2) . Ho seran si el residu de la divisió de n per i, donat per n%i és 0. Per als que sí ho són, els elements s'afegeixen a vectorEnters amb el mètode public boolean add(Object objecte) que és el mètode de la classe java.util.Vector adequat per a aquest objectiu.

    En acabar, afegim a la llista de divisors el mateix nombre
    int n que, certament, és divisor de n.

            vectorEnters.add(new Integer(n));
 
  És el moment de tornar a compilar la classe. Si surt algun error... repassa que hagis escrit exactament el codi. Altrament, fes NewFaDivisors() i tot seguit a l'objecte que s'ha creat, activa el mètode void posaDivisors amb el paràmetre int 20.  
     
  Si ara tornes a inspeccionar veuràs:  
     
 
 
     
 
 
     
  L'objecte vectorEnters de la instància (objecte) faDiviso1 de la classe FaDivisors té 6 llocs plens (això ens ho diu protected int elementCount.)  
     
  Si els inspecciones podràs veure que, efectivament, aquests 6 objectes corresponen als divisors de 20, és a dir {1, 2, 4, 5, 10, 20}. Tot seguit en tens una mostra: l'objecte d'índex 3 té per valor 5 (te'n recordes que el primer index és el 0?)  
     
 
 
     
  Ara ja hem après a posar objectes en un vector, però,... però per què no hi ha escrit, simplement

                 vectorEnters.add(i);

a la línia que posa els nombres que ens interessen com a objectes del vector?
 
     
 

Vegem-ho: Un vector ha de contenir objectes, és a dir, instàncies qualssevol de classes qualssevol, derivades de la classe Object, la qual, a Java, és la mare de totes les classes. Però un nombre enter (int) no és un Object, ni tampoc ho són els nombres reals (float, double) ni els valors booleans (boolean). Si necessitem tractar-los com a Object, aleshores cal que hi hagi definides classes (aquestes sí, filles de Object) que els representin. Pels nombres enters (int), la classe que els representa és la classe Integer. Els mètodes bàsics de la classe Integer són aquests:

  • public Integer(int valor). Un constructor per a construir (obvi!) l'objecte Integer que representa el nombre enter valor.

  • int intValue() . Per a demanar (i, per tant saber!) el nombre enter que és representat per un cert objecte Integer.
Ara ja es veu que new Integer(i) no fa més que construir un Object de la classe Integer que té com a valor el nombre enter i, per poder guardar-lo al vector vectorEnters.

Passa que, a Java, hi ha, en primera aproximació, dos tipus de dades:
  • els tipus primitius: nombres enters (byte, int), nombres amb decimals (float, double) i caràcters de text (char), i

  • els objectes, derivats de classes. Cal tenir en compte, però, que totes les classes deriven d'una única classe mare: la classse Object.
Llavors, si és que necessites que, en algun punt del teu programa, un valor de tipus primitiu sigui considerat un objecte amb totes les de la llei, has de construir el corresponent objecte a partir de les classes (Byte, Integer, Float, Double, Char) que el SDK de Java preveu per a això. Observa'n, a més, els noms: són els dels tipus primitius, però en majúscula.
 
     
  Traiem objectes d'un vector    
     
 

Abans de passar a un projecte que globalitzarà les diferents possibilitats del treball amb vectors, ara veurem una altra manera de construir un vector que reculli la llista de divisors d'un nombre enter.

A diferència de l'exemple anterior ara el que farem serà començar amb un vector amb molts objectes de la classe Integer i en traurem els que sobrin. A la classe FaDivisors pots afegir-hi aquest mètode:

 
     
 

import java.util.*;
/**
 * La classe FaDivisors inclourà dos mètodes diferents
 * per a construir la llista de divisors d'un nombre enter
 *
 * @author SGTI. FIE.
 * @version 2004/08/10
 */
public class FaDivisors {

    <...codi escrit anteriorment...> ;
    
/**
    * Mètode que construeix un vector amb els nombres de l'1 a l'n
    * i després elimina del vector els que no son divisors de n
    * @param n el nombre del qual en vols fer la llista de divisors
    */

    public void treuNoDivisors (int n) {
            if (vectorEnters==null) {
                return;
            }
        vectorEnters.removeAllElements();
            for (int i=1;i<=n;i++) {
                vectorEnters.add(new Integer(i));
            }
            for (int j=n;j>0;j--) {
                    if (n%j!=0) {
                        vectorEnters.remove(j-1);
                    }
            }
    }

}

 
     
 

Com en l'exemple anterior, tot seguit expliquem el codi del mètode
void treuNoDivisors (int n):

  • Les primeres línies coincideixen amb les del mètode posaDivisors: ens preguntem si el Vector vectorEnters ja ha estat construït; si no ho ha estat atuem l'execució dle mètode i si ho ha estat deixem el vector "en blanc":

                if (vectorEnters==null) {
                    return;
                }
            vectorEnters.removeAllElements();

    Potser diràs: s'hagués pogut fer un mètode auxiliar que fes aquesta tasca per tal d'evitar d'escriure codi repetit... i és ben cert, però hem optat més per una visió didàctica del tema. Ja tindràs temps de perfeccionar més i més l'estructura de les teves classes de Java i projectes.

  • Tot seguit omplim el vector amb els objectes de la classe Integer que representen els nombres de l'int 1 al int n.

                for (int i=1;i<=n;i++) {
                    vectorEnters.add(new Integer(i));
                }

    Ho fem, naturalment, amb una estructura for () {...} que repetim per als nombres de l'1 al n i anem afegint successivament els objectes corresponents, que quedaran correlativament en les posicions que corresponen als índexs del 0 al n-1; el nombre j correspon a l'objecte d'índex j-1.

  • I, en acabat, resseguim (amb un altre for () {...}) tots els objectes del vector i eliminem els que no són divisors de n, que són els que compleixen que el residu de la divisió de n per ells no és 0, és a dir n%j!=0..

                for (int j=n;j>0;j--) {
                        if (n%j!=0) {
                            vectorEnters.remove(j-1);
                        }
                }

    Observa que ara comencem pel final, j=n, i anem baixant, (j-- equival a j=j-1 en cada pas del bucle), fins que arribem al primer dels objectes.

    És convenient fer-ho així perquè quan eliminem (remove) un objecte del vector els índexs dels posteriors a ell queden modificats convenientement. Si anem del final cap al principi cada vegada que examinem un objecte té l'índex que li hem asisgnat inicialment.

    Observa que si j no és divisor de n eliminem l'objecte d'índex j-1, conseqüentment al fet que quan omplim el vector el nombre j correspon a l'objecte d'índex j-1.
 
  És el moment que escriguis el codi indicat, compilis la classe i, quan tot funcioni, facis NewFaDivisors() i tot seguit a l'objecte que s'ha creat, activis el mètode
void treuNoDivisors
amb el paràmetre int 20.
 
     
  Naturalment podràs comprovar si inspecciones adequadament que, encara que s'hagi fet per un mètode diferent, el Vector vectorEnters, que té els objectes de la classe Integer que corresponen als divisors de 20, és idèntic al que hem comentat anteriorment.  
     
  Cal reconèixer que fer una i altra vegada l'acc