Comprendere i Tipi di Dati in Java

L’autore ha selezionato il Fondo per il Software Libero e Open Source per ricevere una donazione come parte del programma Scrivi per le Donazioni.

Introduzione

Java è un linguaggio di programmazione staticamente tipizzato. Ciò significa che quando si crea una variabile, è necessario specificare anche il suo tipo di dato, che è il tipo di informazione che memorizza. Questo è in contrasto con i linguaggi dinamicamente tipizzati, come PHP. Con i linguaggi dinamicamente tipizzati, non è necessario specificare il tipo di dato di una variabile, il che può sembrare un sollievo.

Tuttavia, conoscere i tipi di dati e usarli in modo appropriato consente agli sviluppatori di ottimizzare il proprio codice perché ogni tipo di dato ha requisiti specifici di risorse. Inoltre, se si specifica un tipo di dato e si cerca di memorizzare un tipo diverso, ad esempio per errore, non sarà possibile compilare il codice. Pertanto, con i linguaggi staticamente tipizzati, è possibile rilevare gli errori ancora prima di qualsiasi test.

Java ha due tipi di dati: primitivi e di riferimento (anche noti come non primitivi). In questo tutorial, userai variabili per memorizzare e utilizzare informazioni in un programma Java al fine di apprendere alcuni dei tipi di dati comunemente utilizzati in Java. Questa non è una panoramica esaustiva di tutti i tipi di dati, ma questa guida ti aiuterà a familiarizzare con le opzioni disponibili in Java.

Prerequisiti

Per seguire questo tutorial, avrai bisogno di:

Tipi Primitivi

I tipi primitivi di Java sono i tipi di dati più semplici e di base in Java. Rappresentano valori grezzi come numeri e caratteri. I tipi di dati primitivi più utilizzati sono int (interi), boolean (valori booleani) e char (caratteri). Puoi trovare il resto nella documentazione ufficiale sui tipi di dati di Java.

Interi

Gli interi possono essere sia numeri interi negativi che positivi. In Java, userai int per memorizzarli. int può ospitare numeri sufficientemente grandi per la maggior parte degli scopi: da -2.147.483.648 a 2.147.483.647.

Diamo un’occhiata a come viene utilizzato int in un esempio:

int theAnswer = 42;

I tipi primitivi iniziano sempre con una lettera minuscola (int). Le regole di sintassi di Java richiedono che tu specifichi prima il tipo di dati (int) e poi il suo nome (theAnswer). Dopo di che, assegni il valore 42 con il segno uguale (=) alla variabile.

Indipendentemente dal tipo di dati, si utilizza una variabile specificando direttamente il suo nome senza anteporre alcun carattere speciale. Ciò perché Java può riconoscerla come una variabile.

Nota: Il nome della variabile theAnswer e tutte le altre variabili in questo tutorial sono scritte in Camel case. Anche se non c’è un requisito rigoroso per usarlo, questa è la convenzione di denominazione accettata in Java.

Una volta dichiarata la variabile, è possibile utilizzarla facendo riferimento ad essa in un metodo come segue:

int theAnswer = 42;
System.out.println("The answer to all questions is " + theAnswer);

Nella seconda riga, si stampa theAnswer sulla console utilizzando il metodo integrato println del package System.out. Questo è il modo più semplice per testare una variabile per assicurarsi che sia stata dichiarata come previsto.

Per vedere questo codice in azione, utilizzare lo strumento Java Shell. Dopo aver installato Java, aprire un terminale o prompt dei comandi sul computer locale e digitare jshell:

  1. jshell

Il risultato sarà simile al seguente:

Output
| Welcome to JShell -- Version 11.0.16 | For an introduction type: /help intro jshell>

È possibile incollare gli esempi di codice da questo tutorial nella console. Una volta terminato, è possibile uscire da jshell digitando /exit.

Per dichiarare e utilizzare int, incollare le seguenti righe nella console di jshell:

  1. int theAnswer = 42;
  2. System.out.println("The answer to all questions is " + theAnswer);

Vedrete il seguente output:

Output
theAnswer ==> 42 The answer to all questions is 42

Questo output conferma che avete impostato correttamente la variabile int theAnswer a 42 (theAnswer ==> 42). Avete anche utilizzato con successo theAnswer passandola a un metodo, e il metodo ha prodotto il valore della variabile previsto.

Booleano

Booleano rappresenta valori true o false. In Java, si utilizza boolean per memorizzarli. Ad esempio, creiamo una variabile boolean che definisce se Java è divertente:

boolean isJavaFun = true;

Si definisce la variabile isJavaFun come true. Il valore alternativo per boolean è false.

Utilizzando la variabile sopra, è possibile stampare la frase Java is fun: true in questo modo:

  1. boolean isJavaFun = true;
  2. System.out.println("Java is fun: " + isJavaFun);

Eseguendo queste righe in jshell, si otterrà l’output seguente:

Output
isJavaFun ==> true Java is fun: true

Simile all’esempio di int, il metodo println stamperà l’argomento fornito tra parentesi. Il segno più (+) concatena o unisce la stringa “Java is fun: ” con la variabile isJavaFun, in modo che in realtà sia un unico argomento: la stringa Java is fun: true.

Caratteri

Per memorizzare un singolo carattere alfanumerico, si utilizza char. Ad esempio:

char firstLetter = 'a';

Nota che la lettera a è racchiusa tra apici singoli. Gli apici singoli possono essere utilizzati solo per valori char. Gli apici doppi sono utilizzati per le stringhe, come si imparerà in seguito.

char non sembra essere un tipo particolarmente utile perché è improbabile che avrai bisogno di una variabile assegnata a un singolo carattere. Tuttavia, char è utilizzato come mattoncino per le classi di stringhe di caratteri come String, che sono fondamentalmente una collezione di valori char.

Come hai visto in questa sezione, la dichiarazione e l’uso di variabili di tipo primitivo sono semplici perché rappresentano valori semplici come interi. Questi valori sono pronti per essere utilizzati e non richiedono operazioni aggiuntive come la creazione di oggetti, l’invocazione di metodi, e così via.

Tipi di Riferimento

Nel primo tutorial di questa serie, Come Scrivere il Tuo Primo Programma in Java, hai appreso che il codice Java è organizzato in classi e che queste classi sono utilizzate come modelli per creare oggetti. Quando tali oggetti vengono assegnati a variabili, stai puntando o facendo riferimento a questi oggetti. In questi casi, le variabili sono classificate come tipi di riferimento. Queste variabili sono anche conosciute come non primitive perché le variabili di tipo primitivo non possono puntare agli oggetti.

Gli oggetti sono potenti perché hanno proprietà avanzate e sono in grado di agire quando si attivano i loro metodi. Tuttavia, senza variabili che puntano a essi, questi oggetti sono inaccessibili e praticamente inutilizzabili. Ecco perché le variabili di tipo riferimento sono essenziali per Java e la programmazione orientata agli oggetti nel suo complesso.

Nota: I tipi di riferimento puntano agli oggetti creati dalle classi. Per evitare confusione, il tipo di riferimento e l’oggetto creato saranno della stessa classe negli esempi seguenti.

Tuttavia, nei programmi complessi, questo è raramente il caso. In Java, un interfaccia è un insieme di requisiti per un comportamento specifico, e questi requisiti possono essere soddisfatti da una o più classi. Una classe che soddisfa i requisiti di un’interfaccia si dice che implementa questa interfaccia. Quindi, nei programmi complessi, è comune dichiarare una variabile con il tipo di riferimento di un’interfaccia. In questo modo, si specifica il comportamento che la variabile dovrebbe esibire senza legarla a un’implementazione concreta di questo comportamento. Ciò consente di cambiare facilmente l’implementazione a cui punta la variabile senza dover modificare il modo in cui viene utilizzata la variabile. Questo concetto complesso fa parte di un argomento più avanzato su ereditarietà e polimorfismo, che sarà un tutorial separato nella nostra serie Java.

Mentre ci sono solo alcuni tipi primitivi, i tipi di riferimento sono praticamente illimitati perché non c’è un limite al numero di classi (e interfacce), e ogni classe rappresenta un tipo di riferimento. In Java ci sono molte classi incorporate che forniscono funzionalità essenziali. Le più utilizzate si trovano nel package principale java.lang. Ne rivedrai alcune in questa sezione.

La classe String

La classe String rappresenta una combinazione di caratteri che costituiscono una stringa. Per dichiarare una variabile di tipo String, o di qualsiasi altro tipo di riferimento, specifichi prima il suo tipo seguito dal suo nome. Dopo di che, assegni un valore con il segno di uguale. Finora, è simile a lavorare con tipi primitivi. Tuttavia, i tipi di riferimento puntano a oggetti, quindi devi creare un oggetto se non ne è stato ancora creato uno. Ecco un esempio:

String hello = new String("Hello");

hello è il nome della variabile con tipo di riferimento String. Lo assegni a un nuovo oggetto String. Il nuovo oggetto String viene creato con la parola chiave new insieme al nome della classe — String in questo caso. La classe String inizia con una lettera maiuscola. Per convenzione, tutte le classi e quindi i tipi di riferimento iniziano con una lettera maiuscola.

Ogni classe ha un metodo speciale chiamato un costruttore che viene utilizzato per creare nuovi oggetti. Puoi invocare questo costruttore aggiungendo parentesi (()) alla fine del nome della classe. Il costruttore può accettare parametri, come nell’esempio sopra, dove il parametro "Ciao" è applicato al costruttore per String.

Per confermare che la variabile hello si comporti come previsto, passala nuovamente al metodo println in questo modo:

  1. String hello = new String("Hello");
  2. System.out.println(hello);

Eseguire queste righe in jshell produrrà il seguente output:

Output
hello ==> "Hello" Hello

Questa volta, l’output conferma che la variabile hello è impostata su Ciao. Dopo di che, lo stesso Ciao viene stampato su una nuova riga, confermando che il metodo println() lo ha elaborato.

Classi Wrapper

Nella sezione precedente, hai lavorato con il tipo di riferimento String, che viene utilizzato frequentemente. Altri tipi di riferimento popolari sono le cosiddette classi wrapper per tipi primitivi. Una classe wrapper avvolge o contiene dati primitivi, da qui il suo nome. Tutti i tipi primitivi hanno controparti wrapper, ed ecco alcuni esempi:

  • Integer: Per avvolgere valori int.
  • Character: Per avvolgere valori char.
  • Boolean: Per avvolgere valori boolean.

Questi wrapper esistono per consentire di aggiornare un semplice valore primitivo a un potente oggetto. Ogni wrapper ha metodi pronti all’uso relativi ai valori che è progettato per memorizzare.

Come esempio, esplorerai Integer. Nella sezione precedente, hai creato un oggetto String con la parola chiave new. Tuttavia, alcune classi forniscono, e addirittura incoraggiano, l’uso di metodi speciali per acquisire oggetti da loro, e Integer è uno di questi. Nel caso di Integer, l’uso di un metodo speciale riguarda principalmente l’ottimizzazione delle risorse, ma in altri casi potrebbe riguardare la semplificazione della costruzione di oggetti complessi.

Nell’esempio seguente, crei una variabile Integer chiamata theAnswer con il valore 42 utilizzando il metodo valueOf:

  1. Integer theAnswer = Integer.valueOf(42);
  2. System.out.println(theAnswer);

In jshell, otterrai il seguente output:

Output
theAnswer ==> 42 42

Chiamando il metodo valueOf(42) di Integer, istruisci Java a fornirti un oggetto con questo valore. Nel backstage, Java verificherà se esiste già un oggetto con tale valore nella sua cache. Se esiste, l’oggetto sarà collegato alla variabile theAnswer. Se non esiste, verrà creato un nuovo oggetto per la variabile theAnswer.

Molte classi integrate forniscono tali metodi per motivi di prestazioni, e il loro utilizzo è consigliato, se non obbligatorio. Nel caso di Integer, potresti ancora creare un oggetto con la parola chiave new, ma otterrai un avviso di deprecazione.

Oltre alla classe String e ai wrapper, ci sono anche altri utili tipi di riferimento built-in, che puoi trovare nel riassunto del pacchetto java.lang. Per comprendere appieno alcuni di questi tipi di riferimento più avanzati, è necessaria un’ulteriore spiegazione o conoscenza preliminare. Ecco perché ne parleremo nei prossimi tutorial della serie Java.

Literali

I letterali rappresentano valori fissi che possono essere utilizzati direttamente nel codice e quindi possono essere assegnati sia ai tipi primitivi che ai tipi di riferimento. Esistono alcuni tipi di letterali, che possono essere categorizzati come segue.

Letterali di Tipo Primitivo

Hai già utilizzato alcuni letterali nella sezione sui tipi primitivi. Per ogni tipo primitivo, c’è un letterale, come quelli dei nostri esempi: 42, 'a', e true. Gli interi come 42 sono letterali interi. Allo stesso modo, i caratteri come 'a' sono letterali carattere, e true e false sono letterali booleani.

I tipi primitivi letterali possono anche essere utilizzati per creare valori per i tipi di riferimento. Il letterale int è stato utilizzato per creare un oggetto Integer con il codice Integer.valueOf(42). C’è anche una forma abbreviata per questo, e puoi assegnare il valore direttamente in questo modo:

Integer theAnswer = 42;

42 è un letterale intero, proprio come qualsiasi numero intero, e puoi assegnarlo direttamente alla variabile theAnswer senza ulteriori istruzioni. È comune vedere un Integer dichiarato in questo modo perché è conveniente.

Questo approccio abbreviato funziona anche per altri tipi primitivi letterali e i loro tipi di riferimento corrispondenti come Boolean, ad esempio:

Boolean isFun = true;

true è il letterale, che viene assegnato direttamente alla variabile isFun di tipo Boolean. C’è anche un letterale false, che puoi assegnare allo stesso modo.

Il Letterale di Stringa

C’è anche un letterale speciale per il tipo di riferimento String, ed è riconosciuto dalle virgolette doppie che circondano il suo valore. In questo esempio, è "Ciao, Mondo!":

String helloWorld = "Hello, World!";

Utilizzare i letterali è più semplice e più breve, ed è per questo che molti programmatori lo preferiscono. Tuttavia, è comunque possibile dichiarare una variabile String con un nuovo oggetto String, come hai già fatto nella sezione dei tipi di riferimento.

Il Letterale Null

C’è un altro letterale importante: null, che rappresenta l’assenza di un valore o la non esistenza di un oggetto. Null ti consente di creare un tipo di riferimento e di puntarlo a null invece di puntarlo a un oggetto. null può essere utilizzato per tutti i tipi di riferimento, ma non per i tipi primitivi.

C’è una nota da tenere presente con il letterale null: puoi dichiarare variabili con esso, ma non puoi utilizzare queste variabili fino a quando non assegni un valore idoneo e non nullo. Se provi a utilizzare una variabile di tipo riferimento con un valore null, otterrai un errore. Ecco un esempio:

  1. String initiallyNullString = null;
  2. System.out.println("The class name is: " + initiallyNullString.getClass());

Quando provi ad eseguire questo codice in jshell, vedrai un errore simile al seguente:

Output
initiallyNullString ==> null | Exception java.lang.NullPointerException | at (#4:1)

A seconda del tuo sistema operativo e della versione di Java, la tua output potrebbe variare.

Viene lanciato l’errore java.lang.NullPointerException perché stai cercando di invocare il metodo getClass() di String (che restituisce il nome della classe) sulla variabile initiallyNullString (che punta a un oggetto nullo).

Nota: Per semplicità, chiamiamo l’eccezione java.lang.NullPointerException un errore, anche se tecnicamente è un’eccezione. Per ulteriori informazioni su eccezioni ed errori, consulta il tutorial Gestione delle eccezioni in Java.

Per risolvere l’errore, devi riassegnare il valore di initiallyNullString in questo modo:

  1. String initiallyNullString = null;
  2. initiallyNullString = "not null any longer";
  3. System.out.println("The class name is: " + initiallyNullString.getClass());

Il nuovo codice corretto stamperà il seguente output:

Output
initiallyNullString ==> null initiallyNullString ==> "not null any longer" The class name is: class java.lang.String

L’output sopra mostra come initiallyNullString sia prima null, poi diventi un nuovo oggetto String contenente "non più nullo". Successivamente, quando il metodo getClass() viene invocato sull’oggetto istanziato, ottieni java.lang.String, in cui String è il nome della classe e java.lang è il suo package. Infine, viene stampato un messaggio completo e significativo: "Il nome della classe è: class java.lang.String".

Queste dichiarazioni di valore null sono più comuni per il codice legacy. Sono stati usati per creare una variabile prima e successivamente assegnarle il suo vero valore, di solito passando attraverso qualche logica che determina quest’ultimo. Tuttavia, a partire dalla versione 8 di Java, esiste un nuovo tipo di riferimento chiamato Optional, che è più adatto per i casi in cui null è stato utilizzato in precedenza.

Local Variable Type Inference

Fino ad ora, hai utilizzato alcuni dei tipi di dati comuni in Java per definire le variabili. Tuttavia, Java 10 ha introdotto una nuova funzionalità chiamata inferenza del tipo di variabile locale, che ti permette di utilizzare la parola chiave var davanti a una nuova variabile. Con questa funzionalità, Java dedurrà (ovvero, indovinerà automaticamente) il tipo di dati dal contesto locale. L’inferenza del tipo è controversa poiché contrasta con la verbosità precedentemente spiegata nella definizione delle variabili. I vantaggi e gli svantaggi di tale funzionalità sono oggetto di disputa, ma il fatto è che altri linguaggi a tipizzazione statica, come C++, supportano l’inferenza del tipo.

In ogni caso, l’inferenza del tipo non può sostituire completamente l’uso dei tipi di dati perché funziona solo con le variabili locali, ovvero le variabili all’interno di un metodo. Vediamo un esempio con var:

  1. var hello = "Hello";
  2. System.out.println(hello);

Dichiari la variabile hello con la parola chiave var per istruire Java a rilevarne il tipo di dati. Successivamente, la stampi sulla console nel modo usuale per confermare che funziona come previsto:

Ouput
hello ==> "Hello" Hello

Questo esempio funzionerà fintanto che la tua installazione di Java (più specificamente, il JDK) è di versione superiore alla 10. La parola chiave var non è supportata nelle versioni più vecchie.

Il processo di inferenza del tipo avviene durante il processo di compilazione, cioè quando si compila il codice. Il processo di compilazione trasforma il codice sorgente di testo normale in codice macchina e applica varie ottimizzazioni, compresa l’inferenza del tipo. Questo assicura che la quantità corretta di memoria di sistema sia disponibile per le variabili a tipo inferito. Pertanto, il codice macchina eseguito dopo la compilazione è completamente ottimizzato, come se avessi specificato manualmente tutti i tipi di dati.

In questo esempio, la parola chiave var funziona perché la variabile è locale, e il tipo di dati var funziona solo con le variabili locali. Le variabili locali sono definite all’interno dei metodi e sono accessibili solo all’interno dei metodi, motivo per cui vengono chiamate “locali”.

Per dimostrare che var può essere utilizzato solo per le variabili locali, prova a collocarlo al di fuori del metodo principale, come segue:

  1. public class Hello {
  2. var hello = "Hello";
  3. public static void main(String[] args) {
  4. // example code
  5. }
  6. }

Quando incolli il codice sopra in jshell, otterrai il seguente errore:

Output
| Error: | 'var' is not allowed here | var hello = "Hello"; | ^-^

var non è consentito lì perché hello è al di fuori di un metodo e non è più considerato locale. Pertanto, l’inferenza del tipo non funziona per le variabili non locali perché il contesto non può essere utilizzato in modo affidabile per rilevare il tipo di dati.

Anche se l’uso di var può essere impegnativo e non è obbligatorio, è probabile che tu lo incontri, quindi è utile saperne qualcosa.

Parole Chiave Riservate

Quando si dichiarano le variabili in Java, c’è una regola importante in più da conoscere. Ci sono parole chiave riservate che non è possibile utilizzare come nomi di variabili. Ad esempio, non è possibile dichiarare una variabile primitiva di tipo int e chiamarla new in questo modo:

  1. int new = 1;

Se si prova questo esempio, si otterranno errori di compilazione perché new è una parola chiave riservata.

Output
| Error: | '.class' expected | int new = 1; | ^ | Error: | <identifier> expected | int new = 1; | ^ | Error: | '(' or '[' expected | int new = 1; | ^ | Error: | unexpected type | required: value | found: class | int new = 1; | ^--^ | Error: | missing return statement | int new = 1; | ^----------^

La parola chiave new è utilizzata per creare nuovi oggetti e Java non si aspetta di trovarla in questa posizione. Nella lista degli errori nell’output precedente, la prima parte è la più importante:

Output
| Error: | '.class' expected | int new = 1; | ^

L’errore '.class' expected significa che quando si utilizza la parola chiave new, Java si aspetta che segua una classe. A questo punto, Java non è in grado di interpretare l’istruzione e seguono il resto degli errori.

Il resto delle parole chiave riservate, come abstract, continue, default, for, e break, hanno significati specifici in Java e non possono essere utilizzate come nomi di variabili. L’elenco completo delle parole chiave riservate può essere trovato sulla pagina delle Java Language Keywords. Anche se non si ricordano tutte le parole chiave riservate, è possibile utilizzare gli errori di compilazione per identificare il problema.

Conclusioni

In questo tutorial hai imparato sui tipi di dati primitivi e di riferimento in Java, che è un argomento complesso ma essenziale. Prenditi il tuo tempo per esercitarti e passare più volte gli esempi. Prova a cambiare alcuni dei tipi di dati e dei valori. Presta attenzione a quando vengono generati errori e quando no, al fine di sviluppare un senso per l’esecuzione di codice con successo.

Per saperne di più su Java, dai un’occhiata alla nostra serie Come Programmare in Java.

Source:
https://www.digitalocean.com/community/tutorials/understanding-data-types-in-java