Enrera
Mòdul 4
Iniciació a la programació en Java
  Pràctica
1
2
3
4
   
Exercicis
Exercicis
 
 
  Objectes i classes (II)
   
  Les classes:
 

 

Les classes són les mares dels objectes i són omnipresents en el llenguatge. S'han de conèixer els seus mecanismes en profunditat si es vol programar amb seguretat.

Possiblement el que més sorprèn als programadors procedents d'altres llenguatges és l'absolut encapsulament de tots els processos del programa en una o més classes. Si procedim de C/C++ i anem a escriure un programa Java, segurament buscarem el lloc on posar una variable global a tot el programa, independent de les classes: doncs no, no el trobarem. Tot va a dins de l'àmbit d'alguna classe, ni que sigui en una classe genèrica per a tot el programa.

Comencem per estudiar la carcassa de les classes de Java:

Una classe té dues parts: la pròpia declaració de la classe i el cos, que n'és el contingut. Quan declarem una classe li assignem un nom, decidim quina és la seva classe mare (o superclasse) i si la nova classe implementa interfícies (vegeu la pràctica 4) . En el cas que no especifiquem cap classe mare, la nostra classe no quedarà sola: serà filla de la classe java.lang.Object, la mare de totes les classes. Aquestes són sintaxis possibles:

Una classe molt simple, que no fa res i es filla directa de la classe java.lang.Object:

class lamevaClasse {
}

Una classe que és filla de la classe laclasseMare:

class lamevaClasse extends laclasseMare {
    <cos de la classe>
}

Una classe filla de laclasseMare i que implementa unainterficie i unaaltrainterficie:

class lamevaClasse extends laclasseMare implements unainterficie,
                                                 unaaltrainterficie {
    <cos de la classe>
}

Les classes filles hereten les variables i mètodes de les seves mares. Tot el que sap fer una classe mare, ho pot fer una classe filla (la filla encara pot fer més coses, perquè podem ampliar les seves capacitats). També sap fer les coses de la classe àvia i així anar pujant fins arribar a la classe java.lang.Object. Això, explicat d'aquesta forma, sembla simple, però no ho és: hi ha tota una casuística, que haurem d'aprendre, de mètodes i variables visibles o no a les altres classes, mètodes que canvien de contingut quan passen a les classes filles, etc.

El cos de les classes conté les declaracions i inicialitzacions dels membres de la classe, siguin mètodes, variables de classe o variables d'instància i la descripció de la feina que fan els mètodes.

   
  Diferents tipus de classes
   
 

No totes les classes són iguals ni tenen el mateix comportament. Què podem fer si ens interessa que una classe no pugui tenir fills? I si volem que una classe sigui visible fora del paquet? I si ens interessa crear prototipus a l'estil de C++? Per resoldre aquestes situacions, Java ens permet definir les nostres classes en diferents categories:

  • public: Les classes públiques es veuen des de les altres classes, no només des de les classes del propi paquet, sinó també des de classes de paquets aliens. Si volem ocultar una classe dins el seu paquet, no definim la classe com a public. Si volem utilitzar-la fora del paquet la definim així:

    public class unaClasse {
        ...
    }

  • final: Si volem aturar la cadena de l'herència a la classe que estem escrivint, la declarem final. A partir d'aquí ningú la podrà estendre. Habitualment una classe es declara final quan no té cap sentit crear-ne classes filles. Algunes classes predeterminades del SDK de Java, com ara la classe java.lang.Math, estan declarades com a final. Una classe pot ser public i també final, no hi ha cap contradicció. Per a definir-la així farem:

    public final class sHaAcabat {
        ...
    }

  • abstract: Si contràriament, volem crear una classe que només serveixi de model per a fer classes filles, la definirem abstract . De les classes abstractes no en podem crear instàncies, només les podem estendre. Si procediu de C++ us recordaran els prototipus perquè tenen el mateix sentit. Veureu exemples d'utilització de classes abstractes en els mòduls corresponents a programació gràfica. Allí ens serviran per a programar allò que tenen en comú alguns grups d'elements gràfics. Les classes abstractes també poden se public o no. De cap manera una classe abstracta pot ser final (una cosa pensada només per a heretar-se no pot tenir prohibida l'herència). Aquesta contradicció no ens la deixaria passar el compilador. La forma d'una classe abstract és:

    abstract class unaClasse {
    }

    Si ara intentéssim una cosa així:

    unaClasse nom = new unaClasse();

    el compil·lador ens diria que la classe és abstracta i que, per tant, no es pot instanciar.

  • syncronizable: en un sistema de programació multitasca i orientat a objectes es pot donar el cas que diferents instàncies d'una classe estiguin variant valors o utilitzant mètodes a la vegada. Pot ser que no ens interessi aquest treball en col·lisió. Si fem una classe syncronizable garantirem que els processos facin cua per accedir als mètodes i camps. Quan una instància acaba la feina, una altra la comença. Aquest ordre assegurat en el treball té un inconvenient: les classes syncronizable són més lentes que les altres perquè en alguns processos només permeten el treball efectiu d'una instància.
   
  Diferents tipus de variables
   
 

Una variable és efectiva entre les dues claus ("{" i "}") on ha estat creada. Si escrivim una variable al començament de la classe, a l'exterior dels mètodes, estem definint una variable membre, visible per a tots els mètodes. Si la variable ésa l'interior d'un cert mètode, es tracta d'una variable local de mètode i no es veu més enllà d'aquest mètode.

Les variables locals poden tenir el mateix nom que les variables membre. Per exemple, aquesta i és correcta:

class Nombres {

    int i = 1000 ;

    public int unNombre () {
        int i = 200;
        return i + 12;
    }
}

Hem declarat dos enters (int) amb el nom "i". La primera i val 1000 i és una variable membre. La segona val 200 i és una variable local. Quan el compilador troba una i en el mètode unNombre() pensa que fa referència a la variable local. Que farem si des del mètode volem treballar amb la variable membre? Ens referirem a ella amb el terme this.nomvariable:

   
class Nombres {

    int i = 1000;

    public int unNombre () {
        int i = 666;

        return this.i;
    }

    public static void main( String[] args) {
        System.out.println(new Nombres().unNombre());
    }

}
   
 

Quin nombre creieu que ens retornarà aquest programa, 1000 o 666?. Si modifiquem return this.i del mètode unNombre() per return i, quin nombre retorna el programa?

Al començament de la pràctica hem fet dues divisions fonamentals de variables, les variables d'instància i les variables de classe. Quan es crea un objecte, també es creen les seves variables d'instància corresponents. En canvi, quan es crea una variable de classe, sempre hi ha en memòria una sola còpia de la variable, que és compartida per tots els objectes de la classe.

Per definir una variable de classe només ens cal donar-li l'atribut static:

public class gestioVideos {

    static int prestatsAvui = 0;

}

Aquesta variable la podem utilitzar des de qualsevol instància:

public void ferPrestec (int id) {
    prestar(id);
    prestatsAvui=+1;
}

En aquest cas, ens serviria per a controlar quants vídeos hem prestat des que hem iniciat el programa. Cada vegada que fem un préstec incrementem en una unitat la variable de classe prestatsAvui. Aquesta variable tindrà el mateix valor per a totes les instàncies de gestioVideos que corrin a la mateixa Màquina Virtual.

Podem accedir al valor de les variables de classe sense necessitat de crear una instància de la classe. En el nostre exemple, podríem saber l'estat de la variable prestatsAvui escrivint gestioVideos.prestatsAvui des de qualsevol mètode que accedeixi al paquet, sense inicialitzar gestioVideos. Aquesta característica també la comparteixen els mètodes de classe.

   

No existeix cap llei escrita de Java que prohibeixi accedir a les variables públiques d'instància a les nostres classes: degut a això hi són. De totes maneres és recomanable crear les nostres variables d'instància com a privades i fer-ne un accés encapsulat a través de mètodes getter i setter. De fet, algunes de les tecnologies de Java més importants (els Java Beans i els Enterprise Java Beans) gairebé ens obliguen a accedir a les variables seguint aquest model. En lloc d'una classe com aquesta:

public class Temperatures {

    public int dies;
    public boolean malTemps;

}

hauríem de fer una classe com aquesta:

public class Temperatures {

    private int dies;
    private boolean malTemps;

    public void setDies(int dies) {
        this.dies = dies;
    }

    public int getDies() {
        return dies;
    }

    public void setMalTemps(boolean malTemps) {
        this.malTemps = malTemps
    }

    public boolean isMaltemps() {
        return malTemps;
    }

}

Per a cadascuna de les variables, un dels mètodes public ens serveix per modificar el valor de la variable i l'altre per a obtenir-lo. L'increment de codi queda compensat per l'estandardització: moltes APIs de Java esperen accedir a les variables de les classes d'aquesta forma.

   
Com que aquest és un model pràcticament universal, gairebé tots els IDEs incorporen utilitats per a encapsular variables automàticament. JCreator també ho incorpora, però només en la versió professional (de pagament).
   
  Diferents tipus de mètodes
   

Al mòdul 2 hem après la forma i funcionament bàsics dels mètodes, i través d'aquest apartat en veurem les implicacions des de la programació orientada a objectes.

Els mètodes, igual que les variables, poden ser d'instància o estàtics.

  • Mètodes d'instància: Sempre que creem un mètode i no li posem davant la paraula static estem definint un mètode d'instància. A partir d'aquest moment el nostre objecte mantindrà, en un apartat privat de la memòria, una còpia particular de totes les variables de mètode. A la pràctica és com si cada objecte mantingués la seva pròpia còpia del mètode.

    Els mètodes d'instància poden accedir, òbviament, a totes les seves variables de mètode. També poden accedir a les variables de classe i a les variables d'instància. Ara veurem com els mètodes de classe o estàtics tenen més "problemes de visió".

  • Mètodes de classe: Si davant d'un mètode hi posem la paraula static, l'estem convertint en un mètode de classe. Es tracta de mètodes que només poden manipular les seves variables de mètode i les variables de classe (la classe només treballa amb una còpia en memòria d'aquestes variables per a tots els objectes creats a partir d'ella).

    Si us fixeu bé, hem estat creant mètodes estàtics des del primer programa que hem escrit: (public static void main(String args[]) {}) . Ara ja podeu entendre aquest seguint de paraules incomprensibles fa només un mes.

    • public, accessible obertament,

    • static, mètode de classe,

    • void, que no retorna res,

    • String args[] paràmetre amb una matriu de tipus java.lang.String.

    Insistim en les característiques de visibilitat dels mètodes de classe: un mètode de classe només pot actuar amb variables de classe. Si heu fet algun programa de proves aquests dies, és força probable que, sense voler, hagueu vulnerat aquest principi, obtenint un enigmàtic error del compilador: "non-static variable n cannot be referenced from a static context

Escriviu aquest programa:
   
 
public class Peixera {

    int especies_animals = 0;
    static int numero_peixeres = 0;

    public static void main(String args[]) {
        System.out.println("especies en estoc: "+especies_animals);
        System.out.println("peixeres venudes: "+numero_peixeres);
    }

}
   
 

Quan el compilareu us donarà error. No podeu accedir a la variable d'instància especies_animals! Comenteu la declaració i la utilització de la variable i torneu a compilar el programa: ara anirà bé.

Això vol dir que els mètodes de classe no poden accedir de cap forma a les variables d'instància? Sí, hi ha truc: podem aprofitar una de les característiques divertides de Java: una classe pot crear a dins seu instàncies de si mateixa! Per accedir a les variables d'instància des d'un mètode static hem de crear una instància de la pròpia classe dins del mètode. Així:

   
 
public class Peixera {

    int especies_animals = 0;
    static int numero_peixeres = 0;

    public static void main(String args[]) {
        Peixera unapeixera = new Peixera();
        System.out.println("especies en estoc: "+
                            unapeixera.especies_animals);
        System.out.println("peixeres venudes: "+
                            unapeixera.numero_peixeres);
    }

}
   
 
  • Mètodes constructors: Una categoria especial de mètodes són els mètodes constructors. Un mètode constructor és el primer mètode que s'executa quan es crea una instància de la classe. Es tracta d'un mètode que té el mateix nom de la classe i una llista de paràmetres. No l'hem d'incloure a cap tipus, ni void ni res. Exclusivament el nom i els paràmetres.

    Els
    paràmetres ens serviran normalment per inicialitzar les variables d'instància que ens convinguin o per a executar una acció o grup d'accions immediatament després de la creació de la instància de la classe. Fins i tot podem tenir diferents constructors en una mateixa classe (gràcies a la possibilitat de sobrecàrrega de mètodes que estudiarem a la pràctica següent) i així crear objectes nous amb diferents estats d'inicialització.

    Aquesta és una utilització possible d'un
    mètode constructor:

    class CalcularInteresSimple {

        private double quantitat;

        public CalcularInteresSimple(double capital,
                                     double tipus,
                                     int temps) {
            quantitat = capital*tipus*temps;
        }
        
    }
   
 
Amunt