CIAO! Man mano che avanzi in CodeGym, ti sei imbattuto in tipi primitivi molte volte. Ecco un breve elenco di ciò che sappiamo su di loro:
- Non sono oggetti e rappresentano un valore memorizzato
- Ci sono diversi tipi
- Numeri interi: byte , short , int , long
- Numeri in virgola mobile (frazionari): float e double
- Valori logici: booleani
- Valori simbolici (per rappresentare lettere e numeri): char
-
Ogni tipo ha il proprio intervallo di valori:
Tipo primitivo |
Dimensioni in memoria |
Intervallo di valori |
byte |
8 bit |
-128 a 127 |
corto |
16 bit |
-32768 a 32767 |
char |
16 bit |
da 0 a 65536 |
int |
32 bit |
Da -2147483648 a 2147483647 |
lungo |
64 bit |
-9223372036854775808 a 9223372036854775807 |
galleggiante |
32 bit |
(2 elevato a -149) a ((2 - (2 elevato a -23)) * 2 elevato a 127) |
Doppio |
64 bit |
(-2 elevato a 63) a ((2 elevato a 63) - 1) |
booleano |
8 (se utilizzato negli array), 32 (se non utilizzato negli array) |
vero o falso |
Ma oltre ad avere valori diversi, differiscono anche per quanto spazio occupano nella memoria. Un
int occupa più di un byte. E un
lungo è più grande di uno corto. La quantità di memoria occupata dai primitivi può essere paragonata alle matrioske russe:
![Ampliamento e restringimento dei tipi primitivi - 2]()
ogni matrioska ha spazio disponibile all'interno. Più grande è la bambola da nidificazione, più spazio c'è. Una grande bambola da nidificazione (
lunga ) ospiterà facilmente una
bambola più piccola . Si adatta facilmente e non è necessario fare nient'altro. In Java, quando si lavora con le primitive, si parla di conversione implicita. In altre parole, si chiama allargamento.
Ampliamento in Java
Ecco un semplice esempio di conversione di ampliamento:
public class Main {
public static void main(String[] args) {
int bigNumber = 10000000;
byte littleNumber = 16;
bigNumber = littleNumber;
System.out.println(bigNumber);
}
}
Qui assegniamo un valore in byte a una variabile
int . L'assegnazione riesce senza problemi: il valore memorizzato in un byte occupa meno memoria di quella che un
int può ospitare. La piccola bambola da nidificazione (valore in byte) si adatta facilmente alla grande bambola da nidificazione ( variabile
int ). È una questione diversa se si tenta di fare il contrario, ovvero inserire un valore elevato in una variabile il cui intervallo non può contenere un tipo di dati così grande. Con vere bambole nidificanti, il numero semplicemente non si adatterebbe. Con Java, può, ma con sfumature. Proviamo a inserire un
int in una variabile
breve :
public static void main(String[] args) {
int bigNumber = 10000000;
short littleNumber = 1000;
littleNumber = bigNumber;
System.out.println(bigNumber);
}
Errore! Il compilatore capisce che stai cercando di fare qualcosa di anormale spingendo una grande bambola da nidificazione (
int ) all'interno di una piccola (
short ). In questo caso, l'errore di compilazione è un avviso del compilatore: "Ehi, sei
assolutamente sicuro di volerlo fare?" Se sei sicuro, allora dici al compilatore:
"Va tutto bene. So cosa sto facendo!" Questo processo è chiamato conversione esplicita del tipo o restringimento.
Restringimento in Java
Per eseguire una conversione di restringimento, è necessario indicare esplicitamente il tipo in cui si desidera convertire il valore. In altre parole, devi rispondere alla domanda del compilatore:
"Bene, in quale di queste piccole bambole da nidificazione vuoi mettere questa grande bambola da nidificazione?" Nel nostro caso, assomiglia a questo:
public static void main(String[] args) {
int bigNumber = 10000000;
short littleNumber = 1000;
littleNumber = (short) bigNumber;
System.out.println(littleNumber);
}
Indichiamo esplicitamente che vogliamo inserire un
int in una variabile
short e che ci assumeremo la responsabilità. Visto che è stato esplicitamente indicato un tipo più ristretto, il compilatore esegue la conversione. Qual è il risultato? Output della console:
-27008 È stato un po' inaspettato. Perché l'abbiamo capito esattamente? In effetti, è tutto molto semplice. Originariamente, il valore era 10000000. Era memorizzato in una variabile
int , che occupa 32 bit. Questa è la sua rappresentazione binaria:
Scriviamo questo valore in una variabile
breve , che può memorizzare solo 16 bit! Di conseguenza, solo i primi 16 bit del nostro numero verranno spostati lì. Il resto verrà scartato. Di conseguenza, la variabile breve riceve il seguente valore
che in forma decimale è uguale a -27008 Ecco perché il compilatore chiede di "confermare" indicando una conversione di restringimento esplicita a un tipo specifico. Innanzitutto, questo dimostra che ti stai assumendo la responsabilità del risultato. E in secondo luogo, dice al compilatore quanto spazio allocare quando avviene la conversione. Dopotutto, nell'ultimo esempio, se assegnassimo un valore int a una variabile byte anziché short
, allora avremmo a disposizione solo 8 bit, non 16, e il risultato sarebbe diverso. I tipi frazionari (
float e
double ) hanno il proprio processo per restringere le conversioni. Se provi a trasmettere un numero di fazione a un tipo intero, la parte frazionaria verrà scartata.
public static void main(String[] args) {
double d = 2.7;
long x = (int) d;
System.out.println(x);
}
Uscita console:
2
char
Sai già che
char viene utilizzato per visualizzare singoli caratteri.
public static void main(String[] args) {
char c = '!';
char z = 'z';
char i = '8';
}
Ma questo tipo di dati ha diverse caratteristiche che è importante comprendere. Diamo un'altra occhiata alla tabella degli intervalli di valori:
Tipo primitivo |
Dimensioni in memoria |
Intervallo di valori |
byte |
8 bit |
-128 a 127 |
corto |
16 bit |
-32768 a 32767 |
char |
16 bit |
da 0 a 65536 |
int |
32 bit |
Da -2147483648 a 2147483647 |
lungo |
64 bit |
-9223372036854775808 a 9223372036854775807 |
galleggiante |
32 bit |
(2 elevato a -149) a ((2 - (2 elevato a -23)) * 2 elevato a 127) |
Doppio |
64 bit |
(-2 elevato a 63) a ((2 elevato a 63) - 1) |
booleano |
8 (se utilizzato negli array), 32 (se non utilizzato negli array) |
vero o falso |
L'intervallo da 0 a 65536 è indicato per il tipo
di carattere . Ma cosa significa? Dopotutto, un
carattere non rappresenta solo numeri, ma anche lettere, segni di punteggiatura... Il fatto è che in Java i valori
dei caratteri sono memorizzati in formato Unicode. Abbiamo già incontrato Unicode in una delle lezioni precedenti. Probabilmente ricorderai che Unicode è uno standard di codifica dei caratteri che include i simboli di quasi tutte le lingue scritte del mondo. In altre parole, è un elenco di codici speciali che rappresentano quasi ogni carattere in qualsiasi lingua. L'intera tabella Unicode è molto grande e, ovviamente, non è necessario impararla a memoria. Eccone una piccola parte:
![Ampliamento e restringimento dei tipi primitivi - 5]()
La cosa principale è capire come vengono memorizzati i caratteri e ricordare che se conosci il codice di un particolare carattere, puoi sempre produrre quel carattere nel tuo programma. Proviamo con un numero casuale:
public static void main(String[] args) {
int x = 32816;
char c = (char) x ;
System.out.println(c);
}
Output della console: 耰 Questo è il formato utilizzato per memorizzare i
caratteri in Java. Ogni simbolo corrisponde a un numero: un codice numerico a 16 bit (due byte). In Unicode, 32816 corrisponde al carattere cinese 耰.
![Ampliamento e restringimento dei tipi primitivi - 5]()
Prendere nota del seguente punto. In questo esempio, abbiamo utilizzato una variabile
int . Occupa 32 bit in memoria, mentre un
carattere ne occupa 16. Qui abbiamo scelto un
int , perché il nostro numero (32816) non starebbe in un
short . Sebbene la dimensione di un
carattere (proprio come un
short ) sia di 16 bit, non ci sono numeri negativi nell'intervallo
di caratteri , quindi la parte "positiva" del
caratterela portata è doppia (65536 invece di 32767 per il tipo
corto ). Possiamo usare un
int fintanto che il nostro codice rimane al di sotto di 65536. Ma se crei un valore
int maggiore di 65536, occuperà più di 16 bit. E questo si tradurrà in una conversione restringente
char c = (char) x;
i bit extra verranno scartati (come discusso sopra) e il risultato sarà del tutto inaspettato.
Funzioni speciali dell'aggiunta di caratteri e numeri interi
Facciamo un esempio insolito:
public class Main {
public static void main(String[] args) {
char c = '1';
int i = 1;
System.out.println(i + c);
}
}
Uscita console:
50 O_О Che senso ha? 1+1. Da dove vengono i 50?! Sai già che
char
i valori sono archiviati in memoria come numeri nell'intervallo da 0 a 65536 e che questi numeri sono una rappresentazione Unicode di un carattere.
![Ampliamento e restringimento dei tipi primitivi - 6]()
Quando aggiungiamo un
carattere e un tipo di numero intero, il
carattere viene convertito nel numero Unicode corrispondente. Nel nostro codice, quando abbiamo aggiunto 1 e '1', il simbolo '1' è stato convertito nel proprio codice, che è 49 (puoi verificarlo nella tabella sopra). Pertanto, il risultato è 50. Prendiamo ancora una volta il nostro vecchio amico 耰 come esempio e proviamo ad aggiungerlo a un numero.
public static void main(String[] args) {
char c = '耰';
int x = 200;
System.out.println(c + x);
}
Output della console:
33016 Abbiamo già scoperto che 耰 corrisponde a 32816. E quando aggiungiamo questo numero e 200, otteniamo il nostro risultato: 33016. :) Come puoi vedere, l'algoritmo qui è abbastanza semplice, ma non dovresti dimenticarlo .
GO TO FULL VERSION