Construcció d'Aplicacions de Programari Lliure en J2EE/Java: classes i interfícies, herència i polimorfisme, entrada/sortida, col·leccions, accés a bases de dades

Qué ès ?[modifica]

Java és un llenguatge de programació amb tres característiques fonamentals:

  • És independent de plataforma: Java és un llenguatge interpretat, però amb una característica que el fa especial. El seu codi font és primer transformat, per compilació, en un llenguatge anomenat ByteCode. Quan es vol executar, aquest Bytecode ha de ser executat per un interpret, anomenat Màquina Virtual, que el transforma en llenguatge màquina i el lliura al sistema operatiu. Per tant, un programa Java un cop compilat amb ByteCode, podrà ser executat en qualsevol plataforma que disposi d'una màquina virtual de Java.
  • És orientat a objectes: Un programa orientat a objectes està compost per un conjunt d'entitats o objectes que interactuen intercanviant missatges o dades, en compte d'estar format per un conjunt de funcions. Els llenguatges orientats a objectes faciliten la programació d'aplicacions ja que representen la informació d'una forma més propera a la realitat.
  • És robust i segur: Java permet gestionar de forma molt eficient i senzilla les errades de programari, així com controlar l'accés als objectes i als seus mètodes.

Objectes i classes[modifica]

Què són ?[modifica]

Un objecte de programari representa un objecte de la vida real o qualsevol altre concepte al que es pugui associar un estat i un comportament.

L'estat d'un objecte està format per un conjunt de característiques que el defineixen. En el cas del programari, l'estat es guarda a les variables.

El comportament, d'altra banda, el conformen un conjunt d'accions que es poden fer sobre un objecte per a canviar el seu estat. En el cas del programari, el comportament es representa amb mètodes.

Exemple: Objecte Avió

Estat (Variables): Color, Pes, Velocitat, Antiguitat
Comportament (Mètodes): Pintar, Accelerar, Frenar


Una classe representa un conjunt d'objectes amb variables i mètodes comuns. Per exemple, la classe Avió representa a tots els objectes avió. A un objecte concret d'una classe se l'anomena instància. Les classes es representen mitjançant codi en un determinat llenguatge de programació.

Exemple: Classe Avió en Java

class Avió{

  String color;
  int velocitat;


  void pintar(String color){

    this.color = color;

  }


  void accelerar(int velocitat){

    this.velocitat = this.velocitat + velocitat;

  }

  void frenar(int velocitat){

    this.velocitat = this.velocitat - velocitat;

  }
}

Funcionament[modifica]

Una classe es defineix en un fitxer que porta el mateix nom que la classe, amb l'extensió .java.

La seva definió es fa amb la directiva class que pot anar precedida per alguna de les següents directives (en l'ordre en què es presenten):

  • public: Qualsevol altra classe hi pot accedir. En cas de no indicar-se, únicament hi poden accedir classes del mateix paquet
  • abstract: No es poden crear objectes d'aquesta classe
  • final: No es poden derivar altres classes d'aquesta classe

Directives de control d'accés[modifica]

Una classe pot decidir qui podrà accedir als seus mètodes o variables utilitzant les directives private, protected i public. Els efectes d'aquestes directives són:

Directiva/Pot accedir Classe Paquet Subclasse Tothom
private No No No
protected No
public
no especificat No No


Directives de modificació del comportament[modifica]

Hi ha diverses directives de modificació del comportament, que s'apliquen de forma diferent a les variables i als mètodes. Veiem una taula de resum:

Directiva/Aplicació Variable Mètode
static És una variable de la classe, no de l'objecte És un mètode de la classe, no de l'objecte
final És una constant No pot ser sobreescrit per una classe derivada
abstract No aplicable El mètode ha de ser implementat en una classe derivada


Constructors[modifica]

Una classe pot definir constructors, que són mètodes especial que porten el mateix nom que la classe i s'utlitzen per a crear els objectes d'aquesta classe. A les classes que no defineixen cap constructor, Java els hi crea un constructor buid.

Es poden definir tants constructors d'una classe com es vulgui, sempre que tinguin diferents arguments. Un constructor no pot retornar cap valor.

Exemple: Constructor de la classe AvioCarrega

class AvioCarrega extends Avio{

    //Constructor buid
    AvioCarrega(){}

    //Constructor amb un paràmetre
    AvioCarrega(String color){

      this.color = color;

    }
}

Creació d'objectes d'una classe[modifica]

Per a definir objectes d'una classe, s'utilitza la directiva new().

Exemple: Creació d'objectes de la classe AvioCarrega

AvioCarrega meuAvio = new AvioCarrega();

AvioCarrega avioBlau = new AvioCarrega("blau");

Paquets[modifica]

Què són ?[modifica]

Els paquets són agrupacions de classes utilitzats per a facilitar la seva búsqueda i ús, evitar conflictes de noms i controlar-ne l'accés.

Nomenclatura[modifica]

Els noms de paquet es formen amb paraules separades per punts. Per convenció, comencen amb el nom de domini invertit de l'empresa o institució que els ha desenvolupat.

Exemple: Si desenvolupem algun paquet a l'Àrea de sistemes d'informació i comunicació de la Universitat de Lleida dins del projecte Campus Virtual, el nom hauria de començar per ca.udl.asic.campusv

D'aquesta forma, identifiquem fàcilment la procedència dels paquets i, per tant, de les classes que contenen.


Espai de noms[modifica]

Els paquets ens serveixen també per a resoldre conflictes entre noms de les classes. Imagineu que volem desenvolupar una classe i anomenar-la String. Sense el concepte de paquets, això no seria possible, donat que en el llenguatge Java ja existeix una classe amb aquest nom.

Afortunadament, amb el concepte de paquet, aquest problema desapareix donat que java.lang.String mai es podrà confondre amb ca.udl.asic.projecte.String.


Control d'accés[modifica]

Com hem vist, Java ofereix mecanismes de control d'accés a les classes, a les seves variables i als seus mètodes. Amb el concepte de paquet, introduïm un nivell més al mecanisme, de forma que es pot limitar l'accés a classes del mateix paquet.

Creació de paquets[modifica]

Quan volem definir una classe com a pertanyent a un paquet, utilitzem la directiva package a l'inici del fitxer que conté la classe.

Exemple: Declarem la classe Avió com a pertanyent al paquet ca.udl.asic.estiu

package ca.udl.asic.estiu;

class Avio{
}

Per convenció, les classes es guarden en el sistema de fitxers utilitzant una jerarquia de directoris que reflecteix el paquet al qual pertanyen.

Exemple: Les classes del paquet ca.udl.asic.estiu es guardarien al directori ca/udl/asic/estiu/.


Importació de paquets[modifica]

La importació de paquets és un mecanisme que ens evita haver d'escriure el nom complet d'una classe, inclòs el nom del paquet, quan la utilitzem.

Exemple: Sense importació, hem d'utilitzar el nom complet d'una classe per a fer-hi referència

class HolaMon{

   java.lang.String missatge = "Hola món";

   java.lang.System.out.println(missatge);
}


Exemple: Amb importació

import java.lang.*;

class HolaMon{

   String missatge = "Hola món";

   System.out.println(missatge);
}


A les sentències d'importació podem importar una sola classe o totes les classes d'un determinat paquet, utilitzant l'asterisc.

Exemples[modifica]

Exemples de paquets dins del mateix Java son:

  • java.lang: Agrupa les classes fonamentals del llenguatge Java com System, String ...
  • java.io: Agrupa les classes relacionades amb la gestió de l'entrada/sortida
  • java.math: Agrupa les classes relacionades amb els càlculs matemàtics
  • java.net: Agrupa les classes relacionades amb la gestió de les comunicacions per xarxa
  • java.sql: Agrupa les classes relacionades amb la connexió amb bases de dades relacionals
  • java.util: Agrupa classes d'utilitat com col·leccions, dates, diccionaris ...

Herència[modifica]

Què significa ?[modifica]

L'herència, en els llenguatges de programació orientada a objectes, és el nom que es dóna a la capacitat que tenen els objectes de derivar d'altres objectes, heretant les característiques de l'objecte pare i fent possible la seva extensió mitjançant l'agregació de variables i mètodes.

Funcionament[modifica]

En Java, la única herència permesa és l'herència simple, és a dir, una classe pot derivar d'una i solament una classe pare. La declaració d'una classe com a derivada (o filla) d'una altra es fa utilitzant la directiva extends.

Exemple: Declarem AvioCarrega com a classe derivada d'Avio

class AvioCarrega extends Avio{

  int CapacitatBodega;
}


Pel fet de ser una classe derivada d'Avio, AvioCarrega hereta totes les variables i mètodes d' Avio, excepte el constructor. Podrà utilitzar tots els mètodes d' Avio que no estiguin declarats com a private com si estiguessin declarats en el seu propi codi.

Exemple: Cridem al mètode Pintar d'AvioCarrega, tot i que en el seu codi font no consta aquest mètode

AvioCarrega meuAvio = new AvioCarrega();

meuAvio.pintar("blau");

System.out.println("El meu avió de càrrega és de color " + meuAvio.color);

Polimorfisme[modifica]

Què significa ?[modifica]

Polimorfisme és la capacitat ens ofereixen els llenguatges de programació orientada a objetes per a reescriure mètodes en classes derivades que s'han definit a la classe pare. Una classe pot decidir que cap classe derivada pugui reescriure un mètode marcant-lo com a final.

Funcionament[modifica]

Una classe derivada pot reescriure un mètode definit en la classe pare tornant-lo a definir en el seu propi codi.

Exemple: AvioCarrega sobreescriu el mètode pinta d'Avio

class AvioCarrega extends Avio{

  pinta(String color){

    this.color = color + "Metàlic";
  }
}

Una classe derivada pot cridar a un mètode de la classe pare amb la directiva super.

Exemple: AvioCarrega sobreescriu el mètode pinta d' Avio cridant al mètode pinta d' Avio i afegint informació

class AvioCarrega extends Avio{

  pinta(String color){

    super.pinta(color);
    this.color = this.color + "Metàlic";
  }
}

Interfícies i implementacions[modifica]

Què és ?[modifica]

Una interfície es defineix mitjançant un conjunt de mètodes sense implementar, i tota classe que declari que implementa la interfície està obligada a implementar tots aquests mètodes. Per tant, una interfície és un contracte entre una classe i els seus usuaris que garanteix que determinats mètodes tenen implementació. Java posseeix un mecanisme que li permet simular l'herència múltiple, a través d'interfícies. Per tant, una interfície és una col·lecció de noms de mètodes sense definicions reals que indiquen que una classe té un conjunt de comportaments, a més dels quals la classe hereta de les seves superclasses.

Funcionament[modifica]

Exemple: Definició de la interfície ControlVelocitat

public interface ControlVelocitat{

    public int accelerar(int velocitat);

    public int frenar(int velocitat);

    public void fixarVelocitat(int velocitat);
}


Una classe que implementi aquesta interfície haurà d'implementar els seus mètodes

Exemple: Comproveu que aquesta classe és INCORRECTA, i no compila

public class Avio implements ControlVelocitat{

    int velocitat;


    //Constructor de la classe. Fixa la velocitat a 0
    
    Avio(){
       velocitat = 0;
    }


    // Mètode accelerar. Augmenta la velocitat segons el paràmetre d'entrada velocitat

    public int accelerar(int velocitat){
        this.velocitat = this.velocitat + velocitat;
    }
}


Exemple: Comproveu que aquesta classe sí compila

public class Avio implements ControlVelocitat{

    int velocitat;


    //Constructor de la classe. Fixa la velocitat a 0
    
    Avio(){
       velocitat = 0;
    }


    // Mètode accelerar. Augmenta la velocitat segons el paràmetre d'entrada velocitat

    public int accelerar(int velocitat){
        this.velocitat = this.velocitat + velocitat;
    }


    // Mètode frenar. Redueix la velocitat segons el paràmetre d'entrada velocitat

    public int frenar(int velocitat){
        this.velocitat = this.velocitat - velocitat;
        return velocitat;
    }


    // Mètode fixarVelocitat. Fixa la velocitat segons el paràmetre d'entrada velocitat

    public void fixarVelocitat(int velocitat){
        this.velocitat = velocitat;
    }
}

Classes d'utilitat[modifica]

La JDK disposa de moltes classes d'utilitat que ajuden al programador a realitzar accions comuns. Per a conèixer el seu funcionament és imprescindible consultar la documentació de la versió de JDK.

La documentació de la versió que nosaltres utilitzem es pot trobar aquí.

Els paquets de més utilitat són els que s'han comentat a l'apartat que parla dels paquets. A les pròximes seccions parlarem de dos tipus de classes d'utilitat molt importants.


Entrada/Sortida[modifica]

L'entrada/sortida inclou totes les operacions de comunicació entre una aplicació i la resta del món. Aquesta comunicació es pot realitzar fonamentalment per tres vies:

  • Mitjançant l'entrada i sortida estàndards
  • Mitjançant fitxers
  • Mitjançant una xarxa de comunicacions: Aquest cas no el veurem en aquest curs


I/O Streams o fluxes d'entrada/sortida[modifica]

L'entrada/sortida a Java es fa mitjançant Streams (fluxes) d'entrada/sortida. Aquests fluxes poden ser de dos tipus:

  • Fluxe de lectura: Acumula dades que reb del dispositiu d'entrada associat a ell i facilita mètodes per accedir a aquesta informació
  • Fluxe d'escriptura: Facilita mètodes per acumular dades i els traspassa al dispositiu de sortida associat.

Els streams més comodes d'utilitzar son aquells que permeten llegir o escriure línies, BufferedReader i BufferedWriter. El problema comú d'utilitzar aquests fluxes és convertir el mecanisme directe d'obtenció de dades a aquest tipus de fluxe. Veurem com fer-ho en cada cas.


Entrada/sortida estàndard[modifica]

La classe System és la que ens permet comunicar-nos per l'entrada/standard. Per a fer-ho, utilitzarem les seves variables in, out i err que són streams de tipus InputStream, PrintStream i PrintStream respectivament.

Veiem exemples del seu funcionament.

Exemple: Vegeu la classe HolaMon per a veure com escriure un missatge per la sortida estàndard

Exemple: Lectura d'informació de l'entrada standard de forma directa

public class Entrada {

	public static void main(String[] args) {

		try {
			byte[] b = new byte[100];
			
			System.out.println("Escriu algo:");
			
			System.in.read(b);
			
			System.out.println("Has escrit això? : "+b.toString());
			
		} catch (Exception e) {
			e.printStackTrace();
		}	
	}
}


Exemple: Lectura d'informació de l'entrada standard amb BufferedReader

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Entrada {

	public static void main(String[] args) {

		try {
			
			System.out.println("Escriu algo:");
			

                        //IMPORTANT: Observeu com obtenim un BufferedReader a partir d'un InputStream (in)

			BufferedReader d = new BufferedReader(new InputStreamReader(System.in));
			System.out.println("Has escrit això? : "+d.readLine());
			
		} catch (Exception e) {
			e.printStackTrace();
		}	
	}
}


Fitxers[modifica]

La lectura i escriptura de fitxers requereix la utilització de la classe File, a partir de la qual es creen streams per llegir o escriure al fitxer.


Exemple: Lectura i escriptura de fitxers amb FileReader i FileWriter

import java.io.*;

public class Copy {
    public static void main(String[] args) throws IOException {
	File inputFile = new File("/tmp/farrago.txt");
	File outputFile = new File("/tmp/outagain.txt");

        FileReader in = new FileReader(inputFile);
        FileWriter out = new FileWriter(outputFile);
        int c;

        while ((c = in.read()) != -1)
           out.write(c);

        in.close();
        out.close();
    }
}


Exemple: Lectura i escriptura de fitxers amb BufferedReader i BufferedWriter

import java.io.*;

public class Entrada {
	public static void main(String[] args) throws IOException {
		File inputFile = new File("/tmp/farrago.txt");
		File outputFile = new File("/tmp/outagain.txt");

		BufferedReader in = new BufferedReader(new FileReader(inputFile));

		BufferedWriter out = new BufferedWriter(new FileWriter(outputFile));

		String llegit = "";

		while ((llegit = in.readLine()) != null) {

			out.write(llegit);
			out.newLine();
		}
		in.close();
		out.close();
	}
}

Col·leccions[modifica]

Les col·leccions son objectes que agrupen a altres objectes. La forma d'agruparlos diferencia una col·lecció d'una altra.

Tipus de col·leccions:

  • Set: Conté elements sense ordre i no pot contenir elements duplicats
  • List: Conté elements ordenats (es pot especificar la seva posició) i pot contenir duplicats
  • Map: Vincula una clau amb cada objecte, de forma que es pot accedir directament als elements per la seva clau. No pot contenir claus duplicades

Java implementa aquests tipus de col·leccions amb diverses classes, de la que caldrà consultar el seu funcionament.