mercoledì 5 maggio 2010

Java: la classe ArrayList

Avete mai provato a gestire un'array di oggetti? Con un indice si accede all'array e alla posizione scelta si può, quindi, leggere o scrivere il riferimento all'oggetto. Tuttavia, prima di iniziare a lavorare sull'array occorre fissarne la dimensione! Quando l'intero array è stato riempito in ogni sua posizione, per fare posto ad altri oggetti da inserire nella lista occorre, allora, riallocare tutti gli oggetti fin qui inseriti in un array di dimensioni maggiori.
Il pacchetto java.util di Java implementa un utile classe per gli array di oggetti, la classe ArrayList. L'uso di questa classe, per la collezione di oggetti, invece della classica struttura ad array, permette di delegare alla classe stessa la gestione dinamica della lista! Il costruttore della classe costruisce un elenco di array vuoto, se si conoscono le dimensioni iniziali o finali dell'array che ci occorre è poi possibile costruire una lista specificando la sua capacità iniziale. A differenza dell'array classico che provoca in memoria l'allocazione di n voci per gli oggetti, il costruttore della classe ArrayList, non contiene nessun elemento anche dopo la sua istanza. La lista di oggetti cresce dinamicamente, allungandosi quando occorre spazio per memorizzare altri elementi oppure accorciandosi quando gli elementi che prima la caratterizzavano vengono estratti.
Per conoscere il numero di elementi finora presenti nella lista si può invocare sull'oggetto il metodo size(). La classe colleziona oggetti generici, quindi Obejct, pertanto l'estrazione deve sempre essere seguita da un operazione di casting. L'aggiunta di un oggetto alla lista avviene con il metodo add(), il cui parametro è appunto un Object. Esiste un secondo metodo per l'aggiunta di elementi che permette invece di aggiungere gli oggetti in una posizione specifica, fornendo quindi un indice per la posizione (primo parametro). Attenzione, il primo oggetto nella lista ha posizione 0 (come per gli array).
Il riferimento a un oggetto già inserito può essere modificato con il metodo set() che riceve come parametri l'indice di posizione dell'oggetto nella lista e il nuovo riferimento all'oggetto. Per la rimozione dell'oggetto basta invocare il metodo remove() e fornire allo stesso l'indice di posizione dell'oggetto da cancellare. Se invece vogliamo cancellare l'intera lista in un colpo solo è sufficiente ricordarsi del metodo clear().
Quando la dimensione della lista non deve essere più variata (ad esempio quando si è sicuri che non avverranno altre aggiunte) può essere invocato sull'oggetto il metodo trimToSize() che riduce la capacità della lista alle dimensioni che essa effettivamente occupa in memoria (ottimizzandone gli accessi, credo). L'aggiunta di nuovi oggetti alla lista può provocare l'allocazione dinamica di nuovi spazi di memoria se la capacità iniziale è stata superata (che di default, se non indicata, è di 10 oggetti). Tale allocazione è trasparente al programmatore, che non dovrà così preoccuparsi della gestione dinamica della lista. Se si vuole essere sicuri che l'aggiunta di nuovi oggetti avvenga senza riallocare l'array (operazione che potrebbe rallentare un applicazione) bisogna prima chiamare il metodo ensureCapacity() (indicando la capacità di memorizzazione che ci occorre) e successivamente, quindi, il metodo add().
La classe implementa anche alcuni metodi di ricerca di oggetti. Il metodo contains(), ad esempio, ritorna true se l'oggetto passato come parametro si trova nella lista. Con indexOf() e lastIndexOf() è possibile ricevere l'indice di posizione della prima o ultima occorrenza dell'oggetto (passato come parametro) nella lista.
L'ultimo metodo che voglio segnalarvi ci offre la possibilità di ricevere come output un array di oggetti, quali? Quelli finora inseriti nella lista! Provate a invocare il metodo toArray(). Inutile dirlo, altri metodi possono essere trovati consultando la documentazione in linea.



import Magic.*;

import java.util.*;

public class MagicTest {
   public static void main(String args[]) {
      ArrayList deck=new ArrayList();
      Land land1=new Land("Magic",null,null,"bianco");
      Land land2=new Land("Magic",null,null,"bianco");
      Land land3=new Land("Magic",null,null,"bianco");
      Creature creature1=new Creature("Magic",null,null,"bianco",2,2);
      deck.add(land1);
      deck.add(land2);
      deck.add(land3);
      deck.add(creature1);
      System.out.println("Nel deck ci sono "+deck.size()+" carte:");
      Card temp;
      for (int i=0;i<deck.size();i++) {
         temp=(Card)deck.get(i);
         System.out.println(" - "+temp.info());
      }
   }
}

sabato 1 maggio 2010

Java: la classe Object

Java è un linguaggio di programmazione orientato agli oggetti. Ogni classe permette l'istanza di nuovi oggetti (se la classe non è astratta) e l'invocazione dei metodi (pubblici) sugli stessi.
L'astrazione degli oggetti è resa possibile grazie alla classe Object, dalla quale discendono di default tutte le classi che si scrivono in Java (pur non scrivendo per esteso ogni volta class MyNewClase extends Object). In Object vengono implementati diversi metodi che, per il meccanismo dell'ereditarietà usato da Java, sono allora disponibili anche presso le sottoclassi.
Alcuni di questi metodi coinvolgono essenzialmente gli oggetti della classe, come ad esempio i metodi: clone(), toString() ed equals(). Altri invece entrano in gioco quando si fa uso di thread (per la programmazione multiprocesso o multithread), come ad esempio: wait(), notify() e notifyAll(). Ci occuperemo in futuro di questi metodi.
Il codice scritto in questi metodi è generico e spesse volte occorre riscrivere i metodi ereditati affinché il risultato ritornato sia effettivamente corretto.
Il metodo equals() (con firma public boolean equals(Object obj) ritorna un valore booleano che simboleggia l'esito del confronto fra i due oggetti. Le istruzioni collocate di default in equals() verificano l'eventualità che i due oggetti coinvolti nel confronto puntino alla stessa area di memoria. Si tratta quindi di un confronto poco significativo dal momento che possono esistere due oggetti uguali ma residenti in distinte aree di memoria! Occorre allora ridefinire il metodo in ogni classe (se questo serve) fornendo al suo interno altre istruzioni, ad esempio quelle che coinvolgono il confronto di tutti i campi delle istanze.
Il metodo toString() (con firma public String toString()), invece, ritorna una stringa che descrive l'oggetto. Si consiglia di riscrivere questo metodo in ogni classe implementata poiché risulta particolarmente utile in fase di debugging (far stampare a toString() i campi dell'istanza può sempre essere utile) Il metodo println(x), dunque, equivale a chiamare sull'oggetto x il metodo toString(). In altre parole è possibile riscrivere la chiamata a println(x) come println(x.toString()).
Il metodo clone() (con firma protected Object clone()), infine, esegue la copia di un oggetto allocandone uno nuovo e ricopiando in quest'ultimo gli stessi valori dell'istanza campione. Attenzione, il metodo clone() non copia gli oggetti contenuti nell'oggetto da clonare ma si limita a riportare il riferimento per ognuno di questi (per realizzare una copia fedele di un oggetto e degli oggetti in esso contenuti bisogna evidentemente duplicare anche gli altri oggetti).
La classe Object, inoltre, può essere adoperata come contenitore generico di oggetti (essendo la classe padre di tutti gli oggetti Java). Non dimenticate di ripristinare, il tipo dell'oggetto con un operazione di casting!

/* viste da dietro le carte possono sembrare tutte uguali */
class Card {
   public Card(String typeGame, int edition) {
      this.typeGame = typeGame;
      this.edition = edition;
   }
   public String getTypeGame() {
      return typeGame;
   }
   public int getEdition() {
      return edition;
   }
   public boolean equals(Card anotherCard) {
      if ((this.typeGame.compareTo(anotherCard.getTypeGame())) == 0) {
         if (this.edition == anotherCard.getEdition())
            return true;
         else
            return false;
      } else return false;
   }
   public String toString() {
      String description= "Una carta " + this.getTypeGame()
      + " dell'edizione numero " + this.getEdition() + ".";
      return description;
   }
   private String typeGame;
   private int edition;
}

Java: accesso protetto

L'incapsulamento dei dati in Java suggerisce di assegnare ai campi dell'istanza l'identificatore private e ai metodi di accesso l'identificatore public (la visibilità di una funzione privata, infatti, è limitata solo all'interno della classe). Ciò costituisce l'interfaccia pubblica della classe, il set di metodi che è possibile invocare, dall'esterno, sugli oggetti della classe.
Poiché una sottoclasse non può accedere ai dati privati della sua superclasse Java introduce l'identificatore protected per rendere fattibile l'accesso. Si osservino ad esempio le seguenti classi:

class A {
   public A() {
      this.a='a';
   }
   private char a;
}

class B extends A{
   public B() {
      this.b='b';
      this.previousChar=this.a;
   }
   private char b;
   private char previousChar;
}

Il campo dati a della classe A è privato e l'accesso non è possibile! Eclipse riporta: "The field A.a is not visible."! Per agevolare l'accesso è sufficiente modificare il campo dell'istanza da private a protected:

class A {
   public A() {
      this.a='a';
   }
   protected char a;
}

class B extends A{
   public B() {
      this.b='b';
      this.previousChar=this.a;
   }
   private char b;
   private char previousChar;
}

Bisogna analizzare attentamente la possibilità di prevedere un campo dell'istanza protetto. C'è il rischio che a partire da quella classe che ne fa uso vengano poi generate altre classi per il solo gusto di accedere al campo protetto dell'istanza! Si può limitare il fenomeno dichiarando un metodo che accede al campo protetto con l'identificatore protected: un metodo protetto è invocabile solo da una classe derivata (chi deriva una classe dovrebbe conoscerne i metodi anche se usando la riflessione si può comunque aggirare l'ostacolo).

class A {
   public A() {
      this.a='a';
   }
   protected char getA() {
      return a;
   }
   private char a;
}

class B extends A{
   public B() {
      this.b='b';
      this.previousChar=this.getA();
   }
   private char b;
   private char previousChar;
}