Java/Tipi di dato

(Reindirizzamento da Java/Seconda parte)

Java

Linguaggio Java
Linguaggio Java

categoria · sviluppo · modifica
Come posso contribuire?

→ Vai su Wikiversity

Java:  Guida alla programmazioneJava/Introduzione - Specifica di linguaggio - Libreria standardJava/Comunicazione seriale

Tipi di datoJava/Tipi di datoEspressioniJava/OperatoriVariabiliJava/Variabili localiIstruzioniJava/IstruzioniStrutture di controlloJava/Strutture di controlloPackageJava/Package
Classi e oggettiJava/Classi e oggettiOggettiJava/OggettiMetodiJava/MetodiMetodi (cont.)Java/Metodi/2ArrayJava/Array
Modificatori di accessoJava/Modificatori di accessoEreditarietàJava/Ereditarietà
Gestione delle eccezioniJava/Gestione delle eccezioniGenericsJava/Generics
Thread e parallelismoJava/MultithreadingInterblocco ricontrollatoJava/Interblocco ricontrollato
Alcune differenze con il CJava/Concetti fondamentaliLessicoJava/LessicoGrammaticaJava/Grammatica

Un tipo di dati definisce un insieme di possibili valori e un insieme di operazioni che possono essere applicate su tali valori. Ad esempio, le scritture true e false identificano due valori di tipo boolean, ed è possibile utilizzarli in combinazione con gli operatori logici &&, || e altri.

I tipi ammessi in un programma Java sono i seguenti:

  • otto tipi primitivi definiti dal linguaggio (boolean, byte, char, short, int, long, float, double);
  • tipi riferimento (classi, interfacce e tipi array);
  • il nulltype;
  • il tipo degenere void.

I "valori" dei tipi riferimento sono chiamati oggetti o istanze. Una differenza fondamentale tra tipi primitivi e tipi riferimento (ma non l'unica) è la seguente:

  • il programma manipola direttamente i valori di tipi primitivi: ad esempio, i valori vengono immessi direttamente nelle variabili, e sono passati direttamente agli operatori;
  • invece, il programma accede a un oggetto sempre e solo tramite un riferimento (chiamato reference), che svolge un ruolo analogo a quello del puntatore del C.

Un'altra differenza importante è che gli oggetti possono cambiare il proprio stato interno, mentre i valori dei tipi primitivi no.

Il linguaggio stabilisce a priori il numero massimo di valori ammesso per ognuno dei tipi primitivi, ma non vincola a priori il numero di istanze per i tipi riferimento.[1] Gli oggetti sono creati e distrutti dinamicamente, durante l'esecuzione del programma. Ogni oggetto è dotato di uno stato interno, che può eventualmente cambiare nei modi stabiliti dal tipo corrispondente.

Il programmatore non può definire nuovi tipi primitivi, ma può definire classi ed interfacce personalizzate. Un programma può fare uso di un numero illimitato di classi e/o interfacce, definite nella libreria standard o in librerie di terze parti.
Non può neanche definire tipi array personalizzati, in quanto tutti i tipi array hanno un comportamento comune fissato dal linguaggio.

Il nulltype ammette un solo valore possibile, indicato con il valore letterale null. Quest'ultimo, pur non essendo un oggetto, può svolgere il ruolo di "segnaposto" in sostituzione di un oggetto, perché formalmente può essere convertito (tramite cast implicito) verso qualunque tipo riferimento.
Il tipo void non ammette alcun valore ed è utilizzato come tipo di ritorno dei metodi che non restituiscono un valore.

Gli array e le stringhe sono realizzati tramite oggetti. I caratteri singoli vengono gestiti usando il tipo numerico char.

Tipi primitivi

modifica

I tipi primitivi del Java sono i seguenti:

  • boolean: ammette i soli valori true e false.
  • Tipi numerici a virgola mobile: float e double. Essi rispondono alle indicazioni dello standard IEEE 754.
  • Tipi numerici interi:
byte Da -27 a 27 - 1 Da -128 a 127
short Da -215 a 215 - 1 Da -32768 a 32767
char Da 0 a 216 - 1 Da 0 a 65535, ovvero da '\u0000' a '\uffff'
int Da -231 a 231 - 1 Da -2147483648 a 2147483647
long Da -263 a 263 - 1 Da -9223372036854775808 a 9223372036854775807
Il tipo char

Java utilizza l'Unicode. I caratteri vengono gestiti tramite il corrispondente valore numerico Unicode; è per questo che, in realtà, char è a tutti gli effetti un tipo numerico. (Può essere visto come una versione unsigned del tipo short.) I valori letterali di tipo char vanno compresi tra singoli apici: ad esempio, 'a'.

Differenze con il C++

modifica

La dimensione dei tipi primitivi del Java non dipende dalla macchina su cui il programma è in esecuzione. Questo comportamento è differente rispetto a quello di altri linguaggi compilati in codice "nativo", primo fra tutti il linguaggio C++, nel quale i tipi base hanno dimensioni diverse a seconda che ci si trovi su una macchina piuttosto che su un'altra.[2] Questo significa che in Java è il linguaggio stesso che stabilisce il limite minimo e il limite massimo dei valori ammessi dai tipi numerici.

Su una macchina il cui processore gestisce gli interi a 32 bit usando parole a 64 bit, un compilatore C++ potrebbe decidere di implementare le variabili int facendo occupare loro 8 byte anziché 4; in Java questo non è permesso: in questo esempio, anche se la macchina virtuale può decidere di utilizzare parole a 64 bit, questo rimane trasparente al codice Java che dalla VM è eseguito, nel quale il tipo int si comporta sempre esattamente come se fosse dotato di soli 32 bits.

Inoltre, mentre in C++ è possibile scegliere fra un intero signed e uno unsigned, in Java non è possibile.

Conversioni

modifica

Le conversioni in Java si chiamano anche operazioni di cast. Una conversione ammessa dal linguaggio può essere realizzata in due soli modi: con cast esplicito o con cast implicito. Il primo si ha utilizzando l'apposito operatore; il secondo, invece, viene gestito in automatico dal compilatore e non ha una sintassi specifica. Ogni cast implicito può essere reso esplicito, se si vuole (a volte è utile per chiarezza).

L'idea che sta alla base di questa differenza è che, in presenza di una conversione che potrebbe avere risultati indesiderati, il programmatore deve "confermare" esplicitamente al compilatore di esserne consapevole e di volerla ugualmente effettuare[3]. Questa "conferma" è scritta nell'operatore di cast esplicito. Se la specifica di linguaggio riconosce che la conversione non può avere in nessun caso effetti indesiderati, allora è ammesso anche il cast implicito.

Per quanto riguarda i tipi base:

  • non sono ammesse conversioni tra boolean e i tipi numerici; si noti che questo comportamento è differente da quello che si ha nel C;
  • al contrario, sono ammesse tutte le conversioni da un certo tipo numerico verso un altro tipo numerico;
  • ma richiedono necessariamente il cast esplicito tutte le conversioni da un tipo numerico più "capiente" verso uno più "piccolo" (per es. da long verso int) o da un tipo con virgola verso un tipo intero.

Per quanto riguarda le classi:

  • le conversioni da un sottotipo a un supertipo (per es. da String a Object) non richiedono cast esplicito;
  • le conversioni da un supertipo a un suo sottotipo (per es. da Collection ad ArrayList) richiedono cast esplicito.

Il motivo è che

Classi wrapper

modifica

Dovunque sia necessario un oggetto in luogo di un valore di un tipo base, si può usare una delle classi wrapper definite nel package java.lang:

Esiste una classe anche per il tipo void java.lang.Void, sebbene questo tipo non definisca alcun valore. Essa è utilizzata in contesti particolari, tipicamente quando si usa la reflection.

Per i tipi numerici esistono anche le classi java.math.BigInteger e java.math.BigDecimal, le quali, però, non sono considerate classi wrapper. Queste due classi permettono di rappresentare numeri interi o decimali arbitrari, fuoriuscendo dai limiti imposti per i tipi primitivi.

Le classi wrapper sono molto utili dovunque sia necessario interagire con una parte del programma scritta per lavorare su oggetti. Ad esempio, non è possibile aggiungere un intero ad una collezione, perché le collezioni sono scritte come contenitori di oggetti, quindi è necessario prima racchiudere il numero in un oggetto, dopodiché si aggiunge questo oggetto alla collezione.

Autoboxing e unboxing

modifica

La famiglia dei tipi base e la famiglia dei tipi riferimento sono separate l'una dall'altra e definiscono due gerarchie distinte che non hanno alcun punto di giunzione. Per questo motivo, fino alla terza edizione della specifica di linguaggio, non erano ammesse conversioni tra tipi primitivi e tipi riferimento, né tramite cast implicito, né tramite cast esplicito.

Ogni classe wrapper permette di associare un oggetto a un valore del tipo primitivo corrispondente, e viceversa. Ad esempio, è possibile ottenere un oggetto Integer partendo da un numero di tipo int, invocando il costruttore appropriato; ed è possibile riottenere il numero intero invocando sull'oggetto il metodo intValue(). Un discorso analogo vale per ciascuno degli altri tipi primitivi. L'operazione di inserimento di un valore primitivo in un oggetto prende il nome di boxing.

Prima di Java 5, era necessario invocare esplicitamente i costruttori e i metodi xxxValue() appropriati. Per rendere più sintetico e leggibile il codice che richiama queste operazioni, dalla terza versione della specifica di linguaggio (Java 5) in poi, è ammesso il cast (implicito o esplicito) di un valore di un tipo primitivo verso il corrispondente tipo wrapper, e viceversa. Il compilatore sostituisce automaticamente questa sintassi con una istruzione che invoca i metodi o i costruttori del tipo wrapper.
L'operazione di inserimento di un valore primitivo in un oggetto tramite cast si chiama autoboxing; l'operazione inversa (svolta sempre tramite cast) si chiama unboxing.

Quindi, da Java 5 in poi, è possibile scrivere:

Integer k = 200;
int j = k;

invece di:

Integer k = new Integer(200); // oppure Integer k = Integer.valueOf(200);[4]
int j = k.intValue();

in quanto la prima forma viene tradotta automaticamente dal compilatore.

La conversione da tipo primitivo a tipo wrapper è detta autoboxing, mentre la conversione inversa è detta unboxing. Si noti che questi due meccanismi sono solo zucchero sintattico che maschera delle invocazioni a metodo; quindi, in definitiva, non cambiano il fatto che i tipi primitivi siano distinti e separati dai tipi riferimento.

Le stringhe

modifica

Una stringa è un'istanza della classe String: un oggetto che incapsula una sequenza di caratteri.
Esistono molte classi per la manipolazione delle stringhe. Quelle più comunemente utilizzate sono definite nel package java.lang e sono:

  • String (stringhe: oggetti immutabili, quindi thread-safe);
  • StringBuffer (oggetti modificabili e thread-safe);
  • StringBuilder (oggetti modificabili e non thread-safe; presente dalla versione 1.5 in poi).

Le stringhe forniscono metodi che permettono di manipolarle, confrontarle e dividerle in parti. Di seguito si riportano i casi più comuni; per gli altri, si legga la documentazione ufficiale della classe String.

  • La creazione di una stringa avviene tramite l'invocazione dei costruttori (operazione sconsigliata) o tramite uno string literal.
  • La concatenazione di due stringhe si ottiene interponendo tra le due stringhe l'operatore di concatenazione +:
    String s1 = "Ciao";
    String s2 = "come va?";
    
    //mostrerà "Ciao, amico, come va?"
    System.out.println(s1+", amico, "+s2);
  • Si possono confrontare le stringhe attraverso il metodo equals (confronto case-sensitive con un altro oggetto String) o uno dei metodi equalsIgnoreCase (confronto case-insensitive con altro oggetto String). Esistono anche altri metodi, riportati nella documentazione della classe.
Nota: le stringhe si confrontano con il metodo equals, non con l'operatore ==. Infatti, due stringhe possono essere uguali pur essendo contenute in due oggetti diversi:
String s = new String("Ciao");
String t = new String("Ciao");
assert s.equals(t); // true
assert s == t; // false
  • Il numero di caratteri di una stringa si ottiene invocando il metodo length().

Bibliografia

modifica
Note esplicative
  1. Il limite può essere stabilito dal tipo stesso, altrimenti è determinato solo dalla eventuale saturazione della area heap della Java Virtual Machine.
  2. Il programmatore C++ che desideri un tipo lungo esattamente n bits può importare cstdint (stdint.h in C).
  3. L'esempio classico è la conversione di un numero con virgola (float o double) verso un tipo intero, perché una parte del numero va persa (i decimali). Il programmatore potrebbe non esserne consapevole al momento della scrittura del codice.
  4. Il metodo valueOf(int) è stato introdotto in Java 5.
Fonti