JavaRush /Java Blog /Random-IT /Assegnazione e inizializzazione in Java
Viacheslav
Livello 3

Assegnazione e inizializzazione in Java

Pubblicato nel gruppo Random-IT

introduzione

Lo scopo principale dei programmi informatici è l'elaborazione dei dati. Per elaborare i dati è necessario memorizzarli in qualche modo. Propongo di capire come vengono archiviati i dati.
Assegnazione e inizializzazione in Java - 1

Variabili

Le variabili sono contenitori che memorizzano qualsiasi dato. Diamo un'occhiata al tutorial ufficiale di Oracle: Dichiarazione delle variabili membro . Secondo questo Tutorial, esistono diversi tipi di variabili:
  • Campi : variabili dichiarate nella classe;
  • Variabili locali : variabili in un metodo o blocco di codice;
  • Parametri : variabili nella dichiarazione del metodo (nella firma).
Tutte le variabili devono avere un tipo di variabile e un nome di variabile.
  • Il tipo di una variabile indica quali dati rappresenta la variabile (ovvero, quali dati può archiviare). Come sappiamo, il tipo di una variabile può essere primitivo (primitives primitives ) oppure oggetto , non primitivo (Non-primitive). Con le variabili oggetto, il loro tipo è descritto da una classe specifica.
  • Il nome della variabile deve essere minuscolo, in camel case. Puoi leggere ulteriori informazioni sulla denominazione in " Variabili:Nominazione ".
Inoltre, se una variabile a livello di classe, ad es. è un campo di classe, per esso è possibile specificare un modificatore di accesso. Per ulteriori dettagli vedere Controllo dell'accesso ai membri di una classe .

Dichiarazione variabile

Quindi, ricordiamo cos'è una variabile. Per iniziare a lavorare con una variabile, è necessario dichiararla. Per prima cosa, diamo un'occhiata a una variabile locale. Invece di un IDE, per comodità, utilizzeremo la soluzione online di tutorialspoint: Online IDE . Eseguiamo questo semplice programma nel loro IDE online:
public class HelloWorld{
    public static void main(String []args){
        int number;
        System.out.println(number);
    }
}
Quindi, come puoi vedere, abbiamo dichiarato una variabile locale con nome numbere tipo int. Premiamo il pulsante “Esegui” e riceviamo l’errore:
HelloWorld.java:5: error: variable number might not have been initialized
        System.out.println(number);
Quello che è successo? Abbiamo dichiarato una variabile, ma non ne abbiamo inizializzato il valore. Vale la pena notare che questo errore non si è verificato in fase di esecuzione (cioè non in runtime), ma in fase di compilazione. Il compilatore intelligente ha controllato se la variabile locale sarebbe stata inizializzata prima di accedervi o meno. Pertanto da ciò conseguono le seguenti affermazioni:
  • È necessario accedere alle variabili locali solo dopo che sono state inizializzate;
  • Le variabili locali non hanno valori predefiniti;
  • I valori delle variabili locali vengono controllati in fase di compilazione.
Quindi, ci viene detto che la variabile deve essere inizializzata. Inizializzare una variabile significa assegnare un valore a una variabile. Scopriamo allora di cosa si tratta e perché.

Inizializzazione di una variabile locale

L'inizializzazione delle variabili è uno degli argomenti più complicati in Java, perché... è strettamente correlato al lavoro con la memoria, all'implementazione JVM, alle specifiche JVM e ad altre cose altrettanto spaventose e complicate. Ma puoi provare a capirlo almeno in una certa misura. Passiamo dal semplice al complesso. Per inizializzare la variabile, utilizzeremo l'operatore di assegnazione e cambieremo la riga nel nostro codice precedente:
int number = 2;
In questa opzione non ci saranno errori e il valore verrà visualizzato sullo schermo. Cosa succede in questo caso? Proviamo a ragionare. Se vogliamo assegnare un valore a una variabile, allora vogliamo che quella variabile memorizzi un valore. Si scopre che il valore deve essere memorizzato da qualche parte, ma dove? Su disco? Ma questo è molto lento e potrebbe imporci delle restrizioni. Si scopre che l’unico posto in cui possiamo archiviare i dati in modo rapido ed efficiente “qui e ora” è la memoria. Ciò significa che dobbiamo allocare spazio in memoria. Questo è vero. Quando una variabile viene inizializzata, le verrà assegnato spazio nella memoria assegnata al processo Java all'interno del quale verrà eseguito il nostro programma. La memoria allocata a un processo Java è divisa in diverse aree o zone. Quale di essi assegnerà lo spazio dipende dal tipo di variabile dichiarata. La memoria è divisa nelle seguenti sezioni: Heap, Stack e Non-Heap . Cominciamo con la memoria dello stack. Stack è tradotto come una pila (ad esempio, una pila di libri). È una struttura dati LIFO (Last In, First Out). Cioè, come una pila di libri. Quando aggiungiamo libri, li mettiamo in cima e quando li togliamo, prendiamo quello in alto (cioè quello che è stato aggiunto più recentemente). Quindi lanciamo il nostro programma. Come sappiamo, un programma Java viene eseguito da una JVM, cioè una macchina virtuale Java. La JVM deve sapere dove dovrebbe iniziare l'esecuzione del programma. Per fare ciò, dichiariamo un metodo main, chiamato “punto di ingresso”. Per l'esecuzione nella JVM viene creato un thread principale (Thread). Quando viene creato un thread, gli viene allocato il proprio stack in memoria. Questo stack è costituito da frame. Quando ogni nuovo metodo viene eseguito in un thread, gli verrà assegnato un nuovo frame e aggiunto in cima allo stack (come un nuovo libro in una pila di libri). Questo frame conterrà riferimenti a oggetti e tipi primitivi. Sì, sì, il nostro int verrà memorizzato nello stack, perché... int è un tipo primitivo. Prima di allocare un frame, la JVM deve capire cosa salvare lì. È per questo motivo che riceveremo l'errore "la variabile potrebbe non essere stata inizializzata", perché se non è inizializzata, la JVM non sarà in grado di preparare lo stack per noi. Pertanto, durante la compilazione di un programma, un compilatore intelligente ci aiuterà a evitare di commettere errori e di rompere tutto. (!) Per chiarezza, consiglio un articolo super duper : " Java Stack and Heap: Java Memory Allocation Tutorial ". Si collega a un video altrettanto interessante:
Una volta completata l'esecuzione di un metodo, i frame allocati per questi metodi verranno eliminati dallo stack del thread e insieme ad essi verrà cancellata la memoria allocata per questo frame con tutti i dati.

Inizializzazione delle variabili oggetto locali

Cambiamo nuovamente il nostro codice in un po' più complicato:
public class HelloWorld{

    private int number = 2;

    public static void main(String []args){
        HelloWorld object = new HelloWorld();
        System.out.println(object.number);
    }

}
Cosa succederà qui? Parliamone ancora. La JVM sa da dove dovrebbe eseguire il programma, ad es. vede il metodo principale. Crea un thread e gli alloca memoria (dopo tutto, un thread deve memorizzare da qualche parte i dati necessari per l'esecuzione). In questo thread viene allocato un frame per il metodo main. Successivamente creiamo un oggetto HelloWorld. Questo oggetto non viene più creato nello stack, ma nell'heap. Perché object non è un tipo primitivo, ma un tipo di oggetto. E lo stack memorizzerà solo un riferimento all'oggetto nell'heap (dobbiamo in qualche modo accedere a questo oggetto). Successivamente, nello stack del metodo main, verranno allocati i frame per l'esecuzione del metodo println. Dopo aver eseguito il metodo principale, tutti i frame verranno distrutti. Se il frame viene distrutto, tutti i dati verranno distrutti. L'oggetto oggetto non verrà distrutto immediatamente. Innanzitutto il riferimento ad esso verrà distrutto e quindi nessuno farà più riferimento all'oggetto oggetto e l'accesso a questo oggetto in memoria non sarà più possibile. Una JVM intelligente ha il proprio meccanismo per questo: un garbage collector (garbage collector o GC in breve). Quindi rimuove dalla memoria gli oggetti a cui nessun altro fa riferimento. Questo processo è stato nuovamente descritto nel collegamento sopra indicato. C'è anche un video con la spiegazione.

Inizializzazione dei campi

L'inizializzazione dei campi specificati in una classe avviene in modo particolare a seconda che il campo sia statico o meno. Se un campo ha la parola chiave static, allora questo campo si riferisce alla classe stessa e se la parola static non è specificata, allora questo campo si riferisce a un'istanza della classe. Vediamolo con un esempio:
public class HelloWorld{
    private int number;
    private static int count;

    public static void main(String []args){
        HelloWorld object = new HelloWorld();
        System.out.println(object.number);
    }
}
In questo esempio, i campi vengono inizializzati in momenti diversi. Il campo numerico verrà inizializzato dopo la creazione dell'oggetto classe HelloWorld. Ma il campo count verrà inizializzato quando la classe verrà caricata dalla Java virtual machine. Il caricamento delle classi è un argomento separato, quindi non lo mescoleremo qui. Vale solo la pena sapere che le variabili statiche vengono inizializzate quando la classe diventa nota in fase di esecuzione. Qualcos'altro è più importante qui e lo hai già notato. Non abbiamo specificato il valore da nessuna parte, ma funziona. E senza dubbio. Le variabili che sono campi, se non hanno un valore specificato, vengono inizializzate con un valore predefinito. Per i valori numerici questo è 0 o 0,0 per i numeri in virgola mobile. Per booleano questo è falso. E per tutte le variabili di tipo oggetto il valore sarà nullo (ne parleremo più avanti). Sembrerebbe, perché è così? Ma perché gli oggetti vengono creati in Heap (nell'heap). Il lavoro con quest'area viene eseguito in Runtime. E possiamo inizializzare queste variabili in fase di esecuzione, a differenza dello stack, la cui memoria deve essere preparata prima dell'esecuzione. Ecco come funziona la memoria in Java. Ma c'è un'altra caratteristica qui. Questo piccolo pezzo tocca diversi angoli della memoria. Come ricordiamo, un frame viene allocato nella memoria Stack per il metodo main. Questo frame memorizza un riferimento a un oggetto nella memoria Heap. Ma allora dove viene memorizzato il conteggio? Come ricordiamo, questa variabile viene inizializzata immediatamente, prima che l'oggetto venga creato nell'heap. Questa è una domanda davvero complicata. Prima di Java 8 esisteva un'area di memoria chiamata PERMGEN. A partire da Java 8, quest'area è cambiata e si chiama METASPACE. Essenzialmente, le variabili statiche fanno parte della definizione della classe, ad es. i suoi metadati. Pertanto, è logico che siano archiviati nel repository di metadati, METASPACE. MetaSpace appartiene alla stessa area di memoria Non-Heap e ne fa parte. È anche importante tenere presente che viene preso in considerazione l'ordine in cui le variabili vengono dichiarate. Ad esempio, c'è un errore in questo codice:
public class HelloWorld{

    private static int b = a;
    private static int a = 1;

    public static void main(String []args){
        System.out.println(b);
    }

}

Ciò che è nullo

Come detto sopra, le variabili dei tipi di oggetto, se sono campi di una classe, vengono inizializzate su valori predefiniti e tale valore predefinito è nullo. Ma cosa è nullo in Java? La prima cosa da ricordare è che i tipi primitivi non possono essere nulli. E tutto perché null è un riferimento speciale che non si riferisce da nessuna parte, a nessun oggetto. Pertanto, solo una variabile oggetto può essere nulla. La seconda cosa importante da capire è che null è un riferimento. Anche i riferimenti hanno il loro peso. Su questo argomento puoi leggere la domanda su StackOverflow: " La variabile nulla richiede spazio in memoria ".

Blocchi di inizializzazione

Quando si considera l'inizializzazione delle variabili, sarebbe un peccato non considerare i blocchi di inizializzazione. Sembra questo:
public class HelloWorld{

    static {
        System.out.println("static block");
    }

    {
        System.out.println("block");
    }

    public HelloWorld () {
        System.out.println("Constructor");
    }

    public static void main(String []args){
        HelloWorld obj = new HelloWorld();
    }

}
L'ordine di output sarà: blocco statico, blocco, Costruttore. Come possiamo vedere, i blocchi di inizializzazione vengono eseguiti prima del costruttore. E a volte questo può essere un mezzo conveniente di inizializzazione.

Conclusione

Spero che questa breve panoramica sia stata in grado di fornire alcune informazioni su come funziona e perché. #Viacheslav
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION