Casting tra oggetti

Con il termine casting si intende la promozione implicita o esplicita delle variabili affinché possano restituire un risultato omogeneo nelle operazioni tra loro. Può infatti accadere che una variabile di tipo int debba essere sommata ad una variabile di tipo long (primitive widening) e via dicendo…Quando ciò accade uno dei membri dell’operazione viene promosso al formato più capiente affinché l’operazione possa essere compiuta e non vi siano perdite del dato. Tuttavia vi possono anche essere situazioni in cui questa operazione debba essere controllata allo scopo di ottenere il risultato voluto. E’ il caso del primitive narrowing ossia del restringimento del formato più capiente a quello meno capiente, che determina in questo caso una perdita di parte del dato.

Ma cosa accade quando tutto ciò deve essere fatto sulle classi e sugli oggetti?

Upcasting

Vi possono essere situazioni in cui una classe derivata debba essere convertita nella sua classe base, come nell’esempio seguente:

public class ClasseBase {
    /…/
}

public class ClasseDerivata extends ClasseBase {
    /.../
}

public static void main ( String [ ] args ) {
    ClasseBase identificatore = new ClasseDerivata;
}

Questo tipo di operazione comporta il restringimento della classe derivata alla classe base e può comportare la perdita dei metodi e delle variabili contenute nella classe derivata, tuttavia resta sempre possibile, in quanto la classe derivata condivide almeno parte della propria struttura con la classe base.

E’ altresì possibile convertire valori null e array in oggetti, o meglio, assegnare ad oggetti di classe Object i valori null o attribuire all’oggetto un array:

Object identificatore = null;
tipo identificatore_array [ ] = new tipo [ ];

Object identificatore_oggetto = identificatore_array;

Downcasting

Inverso il ragionamento legato al downcasting che deve essere esplicitato attraverso una particolare sintassi:

//Upcasting, con cui avevamo fatto l’upcasting ad una classe base

ClasseBase identificatore1 = new ClasseDerivata;

//Downcasting, con cui forziamo l’oggetto a rientrare nella classe derivata

ClasseDerivata identificatore2 = (ClasseBase) identificatore1;

In caso di omessa esplicitazione della sintassi di cui sopra riscontreremo un errore. Ciò accade perché non é cosa scontata che le due classi siano tra di essere compatibli e qualora non lo fossero avremo un errore di tipo Class CastException.

Anche in questo caso é possibile il downcasting da Object ad un array, riportando l’array nella sua forma originale:

Object identificatore_oggetto = new tipo[dimensione];
int [ ] identificatore_array = (int [ ]) identificatore_oggetto;

E’ possibile compiere la stessa operazione anche tra tipi differenti, purché l’operazione sia supportata dal linguaggio, ad esempio un array di interi può essere trasformata in un array di tipo long.

L’operatore instanceof

Dovesse mai servire di aver bisogno di conoscere il tipo di oggetto contenuto in una variabile l’operatore instanceof giunge in nostro soccorso. L’operatore instanceof restituisce true sia nel caso l’oggetto sia dello stesso tipo di quello indicato, sia nel caso sia un oggetto istanziato da una classe derivata.

TipoOggetto identificatore_oggetto = new TipoOggetto;

if ( identificatore_oggetto instanceof TipoOggetto) {
    /.../
}

Metodi e classi astratte

Tra le possibilità offerte da Java é doveroso menzionare anche la circostanza dell’astrazione di classi e metodi, vale a dirsi la loro prototipazione, utile a dichiararne l’esistenza salvo poi doverne definire attributi e metodi. Un metodo astratto può essere definito solo in una classe astratta, e questa classe non può essere direttamente istanziata bensì deve essere preliminarmente ridefinita in override.

Le classi astratte sono l’antitesi delle classi final e contrariamente a queste devono essere ridefinite. Le due keyword non possono essere presenti allo stesso tempo.

Le classi astratte rappresentano un aspetto molto importante di Java in quanto poggiano sull’idea di polimorfismo, concetto di base dal quale una stessa classe base (o in questo caso astratta) possa essere declinata in molteplici forme e per molteplici scopi. I metodi astratti possono essere contenuti sono nelle classi astratte.

public abstract class Tipo{
    public abstract void nomeMetodo( );
}

Le interfacce

Come si é detto il linguaggio Java prevede il solo concetto di ereditarietà singola, e ciò significa che é possibile estendere una sola classe per volta. Il problema viene parzialmente risolto dalle interfacce attraverso le quali si possono implementare infinite derivazioni. Le interfacce sono a tutti gli effetti delle classi astratte, che non possono essere istanziate, e al pari delle classi astratte non possono contenere codice salvo variabili che devono essere public, static e final, e metodi che sono astratti in modo predefinito. L’apposizione della keyword abstract davanti ai metodi é del tutto opzionale perché sottintesa. La definizione delle interfacce avviene attraverso la keyword riservata interface.

public interface nome_interfaccia {
    public static final String identificatore = “Stringa”;
    public abstract void nomeMetodo( );
}
public class Tipo implements nome_interfaccia {
    public void nomeMetodo( ) {
        /.../
    }
}

Gli array in Java

Per quanto questa guida cerchi di raggruppare tutti gli argomenti legati alla logica di base all’interno della sezione dedicata, é necessario, fare brevi cenni alle differenze in Java rispetto ad altri linguaggi.

Gli array in Java sono sostanzialmente identici agli altri linguaggi con la sola differenza che in Java gli array sono anch’essi oggetti che devono essere istanziati con una particolare sintassi, che comporta dichiarazione, creazione ed inizializzazione.

La dichiarazione degli array

La dichiarazione di un array avviene attraverso la specificazione del tipo di varibili che deve contenere e l’attribuzione di un identificatore, seguito da una coppia di parentesi quadre [ ]. E’ interessante notare come in Java gli array possono essere costituiti anche da oggetti:

//Dichiarazione di un array di tipo char  
tipo identificatore_array[ ];

//Sintassi alternativa
tipo [ ] identificatore_array;

//Array di oggetti
tipo_oggetto identificatore_oggetto [ ];

//Sintassi alternativa
tipo_oggetto [ ] identificatore_oggetto;

Istanza di un array

Giacché oggetti, gli array in Java necessitano di essere creati (o istanziati) attraverso una particolare propria sintassi, che deve contenere anche la dimensione dell’array che intendiamo creare:

//Istanza di un array di tipo char
identificatore = new char [5];

//Istanza di un array di oggetti
identificatore_oggetto = new tipo_oggetto [5];

Dichiarazione e creazione possono avvenire anche contestualmente:

//Dichiarazione e creazione di tipo primitivo
tipo identificatore [ ] = new tipo [5];

//Dichiarazione e creazioni di tipo oggetto
tipo_oggetto identificatore [ ] = new tipo_oggetto [5];

Inizializzazione di un array

Una volta dichiarato e creato l’array é pronto ad accettare valori. Tale operazione, come abbiamo già visto, prende il nome di inizializzazione. L’inizializzazione può essere singola o multipla. L’inizializzazione singola comporta l’assegnamento delle singole variabili ad ogni elemento dell’indice:

identificatore [indice] = ‘valore’;

//Inizializzazione singola con oggetto
identificatore [indice] = new tipo( );

L’inizializzazione multipla assegna invece molteplici valori in una singola istruzione, attraverso la seguente sintassi:

tipo identificatore [ ] = { ‘valore1’, ‘valore2’, ‘valore3’, ‘valore4’};

//inizializzazione multipla con oggetti
tipo_oggetto identificatore [ ] = { new tipo_oggetto( ), … , new tipo_oggetto( ) };

Notiamo bene che l’assegnamento dei valori avviene in modo sequenziale partendo sempre dall’elemento posto all’indice 0 dell’array.

Dimensione di un array

Un modo pratico per ottenere le dimensioni di un array è attraverso l’attributo lenght che restituisce il numero di elementi presenti. Viene largamente utilizzato nelle operazioni di iterazione, quando vi é la necessità di leggere tutti gli elementi, di inizializzarli o modificarli.

for ( int i = 0; i < identificatore.lenght; i++) {
    identificatore [ i ] = 0;
}

Derivazione ed ereditarietà

Il concetto di ereditarietà vuole che da una classe base si possano derivare infinite altre classi capaci di estenderne e specializzarne i contenuti. Nella programmazione ad oggetti esistono diversi tipi di ereditarietà, singola e multipla. il linguaggio Java non supporta l’ereditarietà multipla, per cui parleremo solo di quella singola. Data una classe base:

public class Quadrilatero {    //Attributi
    int lato1
    int lato2
    int lato3
    int lato4
    public Quadrilatero( int a, int b, int c, int d) {
        lato1 = a;
        lato2 = b;
        lato3 = c;
        lato4 = d;
    }

    public int perimetro( ) {
        return lato1 + lato2 + lato3 + lato4;
    }
}

L’estensione di questa classe base in una classe derivata specializzata avviene attraverso la seguente sintassi:

public class Rettangolo extends Quadrilatero {
    public Rettangolo (int a, int b) {
        super (a,b,a,b);
    }
}

In questo esempio possiamo notare come l’invocazione del costruttore della classe base Quadrilatero avviene attraverso la parola chiave super a cui vengono passati due volte gli stessi argomenti (trattandosi di un rettangolo). Qualora volessimo aggiungere l’attributo colore alla classe derivata la sintassi sarebbe in questo caso la seguente:

public class Rettangolo extends Quadrilatero {
    int colore;
    public Rettangolo (int a, int b, int c) {
        super (a,b,a,b);
        colore = c;
    }
} 

Notiamo che nella classe derivata é stato aggiunto un attributo il cui valore di inizializzazione é passato al metodo costruttore in cui viene invocato il metodo costruttore della classe base ed inizializzato l’attributo con l’argomento passato.

Derivazione di una classe derivata

E’ altresì possibile derivare una classe derivata creando una gerarchia di entità. Come illustrato nel diagramma seguente:

La sintassi utilizzata per derivare un classe derivata é la medesima dell’esempio precedente, con la differenza che rispetto ai 2 argomenti necessari al metodo costruttore della classe padre Rettangolo, passiamo un solo argomento, ripetendolo 2 volte, conservando in questo caso la struttura originale.

public class Quadrato extends Rettangolo {
    public Quadrato (int lato) {
        super (lato, lato);
    }
}

Overload e Override

La derivazione delle classi e la loro relativa specializzazione rendono spesso necessarie operazioni di modifica dei metodi allo scopo di ridefinirne le funzionalità. A questo scopo, é necessario che conservi lo stesso identificatore, un livello di accessibilità non inferiore al precedente e lo stesso tipo di ritorno del metodo originale.

La parola chiave final

Qualora, al contrario, volessimo impedire la derivazione di una classe, l’apposizione della parola chiave final si presta allo scopo. Viene utilizzata per indicare che l’elemento non può essere modificato, trovando così impiego anche nella dichiarazione delle costanti., o nei metodi che non possono essere ridefiniti. Nel caso dell’esempio precedente:

public final class Quadrilatero {
    /…/
}

La superclasse Object

Restando in tema di classi derivate é bene sapere che tutte le classi, compresa la classe System, derivano in realtà da una classe comune: la superclasse Object. Questa particolare classe possiede dei metodi che sono in realtà ereditati da tutte le classi e che possono essere ridefiniti in override, si tratta di:

  • Object clone( ); che clona un’oggetto.
  • boolean equals(Object); indica se due oggetti sono equivalenti;
  • void finalize( ); invocato quando l’oggetto viene cancellato;
  • int hashCode( ); restituisce un codice univoco identificativo dell’oggetto;
  • String toString( ); restituisce una descrizione del contenuto dell’oggetto in formato stringa.

Il metodo toString()

Quest’ultimo metodo risulta essere particolarmente interessante nel momento in cui volessimo conoscere l’indirizzo di memoria in cui é contenuto l’oggetto di cui vogliamo ottenere l’ubicazione.

Il metodo equals( )

Il metodo equals confronta due riferimenti a posizioni di memoria. L’invocazione del metodo accetta due argomenti, il primo, sul quale viene invocato il metodo ed il secondo viene passato come parametro. Restituisce true se due oggetti puntano alla stessa posizione di memoria. Per utilizzarlo é necessario utilizzare la seguente sintassi:

public static boolean equals (Object a, Object b)

Resta tuttavia possibile ottenere lo stesso risultato anche attraverso l’operatore di confronto (==), analizzando la sintassi del metodo originale é infatti possibile esaminarne il principio di funzionamento:

public boolean equals(Object obj) {
    return (this == obj)
}

Il metodo clone( )

Un ulteriore metodo messo a disposizione dalla classe Object, é il metodo clone, che gode di un livello di visibilità protected, che effettua una copia dell’oggetto corrente, comprese le variabili d’istanza inizializzate agli stessi valori. Essendo quest’ultimo inizializzato ad un livello di visibilità protected, andrà ridescritto in override per impostare su di esso una visibilità di tipo public.

public class Punto implements cloneable {
    int x, y;
    public Punto(int x, int y) {
        this.x = x;
        this.y = y;
}

//Override del metodo clone di Object a visibilità public

    public Object clone( ){
        Punto punto = new Punto(this.x, this.y);
        try {
            return super.clone( );
        }
        catch(CloneNotSupportedException e) {
            return null;
        }
    }
}

Occorre innanzitutto dire che per rendere possibile la clonazione é necessario dichiarare l’interfaccia “cloneable” l’oggetto che si intende clonare, altrimenti avremo un errore.

Vediamone ora l’implementazione per esteso in un metodo main:

import java.io.*;
public class CreaPunto {
    public static void main ( ) {
        Punto p, q;
        p = new Punto(3, 4);

//Casting al tipo (Punto) perché altrimenti di tipo Object

        q= (Punto) p.clone( );
    }
}

Esaminando il codice notiamo come l’invocazione del metodo clone abbia comportato un casting di tipo (Punto) appunto perché originariamente dichiarato di tipo Object.

Clonazione di un oggetto di classe derivata

Qualora volessimo clonare un oggetto di classe derivata, sarà sufficiente dichiarare il metodo Object.clone( ) con visibilità public in overload nella classe derivata per poi invocare al suo interno il metodo clone ( ) della classe basse attraverso la parola chiave super:

public class PuntoDerivato extends Punto {
    public PuntoDerivato( int x, int y) {
        super(x, y);
    }
    public Object clone {
        return super.clone( );
    }
}

Ciclo di vita degli oggetti

La gestione della memoria in Java é automatica. Ogni volta che viene creato un oggetto Java alloca una congrua quantità di memoria inizializzando il suo ciclo di vita. Quando questo diventa inutile questo viene eliminato attraverso un metodo nativo della JVM denominato garbage collector, prima di eliminarlo però ne esegue il metodo finalize se questo é presente.

I costruttori

I costruttori sono metodi che compiono tutte quelle operazioni necessarie all’inizializzazione dell’oggetto e dei suoi attributi.

La keyword di auto-riferimento this

Se durante l’invocazione del metodo di un oggetto si renda necessario accedere a qualcuno dei suoi stessi attributi, la parola chiave di auto-riferimento this può validamente e semplicemente sostituire il nome dell’oggetto attivo stesso. Questa pratica é definita auto-referenza esplicita.

Esiste in ogni caso anche la possibilità di un auto-referenza implicita strettamente connesso alla visibilità di una variabile, che funziona in questo modo:

  • Se la variabile non appartiene al medesimo blocco di codice viene cercata risalendo a ritroso nella lista dei parametri del metodo corrente.
  • In caso negativo, viene cercata nella lista degli attributi dell’oggetto
  • Se quest’ultimo tentativo fallisce, il compilatore restituisce un errore.

Concetti fondamentali nella programmazione ad oggetti

Incapsulamento

Il concetto di incapsulamento comporta che attributi e proprietà di un oggetto siano accessibili solo attraverso i metodi dell’oggetto stesso, escludendo che questi possano essere raggiunti direttamente, secondo il proncipio dell’information hiding, quel principio che vorrebbe che la maggior parte delle informazioni siano occultate tanto per una questione di semplificazione quanto per un fatto di sicurezza.

Ereditarietà

Il concetto di ereditarietà vuole che, data una classe base, questa possa dare luogo ad uno svariato numero di classi derivate (o sottoclassi) ciascuna delle quali eredita attributi e metodi della classe di partenza.

Polimorfismo

L’idea di polimorfismo comporta che ciascuna classe derivata possa estendere metodi ed attributi della classe base, dando luogo ad oggetti dotati di caratteristiche proprie inizialmente non previste dalla classe base.

Gerarchia delle classi

L’insieme delle classi base e derivate possono essere rappresentate all’interno di una struttura ad albero, utile a rappresentare le derivazioni di ciascuna classe.

Visibilità e scope

Il concetto di incapsulamento, mediante il quale estendiamo o limitiamo la visibilità di un attributo o di una classe viene gestito attraverso parole chiave specifiche del linguaggio.

Public

Indica che i dati sono accessibili a qualunque altra classe

Protected

Limita la visibilità dei dati alle sottoclassi di questa e alle classi contenute nel medesimo package in cui il dato é definito

Package

Limita la visibilità dei dati alle classi contenute nel medesimo package della classe definita.

Private

Indica che il dato non é accessibile alle altre classi ma solo a quella dichiarata.

Scope di variabili

In Java le variabili possono essere dichiarate in qualsiasi punto. La visibilità della variabili é relegata al blocco in cui esse sono dichiarate. Questo ambito di visibilità viene detto scope. Le variabili dichiarate all’interno di una classe prendono il nome di attributi, mentre quelle dichiarate nei metodi assumo in nome di variabili locali. In linea generale vale il principio secondo cui una variabile dichiarata all’interno di un blocco di istruzioni non é visibile all’esterno di quel blocco.

I package e le classi

I package sono una funzionalità utile ad organizzare e raggruppare le classi in modo sistematico e modulare. Le classi principali di Java sono già raggruppate nel package java.lang. Una ulteriore funzionalità interessante dei package é costituita dalla possibilità di attribuire identificatori uguali a classi differenti purché queste siano dislocate in diversi package, purché questi abbiano nomi differenti. Per collocare una classe all’interno di un package é sufficiente indicare all’inizio del file la parola chiave package seguita dal percorso in cui il file della classe stessa é contenuto, con l’accortezza di sostituire gli slash con dei punti.

package app.classes

Overloading

Con il termine overload si intende la chiamata di metodi con lo stesso identificatore ma con parametri diversi.

Le strutture di controllo

L’istruzione condizionale If

L’ istruzione condizionale if esegue un determinato blocco di codice se la condizione é vera.

if (condizione) {
    istruzione;
}

L’istruzione if/else

Molto simile al costrutto if, ma offre un codice alternativo nel caso che la condizione espressa tra parentesi sia false.

if (condizione) {
    istruzione;
}
else {
    istruzione alternativa
}

Il costrutto switch

Il costrutto switch rappresenta la soluzione migliore qualora si voglia eseguire la valutazione di un valore preciso, a cui fare seguire un istruzione ad esso correlata.

switch (condizione) {
    case 1:
        istruzione 1;
        break;
    case 2:

    case 3:
       istruzione 2;
       break;
    default:
        istruzione di default;
        break;
};

Se la valutazione corrisponde ad uno dei valori espressi nelle etichette case, viene eseguito il relativo blocco di istruzioni, in caso contrario viene eseguita l’istruzione di default. La keyword break determina l’uscita dal ciclo.

Gli operatori logici

L’operatore NOT

Restituisce il contrario dell’elemento valutato. Se é true, restituisce false, e viceversa.

operando! NOT
FalseTrue
TrueFalse

L’operatore AND &&

Restruisce true se entrambi gli operandi sono true, altrimenti false.

1 ° operando2° operando&& AND
FalseFalseFalse
FalseTrueFalse
TrueFalseFalse
TrueTrueTrue

L’operatore OR ||

Restituisce true se almeno uno degli operandi é true.

1 ° operando2° operando|| OR
FalseFalseFalse
FalseTrueTrue
TrueFalseTrue
TrueTrueTrue

I cicli iterativi

Il ciclo while

Il ciclo while esegue un blocco di istruzioni fintanto che l’espressione tra parentesi resta true.

while (condizione) {
    istruzione
}

Il ciclo do/while

Il ciclo do/while esegue un blocco di istruzioni fintanto che la condizione espressa tra parentesi é vera, ma, a differenza del precedente, é postcondizionato, vale a dirsi che la valutazione dell’espressione é successiva all’esecuzione del ciclo, ossia che il ciclo verrà eseguito almeno una volta.

do {
    istruzione
}
while (condizione);

Il ciclo for

Simile al ciclo while, viene utilizzato in sua alternativa per l’iterazione degli array. Si costituisce in una serie di espressioni indicate tra parentesi che danno luogo all’esecuzione del blocco di codice fintanto che queste sono true. Tipicamente vi é un contatore costituito da una variabile inizializzata ad un valore (tipicamente 0, o altro valore in caso si tratti di un countdown), un’espressione di confronto del valore raggiunto da questa variabile rispetto ad un valore di termine del ciclo, ed un espressione di incremento (o decremento, qualora si tratti di un countdown).

for (i = 0; i < array.lenght; i++) {
    istruzione; 
}

Le istruzioni break e continue

Qualora fosse necessario determinare l’uscita da un ciclo prima della sua fine naturale, l’utilizzo dell’istruzione break ne interrompe l’esecuzione. Se invece fosse necessaria la sola interruzione dell’esecuzione del ciclo corrente, ripartendo dalla successiva, allora la parola chiave continue, produce questo effetto.

Le Stringhe

In Java le stringhe non sono tipi primitivi ma appartengono alla classe String. Vengono memorizzate in un’area di memoria chiamata string pool. Se tue variabili di tipo stringa hanno il medesimo contenuto, puntano alla stessa identica area di memoria. Esistono due modi per creare una stringa, il primo é scrivendola direttamente nel codice, il secondo é attraverso il costruttore della classe String.

//Primo caso
String s = “Questa é una stringa”;

//Secondo caso
Stringa stringa = new String(“Questa é una stringa”);

L’utilizzo del secondo metodo comporta l’istanziatura di un nuovo oggetto a sé stante rispetto ad un eventuale ulteriore secondo oggetto istanziato attraverso la stessa modalità.

Concatenazione di stringhe

Le stringhe possono essere concatenate allo scopo di creare una nuova stringa contenente i dati delle due, che continuano il loro ciclo vitale a prescindere da ciò che accadrà alla nuova stringa. Per concatenare due stringhe viene utilizzato l’operatore “+”.

Confronto tra stringhe

Le stringhe possono essere confrontate per verificare se contengono la stessa sequenza di caratteri. Questo avviene attraverso il metodo equals(), che restituisce il valore booleano true in caso affermativo, o false in caso negativo.

NB: diversamente da quanto accade in altri linguaggi, in Java le stringhe non possono essere confrontate attraverso l’ausilio dell’operatore di confronto ==, perché le stringhe in Java sono oggetti non sono variabili.

Analisi di stringhe

I metodi per la manipolazione delle stringhe sono contenuti nella classe String. Tra questi, degni di nota, sono il metodo lenght(), che restituisce il numero di caratteri che la compongono, ed il metodo charAt() che restituisce il carattere presente ad una certa posizione.

Le sottostringhe

Il metodo substring() permette di estrarre una porzione di stringa (chiamata sottostringa), e di manipolarla. Accetta due argomenti: il primo é l’indice del primo elemento da estrarre, il secondo dell’ultimo elemento da estrarre.

String s = “Dott. Bianchi Roberto”;

String cognome = s.substring(6, 13);

System.out.println(“Il cognome é ”+ cognome);

Altri tipi di confronto

Il linguaggio Java mette a disposizione molte altre funzionalità che permettono di verificare se una stringa inizia con una determinata sottostringa, oppure di individuare la posizione di una determinata sottostringa. Per verificare se una stringa inizia con una determinata sottostringa è possibile utilizzare il metodo startsWith(), così come il metodo endsWith() sarà in grado di dirci come termina. Il metodo indexOf() restituisce invece l’indice di una sottostringa, o -1 in caso che questa non sia presente.

String s = “Dott. Bianchi Roberto”;

int posNome = s.indexof(“Roberto”);

System.out.println(“Il nome inizia dalla posizione:” +posNome);

Buffer stringhe

Le stringhe in Java sono oggetti immutabili per cui ogni operazione svolta su di esse comporta la creazione di una nuova stringa, fenomeno per cui può determinarsi l’allocamento di una grande quantità di memoria. Una valida opzione é l’utilizzo degli array di caratteri che, in ragione della complessità di manipolazione, possono beneficiare di una libreria ad essi dedicata, la classe StringBuffer. Questa particolare classe si avvale di 3 tipologie di metodi:

  • il metodo append();
  • il metodo insert();
  • altri metodi di ricerca, inversione, estrazione e sostituzione.

Esempio di utilizzo dello StringBuffer:

//Creazione oggetto di classe StringBuffer
StringBuffer ciao = new StringBuffer(“Ciao”);

//Accodamento di una ulteriore stringa
ciao.append(“Pippo”);

//Inseriamo uno spazio
ciao.insert(4, “ “); //Out CiaoPippo
MetodoDescrizione
char charAt(int)Restituisce il carattere alla data posizione
stringBuffer delete (int, int)Elimina i caratteri nell’intervallo
int indexOf(String)Restituisce la posizione della sottostringa indicata
int lenght()Restituisce il numero di caratteri
stringBuffer replace(int, int, String)Sostituisce la sottostringa
stringBuffer reverse()inverte l’ordine dei caratteri
string substring(int, int)Restituisce la sottostringa individuata

Operazioni sui dati

Operatori aritmetici

Le principali operazioni avvengono mediante operatori matematici tipici, come somma, sottrazione, moltiplicazione, divisione ed elevamento a potenza. il simbolo % (modulo) restituisce il resto di una divisione tra due operandi.

Le conversioni

Le operazioni sono possibili solo tra tipi di dato omogenei, per questo, laddove ne si esegua una tra tipi non omogenei, avviene una conversione promozione numerica detta “casting”, che promuove il tipo meno capiente nel medesimo tipo dell’altro operando. E’ bene notare che l’operazione inversa genera errore, per possibile perdita del dato.

int a = 4;
long l = a;
long l = 5L;
int a = l; //Errore di compilazione

Conversioni tra numeri e stringhe

Può rendersi necessaria la conversione di un numero contenuto in una stringa in un variabile numerica per operare su di essa come tale. Per fare ciò si utilizza la classe Double (presente nel package java.lang) ed il metodo parseTipovariabile(), che ha declinazioni per ciascun tipo di dato.

String stringa = “123,45”;
double numero = Double.parseDouble(numero);

Gli operatori logici

Per eseguire i confronti fra variabili si utilizzano i cd. “operatori di confronto”:

== Uguaglianza

!= Disuguaglianza

< Minore di

> Maggiore di

<= Minore o uguale

>= Maggiore o uguale

&& And

|| Or

Operatore ternario

L’operatore ternario è composto da tre operandi, e valuta il primo: se é true valuta il secondo, se é false, valuta il terzo.

espressione1 ? espressione2 : espressione3

Operatori di incremento e decremento

Gli operatori di incremento (++) e decremento (–) incrementano o decrementano una variabile in modo unitario. Posti dinnanzi alla variabile l’operazione prende il nome di preincremento, al contrario assume il nome di post incremento.

Operazioni sui bit

I dati sono memorizzati sempre in codice binario, ragione per cui, può in alcuni casi rivelarsi utile operare sui singoli bit che compongono il dato, per scopi di sviluppo molto specifici, in questo caso gli operatori sono i seguenti:

~ Negazione

& And

| Or

^ Or esclusivo

<< Scorrimento a sinistra

>> Scorrimento a destra

>>> Scorrimento a destra senza segno

Priorità tra gli operatori

In Java gli operatori osservano uno schema di priorità simile a quello della matematica, indi per cui le moltiplicazioni e le divisioni hanno la precedenza su addizioni e sottrazioni. L’aggiunta delle parentesi consentono di attribuire una maggiore priorità.

Variabili e tipi primitivi

Gli identificatori

Quando viene dichiarata una variabile, un metodo, un attributo o una classe bisogna definire un identificatore (un nome). A tal scopo si possono utilizzare i seguenti caratteri:

  • lettere dell’alfabeto
  • numeri
  • underscore _
  • simbolo del dollaro $ (deprecated)

Gli identificatori devono iniziare con una lettera e non possono contenere parole chiave che sono riservate nel linguaggio Java. E’ altresì importante ricordare che il linguaggio Java é case sensitive, cioè sensibile alle lettere maiuscole e minuscole.

Regole stilistiche

Per quanto non siano obbligatorie, sono caldamente consigliate regole stilistiche per la scrittura dei programmi, peraltro comuni alla maggior parte dei linguaggi, che mettano gli altri programmatori in condizione di comprendere meglio il nostro software. A questo scopo é bene ricordare che:

  • I nomi di classe sono maiuscoli (Automobile, Magazzino, Prodotto);
  • I metodi sono parole composte che contengono un verbo ed iniziano con una lettera minuscola (notazione a cammello) (inviaOrdine, cancellaTutto);
  • I nomi degli attributi sono simili ai metodi ma senza verbo (notazione a Cammello) (numeroProdotti, codiceArticolo);
  • Le costanti sono scritte tutto in maiuscolo (es: PI_GRECO);
  • Le variabili vengono scritte in minuscolo.

Le variabili

Una variabile é uno spazio di memoria (RAM) che viene riservato per contenere dei dati. Si compone di un tipo e di un identificativo. Per poter utilizzare una variabile é necessario dichiararla, operazione che permette al compilatore di riservare ad essa la corrispondente quantità di memoria definita dal tipo.

Inizializzazione delle variabili

Una volta dichiarata una variabile é necessario inizializzarla utilizzando l’operatore di assegnamento (=), in caso contrario avremo un errore nella compilazione. E’ possibile inizializzare una variabile contestualmente alla sua dichiarazione:

int i = 10;

Le variabili contenute nelle classi vengono automaticamente inizializzate a zero.

Le costanti

La dichiarazione delle costanti avviene allo stesso modo delle variabili ma vi si aggiunge la parola chiave final anteponendola al tipo. Esempio:

final double PI_GRECO = 3,14;

Tipizzazione dei dati

Contestualmente alla dichiarazione di una variabile o di una costante, é necessario dichiararne il tipo affinché il compilatore riservi un’adeguata quantità di memoria alla stessa. I tipi di dato standard in Java sono:

booleantrue/false
charCarattere
byte, short, int, longValori Interi
floatValori reali a precisione singola
doubleValori reali a precisione doppia

I valori interi possono essere positivi o negativi secondo lo standard IEEE754 e possono contenere zeri positivi o zeri negativi, infiniti, il valore NaN (Not-a-Number). Esiste anche un particolare tipo che viene utilizzato per indicare nessun tipo che é utilizzato in modo particolare nella dichiarazione dei motodi, il void, stante ad indicare un tipo non anacora assegnato. Viene utilizzato nei metodi che non restituiscono nessun valore, perché il linguaggio Java richiede che venga esplicitato il tipo in ogni caso.

OOP – La programmazione orientata agli oggetti

Java é un linguaggio di programmazione orientata agli oggetti, vale a dirsi che la soluzione al problema concreto passa attraverso la definizione per astratto di una serie di oggetti che cooperando, raggiungono il risultato computazionale previsto.

L’oggetto

Secondo la definizione di Booch un oggetto é un elemento caratterizzato da uno stato, un comportamento e un’identità. Per stato si intende l’insieme di attributi e proprietà, per comportamento l’insieme dei suoi metodi. Con identità si intende il tipo e l’identificatore.

La classe

Con la parola classe si intende lo schema di costruzione degli oggetti. Viene definita attraverso la parola chiave class e laddove non altrimenti specificato essa gode di visibilità di default package, cioé e visibile da tutte le classi appartenenti allo stesso package. La sintassi di costruzione di una classe é la seguente:

class Identificatore {
    elenco attributi
    elenco metodi
}

Gli attributi di una classe possono essere dati primitivi o oggetti, e devono essere scritti prima dei metodi.

Le istanze di una classe

Con il termine istanziare si intende l’operazione attraverso la quale viene creato un oggetto a partire da una classe. Essa avviene attraverso l’operatore new che carica dinamicamente in memoria operando su di essa per riferimento (by reference).

La sintassi per istanziare un oggetto (NomeOggetto) di tipo NomeClasse é la seguente:

NomeClasse NomeOggetto = new nomeClasse( );

A partire da questo momento l’oggetto sarà una entità distinta tanto dalla classe da cui é stato costruito quanto dagli altri oggetti dello stesso tipo.

I metodi

I metodi svolgono una funzione analoga alle funzioni e possono dunque essere invocati allo scopo di determinare una variazione degli attributi dell’oggetto, potendo anche restituire un valore. Esso é costituito da:

  • una visibilità
  • un tipo
  • un nome ed un elenco di parametri (o argomenti) accettati
  • un blocco di istruzioni chiamati anche implementazione

Qualora il metodo non restituisca alcunché viene definito come void, mentre qualora emetta un risultato visibile all’esterno del metodo, la parola chiave return consente di proiettare all’esterno questo risultato, terminando l’esecuzione del metodo. La sintassi é la seguente:

scope tipo identificatore ( argomenti ) {
    istruzioni;
}

Il metodo main

Per poter essere eseguibile un programma deve sempre contenere una classe, detta classe principale, di tipo public, contenente un metodo main.

public class MioSoftware {
    public static void main( String [ ] args ){
        /.../
    }
}

Invocazione dei metodi e passaggio di argomenti

Il metodo può essere invocato solo qualora l’oggetto definito dalla classe sia stato istanziato, cioé deve essere esistente a livello logico. Una volta che l’oggetto é stato istanziato é possibile invocarne i metodi attraverso la seguente sintassi:

NomeOggetto.nomeMetodo(argomenti);

Gli argomenti passati possono essere sia tipi primitivi che oggetti, con la differenza che nel primo caso sono passati per valore (by value) nel secondo per riferimento (by reference), ne viene passato cioé un puntatore all’area di memoria che contiene l’oggetto.

La classe System

La classe System è un contenitore di diverse funzioni essenziali del linguaggio Java come la virtuale machine, le funzionalità di sicurezza e del sistema. Fornisce inoltre l’accesso alle funzioni di I/O del sistema:

  • IN: gli input (i dati che entrano)
  • OUT: gli output ( i dati che escono)
  • ERR: il canale dedicato agli errori

I Packages

I packages sono pacchetti di classi già pronte all’uso, contenute all’interno di un file con estensione .jar. Si tratta di librerie a disposizione del programmatore che possono essere incluse nel codice attraverso la parola chiave import. Il package java.lang é il più importante di Java e racchiude le classi fondamentali del linguaggio. Questo però, a differenza dei normali packages viene incluso anche senza che venga specificato.

//Utilizzare l’asterisco * per includere tutte le classi contenute in uno specifico pacchetto

import java.awt.*;

//Oppure specificare solo le classi di interesse, per risparmiare spazio e memoria

import java.awt.Frame;

Visualizzazione dei dati

L’output dei dati a video é disponibile attraverso le funzionalità presenti nella libreria di Java. Lo standard output é accessibile attraverso l’oggetto System.out

La classe System raccoglie alcune funzionalità di sistema, uno dei suoi attributi é out, un oggetto che contiene diversi metodi per produrre output.

Lettura dei dati

La lettura dell’input avviene mediante la classe InputStream contenuta in System. Il metodo readLine() della classe BufferedReader legge sequenze di caratteri che restituisce sotto forma di stringhe. La classe BufferedReader deve essere inizializzata fornendo un input, ad esempio System.in di classe InputStream. Si può istanziare un oggetto di classe BufferedStreamReader in questo modo:

InputStreamReader leggi = new InputStreamReader(System.in);

A questo punto, potremo istanziare BufferedReader con l’oggetto leggi, che ci metterà a disposizione il metodono readLine() in esso contenuto, come nel seguente caso:

BufferedReader input = new BufferedReader(leggi);

Sarà quindi possibile leggere una linea di testo istanziando il metodo readLine() dell’oggetto input:

input.readLine();

I commenti

Come in tutti i linguaggi di programmazione, anche in Java i commenti osservano la seguente sintassi:

  • Doppio slash // ad inizio commento
  • Slash e asterisco /* ad inizio commmento e */ a fine commento
  • Slash e doppio asterisco /** e **/ a fine commmento

Relativamente alla terza categoria ci sono i commenti Javadoc che permettono di scrivere la documentazione delle classi affinché possa essere letta da un browser web. La documentazione può essere generata con il comando javadoc seguito dal nome del file contenente la classe di cui si vuole generare la documentazione e supporta l’inserimento di informazioni specifiche come l’autore e la versione del software, come nel seguente esempio:

//**
* Nome classe

* Descrizione classe

*

* @author Luca Scandroglio

* @version 0.1

*/