Metodologie di Programmazione: Lezione 2

Riccardo Silvestri

Array ed eccezioni

Similmente a molti altri linguaggi, Java ha gli array per rappresentare sequenze di elementi. Gli array sono essenzialmente ripresi da C/C++ anche se in Java, grazie alla gestione automatica della memoria, non c'è bisogno di preoccuparsi della loro allocazione/disallocazione. Essendo Java un linguaggio staticamente tipato, gli elementi di un array devono essere tutti dello stesso tipo, a differenza ad esempio delle liste di Python. Vedremo più avanti che questo vincolo può essere rilassato perdendo però il controllo statico sui tipi, del tutto o in parte.

In questa lezione c'è anche un primo assaggio di come in Java si gestiscono le situazioni di errore al runtime. I meccanismi di base delle exceptions sono ripresi dal C++ e sono molto simili a quelli adottati anche da altri linguaggi come ad esempio Python.

Array

Un array è un tipo speciale di oggetto che contiene zero o più componenti che sono variabili tutte dello stesso tipo. Il tipo delle componenti può essere un qualsiasi tipo primitivo o riferimento. Il numero delle componenti (la lunghezza) è fissato quando l'array è creato e non può essere cambiato. La sintassi per dichiarare una variabile di tipo "array di T", dove T è un qualsiasi tipo, è la seguente:

T[] v;

Il tipo della variabile v è quindi denotato da T[]. Con la precedente dichiarazione, però, si è semplicemente dichiarata una variabile v di tipo "array di T" ma non è stato creato nessun array. Per creare un array di n elementi si usa l'operatore new con la sintassi:

T[] v = new T[n];

dove n può essere una costante numerica, una variabile o più in generale una qualsiasi espressione a valore intero. Le componenti di un array sono numerate a partire da 0 e la sintassi per accedere ad una componente dell'array è anch'essa uguale a quella del C/C++: la componente di indice i dell'array v è v[i]. Ecco alcuni esempi:

int[] interi = new int[10];         // Array di 10 int
String[] stringhe = new String[5];  // Array di 5 riferimenti a oggetti String

Gli array appena creati hanno le componenti inizializzate con i relativi valori di default. Così, le componenti dell'array interi hanno valore 0 e le componenti dell'array stringhe hanno valore null. Si possono anche creare array di lunghezza zero, ad esempio new int[0].

Come in C/C++ è possibile creare un array e inizializzarne le componenti in un unica espressione. Inoltre, anche la sintassi è essenzialmente la stessa elencando i valori delle componenti dell'array tra parentesi graffe:

int[] primi = {2, 3, 5, 7, 11};    // Crea e inizializza un array di 5 int
String[] risposte = {"SI", "NO"};  // Crea e inizializza un array di 2 stringhe

I tipi array (array types) fanno parte della famiglia dei tipi riferimento e ogni array è un oggetto. Questo significa, ad esempio, che la variabile sopra definita stringhe ha come valore il riferimento ad un oggetto array (che a sua volta consiste di 5 componenti ognuna delle quali è un riferimento a un oggetto della classe String). La lunghezza di un array è disponibile tramite un campo (costante) di nome length. La lunghezza di un array, però, non fa parte del tipo array. Questo ha due importanti conseguenze. La prima è che non è possibile dichiarare un metodo che accetta come argomento un array la cui lunghezza è fissata (nei parametri del metodo). Se un metodo accetta tra i suoi parametri, ad esempio, un array di int (dichiarato int[]) dovrà accettare array di int di qualsiasi lunghezza. Questo non è un problema grazie alla disponibilità del campo length. Ecco l'esempio di un metodo che prende in input un array di String e lo stampa:

void printStrings(String[] ss) {
    for (int i = 0 ; i < ss.length ; i++) // ss.length è la lunghezza dell'array
        System.out.println(ss[i]);
}

La seconda conseguenza riguarda gli array di array (o array multidimensionali).

Array multidimensionali

Siccome, come abbiamo già detto, il tipo delle componenti di un array può essere un qualsiasi tipo riferimento e i tipi array sono particolari tipi riferimento, è chiaro che è possibile definire array di array. Ovvero, array il cui tipo delle componenti è a sua volta un tipo array. È quindi possibile, ad esempio, dichiarare le seguenti variabili:

float[][] matrix;       // Array con componenti di tipo float[] (array di float)
int[][][] cube;         // Array con componenti di tipo int[][]
String[][] strMatrix;   /* Array con componenti di tipo String[] (cioè
                           array di riferimenti a oggetti String) */

Ovviamente, è anche possibile creare array di array:

matrix = new float[5][10];       // Matrice 5x10 di float
cube = new int[3][3][3];         // Cubo 3x3x3 di int
strMatrix = new String[10][8];   // Matrice 10x8 di stringhe

Non è necessario specificare tutte le dimensioni, l'unica che è necessario specificare è la prima (da sinistra). Le altre possono essere specificate (e create) dopo. Ad esempio,

matrix = new float[5][10]

è equivalente a:

matrix = new float[5][];    // Crea un array di 5 riferimenti ad array di float
for (int i = 0 ; i < matrix.length ; i++) 
    matrix[i] = new float[10];  /* Crea un array di 10 float e ne assegna il 
                                   riferimento alla i-esima componente 
                                   dell'array matrix */

Si osservi però che le dimensioni non specificate possono essere solamente quelle più a destra:

cube = new int[4][][];           // OK
cube = new int[4][4][];          // OK
cube = new int[4][][4];          // Errore (in compilazione)
cube = new int[][4][4];          // Errore (in compilazione)
cube = new int[][][4];           // Errore (in compilazione)
cube = new int[][4][];           // Errore (in compilazione)

A questo punto arriviamo alla seconda conseguenza del fatto che la lunghezza non fa parte del tipo array: le lunghezze degli array componenti non devono essere necessariamente uguali. Così, è possibile creare, ad esempio, una matrice triangolare:

triang = new float[5][];    // Crea un array di 5 riferimenti ad array di float
for (int i = 0 ; i < triang.length ; i++)
    triang[i] = new float[i + 1];     // Crea un array di lunghezza i + 1

Si osservi che il tipo della variabile triang[i] è riferimento ad array di float (cioè float[]) e che, dopo l'esecuzione di questo frammento di codice, risulterà triang[0].length = 1, triang[1].length = 2, ... triang[4].length = 5. Il seguente esempio mostra un metodo che stampa una matrice le cui dimensioni possono essere qualsiasi (qualsiasi numero di righe e ogni riga di lunghezza qualsiasi):

void printMatrix(float[][] matrix) {
    for (int r = 0 ; r < matrix.length ; r++) {
        for (int c = 0 ; c < matrix[r].length ; c++) 
            System.out.printf("%8.2f ", matrix[r][c]);
        System.out.println();
    }
}

Gli oggetti di tipo PrintStream, come lo è System.out, supportano anche un metodo chiamato printf() che è del tutto simile alla omonima funzione per la stampa formattata della libreria standard del C.

Anche per gli array multidimensionali si possono usare gli inizializzatori:

int[][] latin = {{1, 2, 3}, 
                 {2, 3, 1},
                 {3, 1, 2}};   // Crea e inizializza una matrice 3x3 di int
String[][] answers = {{"SI", "NO"},    // Crea e iniziativa una 
                      {"NO", "SI"}};   // matrice 2x2 di stringhe
int[][] tria = {{1},
                {1, 2},
                {1, 2, 3},       // Crea e inizializza una
                {1, 2, 3, 4}};   // matrice triangolare di int
int[][] mat = {new int[3], new int[5]};  /* Crea e inizializza un array di due
                                            array di int */

Come si vede dall'ultimo esempio le espressioni negli inizializzatori non devono essere necessariamente delle costanti ma possono essere espressioni qualsiasi purché il loro valore sia del tipo giusto.

Enhanced for

La sintassi di questa forma di for, detto anche for-each, è la seguente:

for (Type v : iterExpr)
    istruzione o blocco di istruzioni

Dove iterExpr è una qualsiasi espressione il cui valore è o un array o un oggetto che implementa l'interfaccia Iterable. Perciò il valore di iterExpr è una collezione di elementi su cui iterare. Come vedremo più avanti il for-each si può usare anche per altre collezioni di elementi oltre agli array. Il tipo Type della variabile v deve essere compatibile con quello degli elementi della collezione1. L'effetto del for-each è che ad ogni iterazione la variabile v contiene il prossimo elemento della collezione (o array), e continua così fino a che tutti gli elementi sono stati considerati. Ecco un semplice esempio, un metodo che ritorna la media dei valori di un array di interi:

double average(int[] values) {
    double sum = 0.0;
    for (int val : values) 
        sum += val;
    return sum/values.length;
}

Qui il for-each

for (int val : values) 
    sum += val;

è equivalente a

for (int i = 0 ; i < values.length ; i++)
    sum += values[i];

In tutti i casi in cui non è necessario accedere agli indici degli elementi, il for-each risulta più semplice e leggibile del for tradizionale.

Arrays

Nel package java.util la classe Arrays ha molti metodi (statici) di utilità per gli array. Tra quelli più utili ci sono i metodi Arrays.copyOf che creano una copia di un array con eventualmente una lunghezza differente, i metodi Arrays.sort che ordinano l'array di input e i metodi Arrays.binarySearch che effettuano una ricerca binaria di un valore in un array ordinato. Vediamoli in azione nel seguente metodo che possiamo aggiungere alla nostra classe Utils:

import java.util.Arrays;

. . .

/** Ritorna un array che contiene gli interi distinti e ordinati letti da uno
 * Scanner.
 * @param scan  lo Scanner da cui leggere gli interi
 * @return un array che contiene gli interi distinti e ordinati letti dallo
 * Scanner */
public static int[] readDistinct(Scanner scan) {
    int[] ints = new int[0];
    while (scan.hasNextInt()) {      // Finché c'è un prossimo intero da leggere
        int n = scan.nextInt();      // Leggi il prossimo intero
        if (Arrays.binarySearch(ints, n) < 0) {   // Se non è già presente
            ints = Arrays.copyOf(ints, ints.length + 1);  // Estendi l'array,
            ints[ints.length - 1] = n;                    // aggiungi il nuovo
            Arrays.sort(ints);                            // e ordina l'array
        }
    }
    return ints;
}

Il metodo Arrays.binarySearch che prende in input un array e un valore da ricercare, ritorna l'indice in cui si trova il valore, se presente, e altrimenti ritorna -k - 1 dove k è l'indice in cui si potrebbe inserire il valore mantenendo l'array ordinato. Si noti che così è garantito che il valore cercato è presente solo se Arrays.binarySearch ritorna un indice non negativo. Il metodo Arrays.copyOf, nella versione usata, prende in input un array e una nuova lunghezza e ritorna una copia (cioè un nuovo array) dell'array di input fino alla nuova lunghezza. Se la nuova lunghezza è maggiore di quella originale i nuovi elementi sono riempiti con il valore di default. Il metodo Arrays.sort ordina l'array in senso crescente.

I metodi Arrays.toString ritornano una rappresentazione tramite stringa dell'array di input. Questi sono utili perché il metodo toString degli oggetti array (che è il modo canonico di ottenere una rappresentazione tramite stringa di un oggetto) ritorna solamente una rappresentazione del riferimento all'oggetto (ossia l'indirizzo dell'array). Qui lo usiamo per testare il metodo readDistinct:

private static void test_readDistinct() {
    Scanner input = new Scanner(System.in);
    out.print("Test metodo readDistinct(), digita una serie di interi,\n"+
            "per terminare digita un qualsiasi carattere non numerico: ");
    int [] ints = readDistinct(input);
    out.println(Arrays.toString(ints));
}

Infine i metodi Arrays.equals determinano se un array è uguale ad un altro, cioè sono dello stesso tipo, hanno uguale lunghezza e hanno uguali elementi. Sono utili perché il metodo equals degli oggetti array determina solamente se i due array sono lo stesso oggetto. Ad esempio,

int[] arrA = {1,2,3}, arrB = {1,2,3};
out.println("arrA.equals(arrB) -> "+arrA.equals(arrB));
out.println("Arrays.equals(arrA, arrB) -> "+ Arrays.equals(arrA, arrB));

stampa

arrA.equals(arrB) -> false
Arrays.equals(arrA, arrB) -> true

Argomenti dalla linea di comando

Adesso che conosciamo gli array sappiamo cosa significa il parametro di input del metodo main. È un array di stringhe (String[]). Ognuna delle stringhe dell'array contiene un argomento della linea di comando. Il seguente esempio mostra un programma che interpreta come interi gli argomenti passati nella linea di comando e ne stampa in output la somma:

public class Add {
    public static void main(String[] args) {
        int sum = 0;
        for (String a : args)
            sum += Integer.parseInt(a);
        System.out.println("La somma è "+sum);
    }
}

Per convertire le stringhe in numeri interi si è usato il metodo (statico) int parseInt(String s) della classe Integer. Se il programma è eseguito con

java Add 23 -12 56

allora l'array args avrà tre componenti

args[0]: "23"    args[1]: "-12"    args[2]: "56"

e il programma stamperà

La somma è 67

Numero variabile di parametri

In Java è semplice definire metodi che possono essere invocati con un numero variabile di parametri. Ad esempio, un metodo per calcolare il massimo di un numero variabile di interi può essere così definito:

int max(int first, int...rest) {
    int max = first;
    for (int v : rest)
        if (v > max) max = v;
    return max;
}

Le seguenti sono possibili invocazioni per il metodo max():

max(2)
max(3, 9, 6)
max(2, 8, 1, 10)

La sintassi int...rest sta a significare zero o più parametri di tipo int. Dal punto di vista dell'implementazione, è come se il metodo fosse stato dichiarato int max(int first, int[] rest), nel senso che il tipo dell'argomento rest è int[].

Ovviamente, non c'è nulla di speciale nel tipo int, è possibile definire metodi che prendono un numero variabile di parametri di tipo qualsiasi. Purché quelli variabili sono tutti dello stesso tipo e sono quelli più a destra. Più avanti vedremo come è possibile definire metodi che accettano un numero variabile di parametri di tipi non necessariamente uguali.

Errori ed eccezioni (prima parte)

Durante l'esecuzione di un programma possono verificarsi degli errori. Per alcuni di questi il programma non può fare nulla se non "accorgersi" dell'errore e terminare in modo grazioso (ad esempio, se la memoria non è sufficiente), per altri può in qualche modo cercare di riparare all'errore (ad esempio, se si è tentato di leggere un file inesistente, il programma può chiedere all'utente un nuovo nome di file), per altri ancora, specialmente quelli causati da errori di programmazione, si può solamente prenderne atto e cercare di correggere il programma. Il linguaggio Java offre dei meccanismi (ripresi dal C++) per la gestione degli errori che avvengono durante l'esecuzione. Questi errori sono chiamati eccezioni (exceptions).

Quando si verifica un errore durante l'esecuzione di un programma, ovvero si verifica un'eccezione, la macchina virtuale Java lancia (throws) una opportuna eccezione. Un'eccezione è un oggetto il cui tipo è relativo alla natura dell'errore. Ecco subito due semplici esempi:

public class Test {
    public static void main(String[] args) {
        String str = null;
        int n = str.length();
    }
}

eseguendo il programma la JVM lancerà la seguente eccezione

Exception in thread "main" java.lang.NullPointerException
    at Test.main(Test.java:4)

mentre se si esegue quest'altro programma

public class Test {
    public static void main(String[] args) {
        int n = 1000, d = 0;
        int frazione = n/d;
    }
}

la JVM lancerà un'altra eccezione

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at Test.main(Test.java:4)

Nel primo caso l'errore è dovuto al tentativo di accedere ad un oggetto inesistente (la variabile str ha valore null) e questo produce una eccezione di tipo NullPointerException. Nel secondo caso l'errore è dovuto al tentativo di dividere per zero e questo produce un'eccezione di tipo ArithmeticException. Entrambe le eccezioni, insieme a molte altre, appartengono al package java.lang. E come tutte le eccezioni sono delle classi. Così NullPointerException è il nome di una classe e quando si tenta di accedere ad un oggetto tramite un riferimento null la JVM istanzia un oggetto di tipo NullPointerException e lo "lancia". Se nessuna parte del programma lo "cattura" la JVM termina l'esecuzione del programma riportando l'eccezione. Il meccanismo per la gestione degli errori o eccezioni offerto da Java si basa proprio sulla possibilità di catturare (catch) le eccezioni che possono venir lanciate durante l'esecuzione.

Catturare eccezioni

Per catturare le eccezioni che possono essere lanciate durante l'esecuzione di una porzione del codice si usa la seguente sintassi:

try {
    una o più istruzioni che si vogliono monitorare
} catch (ExceptionType1 ex) {    
    // Eseguito solo se si verifica un'eccezione di tipo ExceptionType1
    gestione dell'eccezione ex di tipo ExceptionType1
} catch (ExceptionType2 ex) {
    // Eseguito solo se si verifica un'eccezione di tipo ExceptionType2
    gestione dell'eccezione ex di tipo ExceptionType2
} 
 . . .
} catch (ExceptionTypeN ex) {
    // Eseguito solo se si verifica un'eccezione di tipo ExceptionTypeN
    gestione dell'eccezione ex di tipo ExceptionTypeN
} finally {  // Eseguito anche se si verifica un'eccezione, catturata o meno
    codice che è eseguito in ogni caso
}

Volendo si possono anche gestire eccezioni di tipi diversi nello stesso blocco catch:

try {
    una o più istruzioni che si vogliono monitorare
} catch (ExceptionType1 | ExceptionType2 | . . . ExceptionTypeN ex) {    
    // Eseguito se si verifica un'eccezione dei tipi sopra
    gestione dell'eccezione
} 

Vediamo un esempio di programma che legge due interi, ne calcola il quoziente e poi legge una stringa e un intero i e stampa il carattere in posizione i della stringa.

import java.util.*;    // Sia per Scanner che per InputMismatchException
import java.lang.System.out;

public class Test {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        try {
            out.print("Inserire due interi: ");
            int n = input.nextInt();    // Può lanciare InputMismatchException
            int m = input.nextInt();    // Può lanciare InputMismatchException
            int quoziente = n/m;        // Può lanciare ArithmeticException
            out.println(n+" / "+m+"  fa  "+quoziente);
            out.print("Inserire una parola e una posizione: ");
            String p = input.next();
            int i = input.nextInt(); // Può lanciare InputMismatchException
            char c = p.charAt(i-1);  // Può lanciare StringIndexOutOfBoundsException
            out.println("Il carattere in pos. "+i+" di \""+p+"\" è "+c);
        } catch (InputMismatchException ex) {
            out.print("Eccezione InputMismatchException: ");
            out.println(ex.getMessage());
        } catch (ArithmeticException ex) {
            out.print("Eccezione ArithmeticException: ");
            out.println(ex.getMessage());
        } finally {
            out.println("Questa stampa viene sempre eseguita");
        }
        out.println("Il programma è terminato normalmente");    
    }
}

Un'eccezione di tipo InputMismatchException (appartenente al package java.util) è lanciata dai metodi (nextInt, nextDouble, ecc.) di oggetti di tipo Scanner quando ciò che è letto in input non corrisponde al tipo atteso. Il metodo getMessage() è uno dei metodi comuni a tutti i tipi di eccezione e ritorna una stringa contenente una descrizione dettagliata dell'eccezione (può anche ritornare null). Vediamo ora alcuni esempi di esecuzione del programma che mostrano cosa accade quando si verificano vari tipi di eccezione. Iniziamo con un esempio in cui non si verificano errori:

Inserire due interi: 12 3
12 / 3  fa  4
Inserire una parola e una posizione: Albero 3
Il carattere in pos. 3 di "Albero" è b
Questa stampa viene sempre eseguita
Il programma è terminato normalmente

Il codice nelle catch non è stato eseguito perchè nessun errore si è verificato mentre il codice di finally è eseguito comunque. Nel seguente esempio si verifica un errore dovuto alla divisione per zero:

Inserire due interi: 12 0
Eccezione ArithmeticException: / by zero
Questa stampa viene sempre eseguita
Il programma è terminato normalmente

Non appena si verifica un'eccezione nel blocco di un try il controllo passa al primo blocco di una catch, se esiste, che cattura l'eccezione. Poi se esiste un blocco finally questo viene comunque eseguito2. Se l'eccezione è stata catturata da un'opportuna catch, allora il controllo passa alla prossima istruzione dopo il try-catch, altrimenti l'esecuzione del metodo termina immediatamente. Il prossimo esempio mostra il verificarsi di un errore dovuto ad un input errato:

Inserire due interi: 12 tre
Eccezione InputMismatchException: null
Questa stampa viene sempre eseguita
Il programma è terminato normalmente

Ora invece consideriamo un esempio in cui si verifica un'eccezione che non è catturata:

Inserire due interi: 12 3
12 / 3  fa  4
Inserisci una parola e una posizione: Albero 7
Questa stampa viene sempre eseguita
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: 
                           String index out of range: 6
        at java.lang.String.charAt(String.java:558)
        at Test.main(Test.java:16)

In questo caso si è verificata un'eccezione StringIndexOutOfBoundsException dovuta al tentativo di accedere a un carattere inesistente di una stringa. Si noti che il blocco di finally è stato comunque eseguito mentre le istruzione successive del programma non sono state eseguite. Un'eccezione non catturata fa terminare l'esecuzione del metodo e si propaga al metodo chiamante che ha l'opportunità di catturarla, se non lo fa, l'eccezione fa terminare anche quest'ultimo e si propaga al metodo superiore, fino a quando qualche metodo nello stack la cattura o arriva al main che se anch'esso non la cattura, provoca la terminazione del programma.

Quello che abbiamo qui spiegato non esaurisce il tema delle eccezioni. Quando avremo introdotto altre caratteristiche di Java potremo approfondire anche le eccezioni.

Esercizi

[ErrArray]    Trovare e spiegare i due errori contenuti nel seguente programma:

public class Test {
    public static void main(String[] args) {
        String[][] mstr = new String[10][];
        for (int i = 0 ; i < mstr.length ; i++)
            mstr[i][0] = "A"; 
        mstr[0] = new String[5];
        int len = mstr[0][0].length();
    }
}

[ErrArray?]    Il seguente programma contiene errori?

public class Test {
    public static void main(String[] args) {
        String[][] m = new String[4][4];
        m[0][0] = "A";
        m[1] = new String[10];
        m[1][7] = "B";
    }
}

[Duplicati]    Scrivere un programma che legge un intero n e poi legge una sequenza di n interi e se la sequenza contiene dei valori ripetuti stampa tutti i valori che si ripetono almeno due volte, altrimenti stampa "non ci sono duplicati".

[OrdinaCaratteri]    Scrivere un programma che legge una stringa e stampa la sequenza ordinata dei caratteri della stringa. Ad esempio, se la stringa è "io programmo in Java" allora il programma stampa

Jaaagiimmnoooprrv

Modificare il programma in modo tale che possa leggere la stringa direttamente dalla linea di comando.

[NonSoloVocali]    Scrivere un programma che legge una linea di testo e per ogni carattere alfabetico, maiuscolo e minuscolo, stampa il numero di volte che appare nella linea di testo. Ad esempio, se la linea di testo è "Contare Caratteri Alfabetici" allora il programma stampa:

a: 4        A: 1
b: 1        B: 0
c: 1        C: 2
d: 0        D: 0
.           .
.           .
.           .
z: 0        Z: 0

Suggerimento: Il metodo indexOf degli oggetti String ritorna l'indice della prima occorrenza del carattere di input nella stringa, -1 se non è presente.

[NStringheVerticali]    Scrivere un programma che legge un intero positivo n, poi legge n stringhe e infine stampa le n stringhe in verticale come nell'esercizio [StringheVerticali]. Modificare il programma in modo che possa leggere le stringhe direttamente dalla linea di comando.

[GraficoABarre]    Scrivere un programma che legge un intero n e poi legge una sequenza di n interi positivi e stampa un grafico a barre della sequenza. Ad esempio, se la sequenza è 2, 5, 1, 3, 7, 5, 4 allora il programma stampa:

    *
    *
 *  **
 *  ***
 * ****
** ****
*******

Suggerimento: usare una matrice di caratteri di dimensione opportuna.

[LettereCifre]    Scrivere un programma che esegue la trasformazione inversa di quella dell'esercizio [CifreLettere]. Letta una linea di testo, se questa è composta di parole rappresentanti numeri da 1 a 9, stampa il numero corrispondente. Ad esempio, se legge "due uno due sette" allora stampa 2127.

Suggerimento: il metodo split degli oggetti String permette di dividere una stringa in parole.

[QuadratiMagici]    Un quadrato magico è una disposizione di numeri interi distinti in una tabella quadrata tale che la somma dei numeri presenti in ogni riga, in ogni colonna e in entrambe le diagonali dia sempre lo stesso numero. Ad esempio, il seguente è un quadrato magico di ordine 3:

8  1  6
3  5  7
4  9  2

Scrivere un programma che letto un intero dispari n stampa un quadrato magico di ordine n. I quadrati magici di ordine dispari possono essere costruiti tramite il semplice algoritmo descritto in Quadrato_magico.

[NumeriCasuali]    Scrivere un programma che legge due interi n e m, poi genera m interi "casuali" nell'intervallo [1, n] (usando il metodo Math.random()) e infine stampa le frequenze e gli scarti percentuali rispetto alla frequenza media. Ad esempio, se n = 12 e m = 10000 il programma potrebbe stampare:

Frequenza media: 833.33%
Valore    Frequenza       Scarto percentuale            
1         829             0.005%
2         813             0.024%
3         859             0.031%
4         802             0.038%
5         806             0.033%
6         843             0.012%
7         846             0.015%
8         824             0.011%
9         824             0.011%
10        842             0.010%
11        869             0.043%
12        843             0.012%

[ParoleCasuali]    Scrivere un programma che legge un intero n e stampa n parole "casuali". Le parole "casuali" possono essere generate servendosi dei seguenti tre array di stringhe:

String[] voc = {"a","e","i","o","u"};
String[] cons1 = {"b","c","d","f","g","l","m","n","p","q","r","s","t","v",
                  "z","br","cl","cr","dr","fl","fr","gl","gn","gr","pl","pn",
                  "pr","ps","sb","sc","sd","sf","sg","sm","sn","sp","sr",
                  "st","sv","tr"};
String[] cons2 = {"bb","cc","dd","ff","gg","ll","mm","nn","pp","rr","ss","tt",
                  "vv","zz","lb","lc","ld","lm","ln","lp","ls","lt","lv","lz",
                  "mb","mp","nc","nd","ng","ns","nt","nv","rb","rc","rd","rp",
                  "rs","rt","rt"};

Per ogni parola si sceglie un intero "casuale" m compreso tra 3 e 9, poi se m è dispari si inizia la parola con un elemento "casuale" di voc altrimenti con un elemento "casuale" di cons1. Poi si alternano scelte "casuali" da voc e da cons1 o cons2, fino a m volte. Ad esempio, se n = 20 il programma potrebbe stampare:

invicca
qisersenca
liqangoze
ubribbarriqe
uppumustoca
icca
esfezzofrazzo
scacrilpu
villa
nefupe
idru
urtarteresvo
plentigruvva
altizzansoppo
lamma
sbotri
stanni
ancaru
alleredessu
svasvo

Il programma può essere raffinato scegliendo in modo più accurato gli elementi degli array voc, cons1 e cons2 e la probabilità di scegliere gli elementi di un array potrebbe essere diversa da elemento a elemento (ad esempio, la probabilità di scegliere una "a" dovrebbe essere più alta della probabilità di scegliere una "u"). Inoltre, si potrebbero introdurre altri array e delle opportune regole di combinazione (non solo la semplice alternanza).

[DigitandoInteri]    Scrivere un programma che legge due interi e ne stampa il prodotto. Prevedere che l'utente possa sbagliare a digitare gli interi e con un'opportuna gestione delle eccezioni far sì che il programma permetta all'utente di ridigitare l'intero finché entrambi gli interi non sono digitati in modo corretto.

26 Feb 2016


  1. Più precisamente, se iterExpr rappresenta un array allora Type deve essere un supertipo del tipo degli elementi dell'array.

  2. Il blocco finally è eseguito anche se il blocco catch che cattura l'eccezione lancia a sua volta un'eccezione o se esegue un return.