Primi passi in Java

Giovedì, 27 Settembre 2007

OBIETTIVI DELLA LEZIONE

In questa lezione impareremo a conoscere:

  1. Variabili e tipi di dato
  2. Operatori
  3. Strutture di controllo: IF

Alla fine della lezione saremo in grado di scrivere autonomamente la nostra prima semplice classe Java.

Ripasso: cosa serve la Java Virtual Machine?

Abbiamo visto che la piattaforma Java si basa su tre componenti principali: Il linguaggio, la libreria di classi e la Java Virtual Machine (JVM).


In Java:

  1. Il programmatore [VOI!] studia e prepara un programma in Java;
  2. il compilatore Java [javac] "traduce" il programma Java in una classe in ByteCode.
  3. ciascuna JVM [una distinta per ogni piattaforma!], ricevuto il messaggio in ByteCode, lo interpreta nel "linguaggio" della macchina locale in modo che possa essere eseguito.

Ripassiamo l'anatomia di un programma Java

Quali sono i componenti principali di un programma Java? Ripassiamoli in questa slide.

Dati

Per quanto ci riguarda, un "dato" è una quantità minimale di informazione che vogliamo elaborare con un nostro programma. Può essere il saldo del nostro conto corrente, il numero di amici che abbiamo invitato alla nostra festa o il colore del gatto del vicino.

Come nell'algebra e in altri linguaggi di programmazione, anche in Java chiamiamo variabile il contenitore simbolico di un certo "dato". Le variabili vengono manipolate con degli operatori che ne modificano il valore. Frasi che coinvolgono variabili ed operatori, vengono chiamate espressioni.

Tipi di dato

Supponiamo di dover preparare un modulo cartaceo da compilare per un sondaggio; (l'utente compilerà il modulo con una penna, nessun computer). L'utente deve inserire il proprio sesso, l'età e la marca delle caramelle preferite. Probabilmente prevederemmo una cosa del genere:

 Sesso: _
 Età: __
 Marca caramelle preferite: ____________________

Infatti, per il sesso basta inserire un carattere, M o F, due numeri per l'età e per la marca preferita, che non conosciamo a priori, lasciamo 20 spazi vuoti. Ciascun dato occuperà un certo "spazio" sul foglio, ad esempio la variabile "Sesso" occuperà uno spazio.

In un programma Java, come del resto in ogni linguaggio di programmazione, i dati vengono immagazzinati in memoria da qualche parte (per oggi non ci preoccuperemo di capire dove). Per ogni variabile viene predisposto lo spazio opportuno: come sul foglio venivano predisposti degli spazi per le lettere scritte a penna, allo stesso modo nella memoria del calcolatore viene riservato un numero di byte adeguato.

Per poter fare questo, Java richiede che venga specificato sempre il tipo di dato che vogliamo sia contenuto in una variabile. Ad esempio:

    int questo;
    short mese;

Il tipo di dato non può essere cambiato nel corso del programma. Java è un linguaggio fortemente tipizzato (non lo sarà affatto il PHP).

Per assegnare un valore ad una variabile si utilizza l’operatore =
Esempio:

  tassoInteresse=0.001;

È possibile dichiarare una variabile e contemporaneamente assegnarle un valore. Esempio:

  int guasti=10;

I nomi delle variabili (ma anche di classi ed oggetti che vedremo più avanti nel corso) si chiamano identificativi. Un identificatore deve rispettare le regole:

Riassumendo, in Java una variabile è un dato a cui si fa riferimento tramite un identificativo. Inoltre:

Il tipo di una variabile ne indica il formato del contenuto ed le operazioni che possono essere effettuate. Java prevede due categorie di tipi di dato:

Tipi di dati primitivi

In Java, i tipi di dati primitivi sono i mattoni base a partire dai quali vengono costruiti tutti gli altri oggetti. NON sono oggetti. Sono stati pensati a partire da quelli definiti dal C, con alcuni miglioramenti e semplificazioni efficaci. Eccoli:

Tipo Valore Default Occupa Intervallo
boolean true, false false 1 bit
char carattere Unicode \u0000 2 bytes da \u0000 a \uFFFF
byte intero (con segno) 0 1 byte da -128 a 127
short intero (con segno) 0 2 bytes da -32768 a 32767
int intero (con segno) 0 4 bytes da -2147483648 a 2147483647
long intero (con segno) 0 8 bytes da -9223372036854775808 a 9223372036854775807
float numeri a virgola mobile (IEEE 754) 0.0 4 bytes da ±1.4E-45 a ±3.4028235E+38
double numeri a virgola mobile (IEEE 754) 0.0 8 bytes da ±4,9E-324 a ±1.7976931348623157E+308

Il valore di default verrà utilizzato per i campi degli oggetti e per gli array [per adesso non sappiamo cosa siano; lo capiremo più avanti].

Valori logici

In Java assumono i valori true o false e NON sono dei numeri interi ne possono essere convertiti in numeri interi come succede in C o C++.

Interi

I numeri interi sono tutti con il segno. NON esistono i numeri unsigned come in C. Inoltre hanno una dimensione che è fissata dal linguaggio (quella della tabella sopra) e non variabile con il tipo di architettura.

I numeri vengono rappresentati in base 2 e non in base decimale. Questo perché, come ben sapete, le memorie sono bistato e possono solo memorizzare due valori, che per convenzione si assumono essere 0 o 1.

Ripasso

In base 2 ci sono solo due simboli a disposizione, ad esempio, 7 diventa 111 mentre 37 diventa 100101. In base 16 (esadecimale) ci sono invece 16 simboli a disposizione e per convenzione si utilizzano le lettere da A a E per rappresentare i numeri da 10 a 15. Ad esempio, 37 diventerebbe 25.

Cosa rappresenta il numero 155 in base 10? 155 = 1 * 102 + 5 * 101 + 5 * 100. Cioè ogni cifra ha un valore in base alla posizione (infatti si chiama notazione posizionale e non è l'unico modo di rappresentare i numeri, basti ad esempio pensare alla rappresentazione romana in cui 155 = CLV).

Come si converte un numero decimale in un altra base? Lo si deve dividere ripetutamente per la base fino ad ottenere 0. I resti delle divisioni intere, letti dall'ultimo al primo, sono le cifre del numero convertito.

Esempio: convertiamo 155 in base 2.

        155/2 = 77/2 = 38/2 = 19/2 = 9/2 = 4/2 = 2/2 = 1/2 = 0
          1      1      0      1     1     0     0     1
        <--- Leggiamo i resti in questa direzione -----
        10011011
        

Esempio: convertiamo 155 in base 16.

        155/16 = 9/16 = 0
         11      9
        <--- Leggiamo i resti in questa direzione -----
        9B
        

Per convertire da base 2 o 16 a base decimale, dobbiamo semplicemente moltiplicare ciascuna cifra del numero per il suo valore posizionale.

Esempio: convertiamo 10011011 in base 10.

        10011011 = 1*2^7 + 0*2^6 + 0*2^5 + 1*2^4 + 1*2^3 + 0*2^2 + 1*2^1 + 1*2^0 =
                 = 1*128 + 0* 64 + 0* 32 + 1* 16 + 1*  8 + 0*  4 + 1*  2 + 1*  1 =
                 =   128 +     0 +     0 +    16 +     8 +     0 +     2 +     1 =
                 = 155
        (il simbolo ^ indica qui l'elevamento a potenza, ma non è un operatore JAVA!)
        

La base 16 è un multiplo della base 2. Dato un numero in base due, per passare ad un numero in base 16 basta convertirlo 4 cifre alla volta.

Esempio: convertiamo 10011011 in base 16.

        10011011 --> 1001 e 1011
                     8+0+0+1 e 8+0+2+1
                     9 e B
                     9B
                     9B
        

I numeri negativi vengono rappresentati in complemento a 2. Per prima cosa ricordiamoci che il complemento a 1 di un numero si costruisce invertendo tutti i bit della sua rappresentazione binaria.

Esempio: calcoliamo il complemento a 1 dell'intero 155.

        155 è un intero, quindi sarà contenuto in 32 bits:
        00000000 00000000 00000000 10011011
        invertiamo tutti i bits
        11111111 11111111 11111111 01100100
        

Il complento a 2 del numero A si ottiene sommando 1 al suo complemento a 1 (che in Java è l'operatore ~ come in C).

Esempio: calcoliamo il complemento a 2 di 155.

        ~155 + 1 =
                   11111111 11111111 11111111 01100100 +
                   00000000 00000000 00000000 00000001 =

                   11111111 11111111 11111111 01100101
        

La notazione in complemento a 2 si utilizza in modo che A+(-A)=0.

Esempio: calcoliamo 155 + (-155).

         155 -->   00000000 00000000 00000000 10011011 +
        -155 -->   11111111 11111111 11111111 01100101 +

                   00000000 00000000 00000000 00000000
        

I numeri negativi in Java sono rappresentati in complemento a due. Attenzione alla circolarità indotta dalla rappresentazione: se allo short A = 32767 aggiungiamo 1 otteniamo -32768! Vediamolo:

         32767 è uno short, dunque è memorizzato in 16 bits
         32767 -->   01111111 11111111 +
             1 -->   00000000 00000001 =

                     10000000 00000000 
        
Che è la rappresentazione in complemento a due di -32768 su 16 bits.
class EsempioCircolarita {
  public static void main(String[] args) {
    short a = 32767;
    a += 1;
    System.out.println(a);
    short b = -32768;
    b -= 1;
    System.out.println(b);
  }
}
>java EsempioCircolarita
-32768
32767

Nei nostri programmi utilizzeremo solo numeri interi di tipo int e long, visto che per quanto ci riguarda possiamo fare a meno delle altre tipologie.

Numeri a virgola mobile

I numeri a virgola mobile sono una rappresentazione dei numeri razionali. NON sono un insieme denso cioè tra due numeri non è detto che ce ne sia un terzo né sono illimitati.

L'aritmetica a virgola mobile di Java è un sottoinsieme dello standard IEEE-754. Per i più curiosi, all'indirizzo http://www.h-schmidt.net/FloatApplet/IEEE754.html potete trovare un applet Java che vi permette di capire meglio la rappresentazione in virgola mobile.

Durante il corso utilizzaremo solo numeri a virgola mobile di tipo double (64 bits).

Caratteri

Ripasso

Nella memoria di un calcolatore possiamo memorizzare solo numeri binari. Di certo non possiamo memorizzare un segno grafico come un carattere. Lo stesso problema lo affrontarono coloro che organizzarono le trasmissioni telegrafiche: come trasmettere un messaggio con uno strumento in grado di trasmettere solo punti e linee? Ovviamente è necessaria una codifica: ad ogni carattere associamo un numero che trasmettiamo o, nel nostro caso, memorizziamo.

Una codifica che si affermò in modo pressoché universale fu quella standard ASCII (1963), che comprendeva 128 caratteri (7 bits) tra cui alcuni caratteri di controllo che erano utilizzati per le trasmissioni e la gestione del terminale. 128 caratteri non sono molti ma erano sufficienti per codificare la lingua inglese che non usa le lettere accentate. La codifica ASCII è la seguente:

 NUL(  0)    SOH(  1)    STX(  2)    ETX(  3)
 EOT(  4)    ENQ(  5)    ACK(  6)    BEL(  7)
 BS (  8)    HT (  9)    NL ( 10)    VT ( 11)
 FF ( 12)    CR ( 13)    SO ( 14)    SI ( 15)
 DLE( 16)    DC1( 17)    DC2( 18)    DC3( 19)
 DC4( 20)    NAK( 21)    SYN( 22)    ETB( 23)
 CAN( 24)    EM ( 25)    SUB( 26)    ESC( 27)
 FS ( 28)    GS ( 29)    RS ( 30)    US ( 31)
 SP ( 32)     ! ( 33)     " ( 34)     # ( 35)
  $ ( 36)     % ( 37)     & ( 38)     ' ( 39)
  ( ( 40)     ) ( 41)     * ( 42)     + ( 43)
  , ( 44)     - ( 45)     . ( 46)     / ( 47)
  0 ( 48)     1 ( 49)     2 ( 50)     3 ( 51)
  4 ( 52)     5 ( 53)     6 ( 54)     7 ( 55)
  8 ( 56)     9 ( 57)     : ( 58)     ; ( 59)
  < ( 60)     = ( 61)     > ( 62)     ? ( 63)
  @ ( 64)     A ( 65)     B ( 66)     C ( 67)
  D ( 68)     E ( 69)     F ( 70)     G ( 71)
  H ( 72)     I ( 73)     J ( 74)     K ( 75)
  L ( 76)     M ( 77)     N ( 78)     O ( 79)
  P ( 80)     Q ( 81)     R ( 82)     S ( 83)
  T ( 84)     U ( 85)     V ( 86)     W ( 87)
  X ( 88)     Y ( 89)     Z ( 90)     [ ( 91)
  \ ( 92)     ] ( 93)     ^ ( 94)     _ ( 95)
  ` ( 96)     a ( 97)     b ( 98)     c ( 99)
  d (100)     e (101)     f (102)     g (103)
  h (104)     i (105)     j (106)     k (107)
  l (108)     m (109)     n (110)     o (111)
  p (112)     q (113)     r (114)     s (115)
  t (116)     u (117)     v (118)     w (119)
  x (120)     y (121)     z (122)     { (123)
  | (124)     } (125)     ~ (126)    DEL(127)

Per permettere la codifica di lingue che utilizzano molti caratteri accentati lo standard ASCII fu esteso a 8 bits in vari modi differenti. IBM e Microsoft per MSDOS introdussero le "code pages": i caratteri dal 128 al 255 erano diversi a seconda della code page scelta. Ad esempio, la code page inglese era la 437 mentre quella italiana la 850. Purtroppo per leggere un testo era sempre necessario conoscere la codepage con cui era stato codificato!

Ad un certo punto per evitare il dilagare di approcci differenti fu introdotto lo standard a 8 bits ISO 8859. In Italia e negli altri paesi occidentali è utilizzato lo standard ISO/IEC 8859-1 chiamato anche ISO Latin1, che è parte di ISO 8859 . La code page di MS Window nelle lingue occidentali è la 1252, compatibile con lo standard.

La differenza tra le code pages causa effetti deleteri quando si programma in linguaggi come il C. Ad esempio la lettera 'è' viene codificata con il codice 232 da MS Windows mentre lo stesso codice corrisponde alla lettera thorn maiuscola Þ (presente nell'alfabeto finlandese) nella console DOS. Questo è il motivo per cui i programmi che svilupperete con Java se contengono messaggi contenenti lettere accentate daranno risultati bizzarri dalla console DOS.

Fortunatamente queste sono complicazioni in estinzione. Per semplificare le comunicazioni nell'era dell'informatizzazione globale è stata introdotta una codifica detta UNICODE in cui si arrivano ad utilizzare fino a 32 bits per carattere: è possibile rappresentare anche tutti gli ideogrammi cinesi!

Lo standard Unicode, fino alla release 3.0 era di fatto uno standard di codifica a 16 bits e Java lo aveva abbracciato appieno dalla nascita. Dalla versione Unicode 3.1 in poi sono stati necessari più di 16 bits per codificare alcuni caratteri. La transizione a Unicode 4.0 è stata per Java un processo più complesso, ma oggi completamente completato nella versione Java 1.5 Tiger. Java utilizza la codifica di carattere variabile (16 o 32 bits) UTF-16 definita nello standard ISO/IEC 10646-1.

I caratteri in Java sono codificati seguendo lo standard di codifica UCS-2 (che è un sottoinsieme di UTF-16) capace di codificare tutti i caratteri del "basic multilingual plane" con 16 bits. In Java dunque per ciascun carattere sono utilizzati due bytes (NON uno come in C). Questo permette di includere pressoché qualunque tipo di carattere utilizzato sulla terra (ma non tutti gli ideogrammi cinesi per i quali serve utilizzare una codifica a 32 bits UTF-16. Ne vedremo un accenno più avanti).

Per rappresentare una costante carattere la si deve includere tra due apici semplici. Per assegnare un carattere unicode ad una variabile si utilizza la forma \uXXXX dove XXXX è la codifica esadecimale. Comode sone le seguenze di escape, come in C:

\n a capo (LF)
\t tabulazione orizzonatale (HT)
\\ backslash \
\' apice '
\" virgolette doppie "

Esempi:

    char carattere = 'R';
    char carattere = '\n';
    char carattere = '\u05D0';    

Conversione tra tipi differenti

Talvolta è necessario mischiare valori numerici:

  byte i = 100; 
  long k = i *3 * 4; 
  double d = i *3.1 + k /2;

Quando viene svolta operazione con dati di tipo diverso:

Casting

Il Java ha un controllo dei tipi molto più stringente che il C. Permette conversioni di tipo implicite SOLO quando è impossibile perdere informazioni. E’ possibile passare implicitamente da una variabile “meno capiente” ad una “più capiente” (es. da int a long). Non il viceversa. Altrimenti, se si vuole forzare la conversione di tipo dobbiamo forzarla, come in C, con un cast. Esempio:

    long numeroLungo = 35267;
    int numeroCorto;
    numeroCorto = (int)numeroLungo;

La seguente è la tabella delle conversioni implicite ammesse:

boolean byte short int long float double char
boolean - NO NO NO NO NO NO NO
byte NO - SI SI SI SI SI CAST
short NO CAST - SI SI SI SI CAST
int NO CAST CAST - SI SI SI CAST
long NO CAST CAST CAST - SI SI CAST
float NO CAST CAST CAST CAST - SI CAST
double NO CAST CAST CAST CAST CAST - CAST
char NO CAST CAST SI SI SI SI -

Costanti letterali

Ogni tipo è dotato di costanti letterali: permettono di rappresentare i valori constanti per ogni tipo.

Ne abbiamo (senza saperlo) già viste molte:

int contatore = 1;
double tassoInteresse = 3.4;
boolean esameSuperato = true;
char carattere = 'R'; 
char carattere = '\n'; 
char carattere = '\u05D0';
long numeroLungo = 323423423L

Operatori

Gli operatori vengono utilizzati per effettuare operazioni su variabili o costanti letterali. Sono di tre tipi:

Una buona notizia: gli operatori Java coincidono pressoché con quelli C/C++.

Aritmetici

Somma (+), sottrazione (-), moltiplicazione (*), divisione (/) e resto della divisione intera (%). ATTENZIONE: se la divisione è fatta tra due interi NON produce un numero a virgola mobile!

Operatori di auto-incremento:

auto-decremento

Che differenza c'è tra operatori di pre-incremento e post-incremento?

int i=10;
int b = 10 * i++; 
// corrisponde a: int b = 10 * i; i = i + 1;
int b = 10 * (++i); 
// corrisponde a: i = i + 1; int b = 10 * i; 

Gli operatori aritmetici possono essere utilizzati con operandi di tipo char. Il valore su cui vengono effettuate le operazioni aritmetiche è il codice Unicode corrispondente. Esempio:

int  a = 5 * ‘r’; 
equivalente a:
int  a = 5 * 114;

Assegnamento

L'operatore di assegnamento è il segno di uguale (=). Come in C, la maggior parte degli operatori può essere combinata con l'operatore di assegnamento. Ad esempio, a += 3; corrisponde a a = a + 3;.

Relazionali

Uguale (==), diverso (!=), maggiore (>), maggiore e uguale (>=), minore (<) e minore e uguale (<=). Restituiscono il valore boolean true se la relazione è verificata, false altrimenti.

Logici

Operano su due operandi boolean e restituiscono un valore boolean. not (!) [unario prefisso], and (&&) [binario], or (||) [binario] e xor (^) [binario].

&& e || sono operatori “short-circuit”: il secondo operando è valutato solo se necessario.

op1 && op2 // se op1 è false, op2 non viene valutato
op1 || op2 // se op1 è true, op2 non viene valutato

Esistono anche gli operatori logici: & (unconditional and) e | (unconditional or) che non sono “short-circuit” (op1 e op2 sono comunque valutati). Sono sconsigliati!

Commenti

In Java è possibile inserire commenti nel codice in tre modi. I primi due sono identici a quelli previsti dal C++. Esempio:

  /* Questo è 
  un commento
  su 
  più righe. */

  // Questo è un commento a singola riga

Il terzo modo serve per specificare dettagli di un programma agli altri programmatori (o anche a noi stessi). Lo vederemo più avanti.

Stringhe

Le stringhe NON sono un tipo di dato primitivo ma un oggetto della classe String. Mnemonicamente potete ricordare che String inizia con una maiuscola, come del resto tutte le classi, a differenza dei tipi primitivi che iniziano con una minuscola.

Le stringhe però a differenza di quasi tutti gli altri oggetti possono essere inizializzate con una costante letterale, come in C. Esempio:

    String frase = "Questa è una stringa";

Le stringhe sono terminate da un carattere NULL come in C? Non ci interessa come siano rappresentate nella memoria. Questo è un tipico della programmazione ad oggetti e si chiama "incapsulazione": nascondere i dettagli implementativi.

Concatenamento stinghe

L'operatore + quando usato con due stringhe le concatena. Esempio:

    String nome = "Luigi" + " " + "Rossi";

Istruzioni e blocchi

Una istruzione in Java coincide con un comando eseguito dalla JVM. Un blocco è un insieme di istruzioni racchiuso tra parentesi graffe. E' possibile dichiarare delle variabili locali all'interno di in blocco di istruzioni, e non è necessario farlo all'inizio come in C89. I blocchi possono essere annidati a piacere: le variabili locali sono visibili dal punto della loro dichiarazione fino alla fine del blocco cui appartengono, (quindi anche nei sottoblocchi). (Queste sono chiamate in genere regole di scopo o di visibilità).

  int a = 1;
  {
    int b;
    b = a; // b diventa 1
  } // qui la variabile b cessa di esistere
 

Una sequenza è invece una banale successione di istruzioni.

Strutture di controllo del flusso 1/4

Strutture condizionali: if

Composta da UNA espressione e UNO/DUE/TRE blocchi di codice (chiamati i "rami" dell' if)

if (condizioneLogica)
  istruzione1;
 

oppure

if (condizioneLogica)
  istruzione1;
else
  istruzione2;
 

oppure

if (condizioneLogica1)
  istruzione1;
else if (condizioneLogica2)
  istruzione2;
else if (condizioneLogica3)
  istruzione3;
else
  istruzione4;
 

Ciuscuna istruzione può essere costituita da un istruzione singola o da un blocco. Attenzione! Il costrutto else si riferisce SEMPRE al istruzione if più vicino:

if (condizioneLogica1)
  if (condizioneLogica2)
    if (condizioneLogica3)
else
  istruzione;
 

In questo codice, indentato male appositamente, il costrutto else è legato alla condizioneLogica3.

Lab

Esercizio 0

Studiare, compilare ed eseguire il seguente programma Java. Come si chiama il nome del file in cui deve essere contenuto? [Soluzione: Somma.java]

import javax.swing.*;

class Somma {
  public static void main(String[] args) {
    String stringaInput;
    int primoNumero, secondoNumero;

    stringaInput = JOptionPane.showInputDialog("Inserisci il primo numero");
    primoNumero = Integer.parseInt(stringaInput);

    stringaInput = JOptionPane.showInputDialog("Inserisci il secondo numero");
    secondoNumero = Integer.parseInt(stringaInput);

    JOptionPane.showMessageDialog(null, "La somma dei due numeri è " +
      (primoNumero+secondoNumero));

    System.exit(0);
  }
}

Esercizio 1 [Soluzione]

Dopo aver studiato l'esercizio 0, utilizzando le funzioni di input/output della classe JOptionPane, scrivere la classe Java Radice che chieda all'utente un numero a virgola mobile e restituisca all'utente la sua radice quadrata.

Suggerimenti: usate la funzione Double.parseDouble(String x) per ottenere un double da una stringa di input; utilizzate la funzione Math.sqrt(double x) per ottenere la radice quadrata.

Esercizio 2 [Soluzione]

Dopo aver svolto l'esercizio 1, scrivere la classe Java EquazioneSecondoGrado che risolva una equazione di secondo grado.

©2007 Roberto Sassi