Introduzione a Php 8

Php è il linguaggio per diffuso nel mondo delle applicazioni web. Si stima che circa l’83% dei siti web mondiali utilizzino questo linguaggio (dati 2018) per cui si evince come di fondamentale importanza acquisirne la padronanza per chiunque si accinga alla professione di web developer.

Nasce nel 1995 per opera di Rasmus Lerdorf che lo sviluppava come linguaggio destinato all’esecuzione lato server per la realizzazione di siti web personali (PHPPersonal Home Page) e si é nel tempo evoluto in seno alla propria community che ne ha sviluppato sempre ulteriori funzionalità. Il merito di questo successo risiede in alcuni fattori chiave che hanno contributo a rendere PHP un linguaggio di largo utilizzo e che possono così essere riassunti:

  • é un linguaggio open source disponibile gratuitamente e sviluppato in virtù di ciò da un ampia community di sviluppatori, a cui chiunque può contribuire (https://www.php.net)
  • é semplice e di alto livello, per cui semplifica di molto il lavoro dello sviluppatore che non dovrà preoccuparsi degli aspetti di basso livello perché questi sono interpretati e tradotti in linguaggio macchina
  • è perfettamente complemetnare ai “linguaggi” di markup come xml e html, nonché perfettamente interfacciabile ai database

Novità di Php 8

PHP ha, nel corso del tempo, conosciuto susseguenti evoluzioni che ne hanno fatto un linguaggio estremamente versatile, sopratutto a partire dalla versione 7, che ha introdotto notevoli miglioramenti, sia prestazionali che in termini di sicurezza. A cominciare dalla gestione degli errori che non é più “bloccante” ma che consente la gestione degli stessi, l’introduzione dell’operatore spaceship <=> e la funzione spread semplificano le operazioni sugli array, gli operatori “freccia” che semplificano l’uso delle funzioni anonime ed ulteriori introduzioni nelle dichiarazioni di tipo nelle proprietà delle classi, migliorando anche complessivamente la gestione delle stringhe e dei numeri. Dalla versione 7.4 viene introdotta la funzione di pre-caricamento delle classi e delle funzioni con un notevole incremento di prestazioni in contesti di forte stress operativo. Con l’introduzione di PHP 8 il codice viene compilato con il compilatore JIT (Just-In-Time) e trasformato in opcode, che si trasforma in linguaggio macchina eseguibile migliorando ulteriormente i tempi di esecuzione del codice, aprendo a scenari futuri di sviluppo non prettamente relegati alle applicazioni web, ma anche ai videogiochi, ad esempio.

Il futuro di PHP é ancora tutto da scrivere.

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.