JavaRush /Java Blog /Random-IT /Tipi primitivi in Java: non sono così primitivi
Viacheslav
Livello 3

Tipi primitivi in Java: non sono così primitivi

Pubblicato nel gruppo Random-IT

introduzione

Lo sviluppo di applicazioni può essere considerato come lavorare con alcuni dati, o meglio, memorizzarli ed elaborarli. Oggi vorrei toccare il primo aspetto fondamentale. Come vengono archiviati i dati in Java? Qui abbiamo due formati possibili: riferimento e tipi di dati primitivi . Parliamo dei tipi primitivi e delle possibilità di lavorare con essi (qualunque cosa si possa dire, questo è il fondamento della nostra conoscenza di un linguaggio di programmazione). I tipi di dati primitivi Java sono la base su cui poggia tutto. No, non sto affatto esagerando. Oracle ha un tutorial separato dedicato alle primitive: Tipi di dati primitivi Tipi primitivi in ​​Java: non sono così primitivi - 1 Un po' di storia. In principio era zero. Ma lo zero è noioso. E poi è apparso un po ' . Perché si chiamava così? Il suo nome deriva dall'abbreviazione “ binary digi t ” (numero binario). Cioè, ha solo due significati. E poiché era zero, è logico che ora sia 0 o 1. E la vita è diventata più divertente. I pezzetti cominciarono a riunirsi in stormi. E questi stormi iniziarono a essere chiamati byte (byte). Nel mondo moderno, byte = 2 alla terza potenza, cioè 8. Ma si scopre che non è sempre stato così. Ci sono molte ipotesi, leggende e voci sull'origine del nome byte. Alcune persone pensano che sia tutta una questione di codifiche di quel tempo, mentre altri pensano che fosse più redditizio leggere le informazioni in questo modo. Un byte è la più piccola porzione di memoria indirizzabile. Sono i byte che hanno indirizzi univoci in memoria. C'è una leggenda secondo cui ByTe è l'abbreviazione di Binary Term, una parola macchina. Parola macchina: in poche parole, questa è la quantità di dati che il processore può elaborare in un'unica operazione. In precedenza, la dimensione delle parole macchina era la stessa della memoria indirizzabile più piccola. In Java, le variabili possono memorizzare solo valori byte. Come ho detto sopra, ci sono due tipi di variabili in Java:
  • i tipi primitivi Java memorizzano direttamente il valore dei byte di dati (di seguito esamineremo i tipi di questi primitivi più in dettaglio);
  • un tipo reference, memorizza i byte dell'indirizzo dell'oggetto in Heap, cioè attraverso queste variabili otteniamo l'accesso direttamente all'oggetto stesso (una sorta di controllo remoto per l'oggetto)

Byte Java

Quindi la storia ci ha dato un byte, la quantità minima di memoria che possiamo utilizzare. Ed è composto da 8 bit. Il tipo di dati intero più piccolo in Java è byte. Questo è un tipo a 8 bit con segno. Cosa significa? Contiamo. 2^8 fa 256. Ma cosa succede se vogliamo un numero negativo? E gli sviluppatori Java hanno deciso che il codice binario "10000000" rappresenterà -128, cioè il bit più significativo (il bit più a sinistra) indicherà se il numero è negativo. Il binario “0111 1111” equivale a 127. Cioè, 128 non può essere designato in alcun modo, perché sarà -128. Il calcolo completo è riportato in questa risposta: Perché l'intervallo di byte è compreso tra -128 e 127 in Java? Per capire come si ottengono i numeri, dovresti guardare l'immagine:
Tipi primitivi in ​​Java: non sono così primitivi - 2
Di conseguenza, per calcolare la dimensione 2^(8-1) = 128. Ciò significa che il limite minimo (e ha un segno meno) sarà -128. E il massimo è 128 – 1 (sottrarre zero). Cioè, il massimo sarà 127. In effetti, non lavoriamo così spesso con il tipo di byte a un "livello alto". Fondamentalmente si tratta del trattamento dei dati “grezzi”. Ad esempio, quando si lavora con la trasmissione di dati su una rete, quando i dati sono un insieme di 0 e 1 trasmessi attraverso un canale di comunicazione. O durante la lettura dei dati dai file. Possono essere utilizzati anche quando si lavora con stringhe e codifiche. Codice di esempio:
public static void main(String []args){
        byte value = 2;
        byte shortByteValue = 0b10; // 2
        System.out.println(shortByteValue);
        // Начиная с JDK7 мы можем разделять литералы подчёркиваниями
        byte minByteValue = (byte) 0B1000_0000; // -128
        byte maxByteValue = (byte) 0b0111_1111; // 127
        byte minusByteValue = (byte) 0b1111_1111; // -128 + 127
        System.out.println(minusByteValue);
        System.out.println(minByteValue + " to " + maxByteValue);
}
A proposito, non pensare che l'utilizzo del tipo byte riduca il consumo di memoria. Il byte viene utilizzato principalmente per ridurre il consumo di memoria durante l'archiviazione dei dati negli array (ad esempio, l'archiviazione dei dati ricevuti sulla rete in un buffer, che verrà implementato come array di byte). Ma quando si eseguono operazioni sui dati, l'utilizzo di byte non soddisferà le tue aspettative. Ciò è dovuto all'implementazione della Java Virtual Machine (JVM). Poiché la maggior parte dei sistemi sono a 32 o 64 bit, byte e short durante i calcoli verranno convertiti in un int a 32 bit, di cui parleremo più avanti. Ciò semplifica i calcoli. Per maggiori dettagli, vedere L'aggiunta di byte viene convertita in int a causa delle regole del linguaggio Java o a causa di jvm? . La risposta contiene anche collegamenti a JLS (Java LanguageSpecific). Inoltre, l'utilizzo di byte nel posto sbagliato può portare a momenti imbarazzanti:
public static void main(String []args){
        for (byte i = 1; i <= 200; i++) {
            System.out.println(i);
        }
}
Ci sarà un loop qui. Poiché il valore del contatore raggiunge il massimo (127), si verificherà un overflow e il valore diventerà -128. E non usciremo mai dal ciclo.

corto

Il limite per i valori dei byte è piuttosto piccolo. Pertanto, per il tipo di dati successivo abbiamo deciso di raddoppiare il numero di bit. Cioè, ora non sono 8 bit, ma 16. Cioè 2 byte. I valori possono essere calcolati allo stesso modo. 2^(16-1) = 2^15 = 32768. Ciò significa che l'intervallo è compreso tra -32768 e 32767. Viene utilizzato molto raramente per casi speciali. Come ci dice la documentazione del linguaggio Java: “ puoi usare un breve per risparmiare memoria in array di grandi dimensioni ”.

int

Quindi siamo arrivati ​​al tipo più utilizzato. Occupa 32 bit o 4 byte. In generale, continuiamo a raddoppiare. L'intervallo di valori va da -2^31 a 2^31 – 1.

Valore intero massimo

Il valore massimo di int 2147483648 è 1, che non è affatto piccolo. Come detto sopra, per ottimizzare i calcoli, perché È più conveniente per i computer moderni, tenendo conto della loro capacità in bit, contare; i dati possono essere convertiti implicitamente in int. Ecco un semplice esempio:
byte a = 1;
byte b = 2;
byte result = a + b;
Codice così innocuo, ma riceviamo l'errore: "errore: tipi incompatibili: possibile conversione con perdita da int a byte". Dovrai correggerlo in byte result = (byte)(a + b); E un altro esempio innocuo. Cosa succede se eseguiamo il seguente codice?
int value = 4;
System.out.println(8/value);
System.out.println(9/value);
System.out.println(10/value);
System.out.println(11/value);
E arriveremo alla conclusione
2
2
2
2
*suoni di panico*
Il fatto è che quando si lavora con valori int, il resto viene scartato, lasciando solo l'intera parte (in questi casi è meglio usare double).

lungo

Continuiamo a raddoppiare. Moltiplichiamo 32 per 2 e otteniamo 64 bit. Per tradizione, questo è 4 * 2, ovvero 8 byte. L'intervallo di valori va da -2^63 a 2^63 – 1. Più che sufficiente. Questo tipo ti consente di contare numeri grandi e grandi. Spesso utilizzato quando si lavora con il tempo. O su lunghe distanze, per esempio. Per indicare che un numero è lungo, inserisci la lettera L – Long dopo il numero. Esempio:
long longValue = 4;
longValue = 1l; // Не ошибка, но плохо читается
longValue = 2L; // Идеально
Vorrei andare avanti con me stesso. Successivamente, considereremo il fatto che esistono wrapper corrispondenti per le primitive, che consentono di lavorare con le primitive come oggetti. Ma c'è una caratteristica interessante. Ecco un esempio: utilizzando lo stesso compilatore online Tutorialspoint, puoi controllare il seguente codice:
public class HelloWorld {

     public static void main(String []args) {
        printLong(4);
     }

    public static void printLong(long longValue) {
        System.out.println(longValue);
    }
}
Questo codice funziona senza errori, va tutto bene. Ma non appena il tipo nel metodo printLong viene sostituito da long a Long (ovvero il tipo non diventa primitivo, ma oggetto), a Java non è chiaro quale parametro stiamo passando. Inizia a presumere che venga trasmesso un int e si verificherà un errore. Pertanto, nel caso di metodo, sarà necessario indicare esplicitamente 4L. Molto spesso long viene utilizzato come ID quando si lavora con i database.

Java float e Java double

Questi tipi sono chiamati tipi a virgola mobile. Cioè, questi non sono tipi interi. Il tipo float è a 32 bit (come int) e double è chiamato tipo a doppia precisione, quindi è a 64 bit (moltiplica per 2, proprio come ci piace). Esempio:
public static void main(String []args){
        // float floatValue = 2.3; lossy conversion from double to float
        float floatValue = 2.3F;
        floatValue = 2.3f;
        double doubleValue = 2.3;
        System.out.println(floatValue);
        double cinema = 7D;
}
Ed ecco un esempio della differenza di valori (dovuta alla precisione del tipo):
public static void main(String []args){
        float piValue = (float)Math.PI;
        double piValueExt = Math.PI;
        System.out.println("Float value: " + piValue );
        System.out.println("Double value: " + piValueExt );
 }
Questi tipi primitivi vengono utilizzati, ad esempio, in matematica. Ecco la dimostrazione, una costante per calcolare il numero PI . Bene, in generale, puoi guardare l'API della classe Math. Ecco cos'altro dovrebbe essere importante e interessante: anche la documentazione dice: “ Questo tipo di dati non dovrebbe mai essere utilizzato per valori precisi, come la valuta. Per questo, dovrai invece utilizzare la classe java.math.BigDecimal. Numbers and Strings copre BigDecimal e altre classi utili fornite dalla piattaforma Java. " Cioè, non è necessario calcolare il denaro in float e double. Un esempio sulla precisione utilizzando l'esempio del lavoro presso la NASA: Java BigDecimal, Gestione dei calcoli ad alta precisione Bene, per provarlo tu stesso:
public static void main(String []args){
        float amount = 1.0000005F;
        float avalue = 0.0000004F;
        float result = amount - avalue;
        System.out.println(result);
}
Segui questo esempio, quindi aggiungi 0 prima dei numeri 5 e 4. E vedrai tutto l'orrore) C'è un rapporto interessante in russo su float e double sull'argomento: https://youtu.be/1RCn5ruN1fk Esempi di funzionamento con BigDecimal può essere visto qui: Guadagna centesimi con BigDecimal A proposito, float e double possono restituire più di un semplice numero. Ad esempio, l'esempio seguente restituirà Infinity:
public static void main(String []args){
        double positive_infinity = 12.0 / 0;
        System.out.println(positive_infinity);
}
E questo restituirà NAN:
public static void main(String []args){
        double positive_infinity = 12.0 / 0;
        double negative_infinity = -15.0 / 0;
        System.out.println(positive_infinity + negative_infinity);
}
È chiaro riguardo all’infinito. Cos'è NaN? Questo non è un numero , il che significa che il risultato non può essere calcolato e non è un numero. Ecco un esempio: vogliamo calcolare la radice quadrata di -4. La radice quadrata di 4 è 2. Cioè, 2 deve essere elevato al quadrato e poi otteniamo 4. Cosa deve essere elevato al quadrato per ottenere -4? Non funzionerà, perché... se c'è un numero positivo, rimarrà. E se fosse negativo, meno per meno darà un vantaggio. Cioè, non è calcolabile.
public static void main(String []args){
        double sqrt = Math.sqrt(-4);
        System.out.println(sqrt + 1);
        if (Double.isNaN(sqrt)) {
           System.out.println("So sad");
        }
        System.out.println(Double.NaN == sqrt);
}
Ecco un'altra fantastica panoramica sul tema dei numeri in virgola mobile: dov'è il tuo punto?
Cos'altro leggere:

Booleano Java

Il tipo successivo è booleano (tipo logico). Può accettare solo i valori true o false, che sono parole chiave. Utilizzato in operazioni logiche come i cicli while e nelle ramificazioni utilizzando if, switch. Quali cose interessanti puoi scoprire qui? Ebbene, ad esempio, in teoria abbiamo bisogno solo di 1 bit di informazione, 0 o 1, cioè vero o falso. Ma in realtà, Boolean occuperà più memoria e ciò dipenderà dalla specifica implementazione della JVM. In genere questo costa lo stesso di int. Un'altra opzione è utilizzare BitSet. Ecco una breve descrizione dal libro Java Fundamentals: BitSet

Carattere Java

Ora abbiamo raggiunto l'ultimo tipo primitivo. Quindi, i dati in char occupano 16 bit e descrivono il carattere. Java utilizza la codifica Unicode per char. Il simbolo può essere impostato secondo due tabelle (puoi vederlo qui ):
  • Tabella dei caratteri Unicode
  • Tabella dei caratteri ASCII
Tipi primitivi in ​​Java: non sono così primitivi - 3
Esempio in studio:
public static void main(String[] args) {
    char symbol = '\u0066'; // Unicode
    symbol = 102; // ASCII
    System.out.println(symbol);
}
A proposito, char, essendo essenzialmente un numero, supporta operazioni matematiche come sum. E a volte questo può portare a conseguenze divertenti:
public class HelloWorld{

    public static void main(String []args){
        String costForPrint = "5$";
        System.out.println("Цена только для вас " +
        + costForPrint.charAt(0) + getCurrencyName(costForPrint.charAt(1)));
    }

    public static String getCurrencyName(char symbol) {
        if (symbol == '$') {
            return " долларов";
        } else {
            throw new UnsupportedOperationException("Not implemented yet");
        }
    }

}
Consiglio vivamente di controllare l' IDE online da tutorialspoint . Quando ho visto questo puzzle in una delle conferenze, mi ha sollevato il morale. Spero che anche l'esempio vi piaccia) AGGIORNATO: Questo era al Joker 2017, rapporto: " Java Puzzlers NG S03 - Da dove venite tutti?! "

Letterali

Un valore letterale è un valore specificato esplicitamente. Utilizzando i valori letterali, puoi specificare valori in diversi sistemi numerici:
  • Sistema decimale: 10
  • Sistema esadecimale: 0x1F4, inizia con 0x
  • Sistema ottale: 010, inizia da zero.
  • Sistema binario (da Java7): 0b101, inizia da 0b
Mi soffermerei un po’ di più sul sistema ottale, perché è divertente:
int costInDollars = 08;
Questa riga di codice non verrà compilata:
error: integer number too large: 08
Sembra una sciocchezza. Ora ricordiamo i sistemi binario e ottale. Non ce n'è due nel sistema binario, perché i valori sono due (a partire da 0). E il sistema ottale ha 8 valori, partendo da zero. Cioè, il valore 8 stesso non esiste. Si tratta quindi di un errore che a prima vista sembra assurdo. E da ricordare, ecco la regola di “follow-up” per la traduzione dei valori:
Tipi primitivi in ​​Java: non sono così primitivi - 4

Classi wrapper

Le primitive in Java hanno le proprie classi wrapper in modo che tu possa lavorarci come oggetti. Cioè, per ogni tipo primitivo esiste un tipo di riferimento corrispondente. Tipi primitivi in ​​Java: non sono così primitivi - 5Le classi wrapper sono immutabili: ciò significa che una volta creato un oggetto, il suo stato, ovvero il valore del campo valore, non può essere modificato. Le classi wrapper sono dichiarate finali: oggetti, per così dire, di sola lettura. Vorrei anche menzionare che non è possibile ereditare da queste classi. Java effettua automaticamente conversioni tra i tipi primitivi e i relativi wrapper:
Integer x = 9;          // autoboxing
int n = new Integer(3); // unboxing
Il processo di conversione dei tipi primitivi in ​​tipi di riferimento (int->Integer) è chiamato autoboxing e il processo inverso è chiamato unboxing . Queste classi rendono possibile salvare una primitiva all'interno di un oggetto e l'oggetto stesso si comporterà come un oggetto (beh, come qualsiasi altro oggetto). Con tutto ciò, otteniamo un gran numero di metodi statici vari e utili, come confrontare numeri, convertire un simbolo in maiuscole e minuscole, determinare se un simbolo è una lettera o un numero, cercare il numero minimo, ecc. L'insieme di funzionalità fornito dipende solo dal wrapper stesso. Un esempio della tua implementazione di un wrapper per int:
public class CustomerInt {

   private final int value;

   public CustomerInt(int value) {
       this.value = value;
   }

   public int getValue() {
       return value;
   }
}
Il pacchetto principale, java.lang, ha già implementazioni delle classi Boolean, Byte, Short, Character, Integer, Float, Long, Double, e non dobbiamo creare nulla di nostro, ma basta riutilizzare quelle già pronte quelli. Ad esempio, tali classi ci danno la possibilità di creare, ad esempio, una Lista , perché una List dovrebbe contenere solo oggetti, cosa che le primitive non lo sono. Per convertire un valore di tipo primitivo, esistono metodi statici valueOf, ad esempio Integer.valueOf(4) restituirà un oggetto di tipo Integer. Per la conversione inversa ci sono metodi intValue(), longValue(), ecc. Il compilatore inserisce da solo le chiamate a valueOf e *Value, questa è l'essenza dell'autoboxing e dell'autounboxing. Come appare in realtà l'esempio di autopacking e autounpacking presentato sopra:
Integer x = Integer.valueOf(9);
int n = new Integer(3).intValue();
Puoi leggere ulteriori informazioni sull'autopacking e sull'autounpacking in questo articolo .

Lancio

При работе с примитивами существует такое понятие How приведение типов, одно из не очень приятных свойств C++, тем не менее приведение типов сохранено и в языке Java. Иногда мы сталкиваемся с такими ситуациями, когда нам нужно совершать взаимодействия с данными разных типов. И очень хорошо, что в некоторых ситуациях это возможно. В случае с ссылочными переменными, там свои особенности, связанные с полиморфизмом и наследованием, но сегодня мы рассматриваем простые типы и соответственно приведение простых типов. Существует преобразование с расширением и преобразование сужающее. Всё на самом деле просто. Если тип данных становится больше (допустим, был int, а стал long), то тип становится шире (из 32 бит становится 64). И в этом случае мы не рискуем потерять данные, т.к. если влезло в int, то в long влезет тем более, поэтому данное приведение мы не замечаем, так How оно осуществляется автоматически. А вот в обратную сторону преобразование требует явного указания от нас, данное приведение типа называется — сужение. Так сказать, чтобы мы сами сказали: «Да, я даю себе отчёт в этом. В случае чего — виноват сам».
public static void main(String []args){
   int intValue = 128;
   byte value = (byte)intValue;
   System.out.println(value);
}
Whatбы потом в таком случае не говорor что «Ваша Джава плохая», когда получат внезапно -128 instead of 128 ) Мы ведь помним, что в byteе 127 верхнее meaning и всё что находилось выше него соответственно можно потерять. Когда мы явно превратor наш int в byte, то произошло переполнение и meaning стало -128.

Область видимости

Это то место в codeе, где данная переменная будет выполнять свои функции и хранить в себе Howое-то meaning. Когда же эта область закончится, переменная перестанет существовать и будет стерта из памяти и. How уже можно догадаться, посмотреть or получить ее meaning будет невозможно! Так что же это такое — область видимости? Tipi primitivi in ​​Java: non sono così primitivi - 6Область определяется "блоком" — вообще всякой областью, замкнутой в фигурные скобки, выход за которые сулит удаление данных объявленных в ней. Или How минимум — сокрытие их от других блоков, открытых вне текущего. В Java область видимости определяется двумя основными способами:
  • Классом.
  • Методом.
Как я и сказал, переменная не видна codeу, если она определена за пределами блока, в котором она была инициализирована. Смотрим пример:
int x;
x = 6;
if (x >= 4) {
   int y = 3;
}
x = y;// переменная y здесь не видна!
И How итог мы получим ошибку:

Error:(10, 21) java: cannot find symbol
  symbol:   variable y
  location: class com.javaRush.test.type.Main
Области видимости могут быть вложенными (если мы объявor переменную в первом, внешнем блоке, то во внутреннем она будет видна).

Заключение

Сегодня мы познакомorсь с восемью примитивными типами в Java. Эти типы можно разделить на четыре группы:
  • Целые числа: byte, short, int, long — представляют собой целые числа со знаком.
  • Числа с плавающей точкой — эта группа включает себе float и double — типы, которые хранят числа с точностью до определённого знака после запятой.
  • Булевы значения — boolean — хранят значения типа "истина/ложь".
  • Caratteri : questo gruppo include il tipo char.
Come ha mostrato il testo sopra, le primitive in Java non sono così primitive e consentono di risolvere molti problemi in modo efficace. Ma questo introduce anche alcune funzionalità che dovremmo tenere a mente se non vogliamo incontrare comportamenti imprevedibili nel nostro programma. Come si suol dire, devi pagare tutto. Se vogliamo una primitiva con un intervallo “ripido” (ampio) - qualcosa come lungo - sacrifichiamo l'allocazione di una porzione di memoria più grande e nella direzione opposta. Risparmiando memoria e utilizzando i byte, otteniamo un intervallo limitato da -128 a 127.
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION