Oggetti e array

Benvenuti a un'altra edizione di Under The Hood . Questa colonna si concentra sulle tecnologie di base di Java. Ha lo scopo di dare agli sviluppatori un'idea dei meccanismi che fanno funzionare i loro programmi Java. L'articolo di questo mese dà un'occhiata ai bytecode che si occupano di oggetti e array.

Macchina orientata agli oggetti

La Java virtual machine (JVM) funziona con i dati in tre forme: oggetti, riferimenti a oggetti e tipi primitivi. Gli oggetti risiedono nell'heap di Garbage Collection. I riferimenti agli oggetti e i tipi primitivi risiedono nello stack Java come variabili locali, nell'heap come variabili di istanza degli oggetti o nell'area del metodo come variabili di classe.

Nella Java virtual machine, la memoria viene allocata nell'heap raccolto in modo indesiderato solo come oggetti. Non è possibile allocare memoria per un tipo primitivo sull'heap, tranne come parte di un oggetto. Se si desidera utilizzare un tipo primitivo in cui Objectè necessario un riferimento, è possibile allocare un oggetto wrapper per il tipo dal java.langpacchetto. Ad esempio, esiste una Integerclasse che avvolge un inttipo con un oggetto. Solo i riferimenti a oggetti e i tipi primitivi possono risiedere nello stack Java come variabili locali. Gli oggetti non possono mai risiedere nello stack Java.

La separazione architetturale di oggetti e tipi primitivi nella JVM si riflette nel linguaggio di programmazione Java, in cui gli oggetti non possono essere dichiarati come variabili locali. Solo i riferimenti a oggetti possono essere dichiarati come tali. Dopo la dichiarazione, un riferimento a un oggetto non fa riferimento a nulla. Solo dopo che il riferimento è stato esplicitamente inizializzato, con un riferimento a un oggetto esistente o con una chiamata a new, il riferimento si riferisce a un oggetto reale.

Nel set di istruzioni JVM, tutti gli oggetti vengono istanziati e vi si accede con lo stesso set di codici operativi, ad eccezione degli array. In Java, gli array sono oggetti a tutti gli effetti e, come qualsiasi altro oggetto in un programma Java, vengono creati dinamicamente. I riferimenti agli array possono essere utilizzati ovunque sia richiesto un riferimento al tipo Objecte qualsiasi metodo di Objectpuò essere richiamato su un array. Tuttavia, nella Java virtual machine, gli array vengono gestiti con speciali bytecode.

Come con qualsiasi altro oggetto, gli array non possono essere dichiarati come variabili locali; solo i riferimenti ad array possono. Gli stessi oggetti array contengono sempre un array di tipi primitivi o un array di riferimenti a oggetti. Se dichiari un array di oggetti, ottieni un array di riferimenti a oggetti. Gli oggetti stessi devono essere creati esplicitamente newe assegnati agli elementi della matrice.

Codici operativi per gli oggetti

La creazione di istanze di nuovi oggetti viene eseguita tramite

new

codice operativo. Due operandi di un byte seguono il

new

codice operativo. Questi due byte vengono combinati per formare un indice a 16 bit nel pool di costanti. L'elemento del pool di costanti all'offset specificato fornisce informazioni sulla classe del nuovo oggetto. La JVM crea una nuova istanza dell'oggetto sull'heap e inserisce il riferimento al nuovo oggetto sullo stack, come mostrato di seguito.

Creazione di oggetti
Codice operativo Operando (i) Descrizione
new indexbyte1, indexbyte2 crea un nuovo oggetto sull'heap, spinge il riferimento

La tabella successiva mostra i codici operativi che inseriscono e ottengono i campi oggetto. Questi codici operativi, putfield e getfield, operano solo sui campi che sono variabili di istanza. Alle variabili statiche si accede da putstatic e getstatic, descritti in seguito. Le istruzioni putfield e getfield accettano ciascuna due operandi di un byte. Gli operandi vengono combinati per formare un indice a 16 bit nel pool di costanti. L'elemento del pool costante in quell'indice contiene informazioni sul tipo, la dimensione e l'offset del campo. Il riferimento all'oggetto viene preso dallo stack sia nelle istruzioni putfield che getfield. L'istruzione putfield prende il valore della variabile di istanza dallo stack e l'istruzione getfield inserisce il valore della variabile di istanza recuperata nello stack.

Accesso alle variabili di istanza
Codice operativo Operando (i) Descrizione
putfield indexbyte1, indexbyte2 imposta il campo, indicato dall'indice, dell'oggetto su valore (entrambi presi dallo stack)
getfield indexbyte1, indexbyte2 spinge il campo, indicato dall'indice, dell'oggetto (preso dallo stack)

Le variabili di classe sono accessibili tramite i codici operativi getstatic e putstatic, come mostrato nella tabella seguente. Sia getstatic che putstatic accettano due operandi di un byte, che vengono combinati dalla JVM per formare un offset senza segno a 16 bit nel pool di costanti. L'elemento del pool costante in quella posizione fornisce informazioni su un campo statico di una classe. Poiché non esiste alcun oggetto particolare associato a un campo statico, non esiste alcun riferimento a un oggetto utilizzato da getstatic o putstatic. L'istruzione putstatic prende il valore da assegnare dallo stack. L'istruzione getstatic inserisce il valore recuperato nello stack.

Accesso alle variabili di classe
Codice operativo Operando (i) Descrizione
putstatic indexbyte1, indexbyte2 imposta il campo, indicato dall'indice, dell'oggetto su valore (entrambi presi dallo stack)
getstatic indexbyte1, indexbyte2 spinge il campo, indicato dall'indice, dell'oggetto (preso dallo stack)

I seguenti codici operativi controllano per vedere se il riferimento all'oggetto in cima allo stack si riferisce a un'istanza della classe o dell'interfaccia indicizzata dagli operandi che seguono il codice operativo. L'istruzione checkcast genera CheckCastExceptionse l'oggetto non è un'istanza della classe o dell'interfaccia specificata. Altrimenti, Checkcast non fa nulla. Il riferimento all'oggetto rimane nello stack e l'esecuzione prosegue con l'istruzione successiva. Questa istruzione garantisce che i cast siano sicuri in fase di esecuzione e fanno parte della copertura di sicurezza della JVM.

L'istruzione instanceof estrae il riferimento all'oggetto dalla cima dello stack e inserisce true o false. Se l'oggetto è effettivamente un'istanza della classe o dell'interfaccia specificata, true viene inserito nello stack, altrimenti false viene inserito nello stack. L'istruzione instanceof viene utilizzata per implementare la instanceofparola chiave di Java, che consente ai programmatori di verificare se un oggetto è un'istanza di una particolare classe o interfaccia.

Controllo del tipo
Codice operativo Operando (i) Descrizione
checkcast indexbyte1, indexbyte2 Genera ClassCastException se objectref nello stack non può essere sottoposto a cast alla classe in corrispondenza dell'indice
instanceof indexbyte1, indexbyte2 Inserisce true se objectref in pila è un'istanza di classe in index, altrimenti inserisce false

Codici operativi per array

La creazione di istanze di nuovi array viene eseguita tramite gli opcode newarray, anewarray e multianewarray. Il codice operativo newarray viene utilizzato per creare array di tipi primitivi diversi dai riferimenti agli oggetti. Il particolare tipo primitivo è specificato da un singolo operando di un byte che segue il codice operativo newarray. L'istruzione newarray può creare array per byte, short, char, int, long, float, double o boolean.

L'istruzione anewarray crea un array di riferimenti a oggetti. Due operandi di un byte seguono il codice operativo anewarray e vengono combinati per formare un indice a 16 bit nel pool di costanti. Una descrizione della classe di oggetti per cui deve essere creato l'array si trova nel pool di costanti all'indice specificato. Questa istruzione alloca spazio per la matrice di riferimenti a oggetti e inizializza i riferimenti a null.

The multianewarray instruction is used to allocate multidimensional arrays -- which are simply arrays of arrays -- and could be allocated with repeated use of the anewarray and newarray instructions. The multianewarray instruction simply compresses the bytecodes needed to create multidimensional arrays into one instruction. Two one-byte operands follow the multianewarray opcode and are combined to form a 16-bit index into the constant pool. A description of the class of object for which the array is to be created is found in the constant pool at the specified index. Immediately following the two one-byte operands that form the constant pool index is a one-byte operand that specifies the number of dimensions in this multidimensional array. The sizes for each dimension are popped off the stack. This instruction allocates space for all arrays that are needed to implement the multidimensional arrays.

Creating new arrays
Opcode Operand(s) Description
newarray atype pops length, allocates new array of primitive types of type indicated by atype, pushes objectref of new array
anewarray indexbyte1, indexbyte2 pops length, allocates a new array of objects of class indicated by indexbyte1 and indexbyte2, pushes objectref of new array
multianewarray indexbyte1, indexbyte2, dimensions pops dimensions number of array lengths, allocates a new multidimensional array of class indicated by indexbyte1 and indexbyte2, pushes objectref of new array

The next table shows the instruction that pops an array reference off the top of the stack and pushes the length of that array.

Getting the array length
Opcode Operand(s) Description
arraylength (none) pops objectref of an array, pushes length of that array

The following opcodes retrieve an element from an array. The array index and array reference are popped from the stack, and the value at the specified index of the specified array is pushed back onto the stack.

Retrieving an array element
Opcode Operand(s) Description
baload (none) pops index and arrayref of an array of bytes, pushes arrayref[index]
caload (none) pops index and arrayref of an array of chars, pushes arrayref[index]
saload (none) pops index and arrayref of an array of shorts, pushes arrayref[index]
iaload (none) pops index and arrayref of an array of ints, pushes arrayref[index]
laload (none) pops index and arrayref of an array of longs, pushes arrayref[index]
faload (none) pops index and arrayref of an array of floats, pushes arrayref[index]
daload (none) pops index and arrayref of an array of doubles, pushes arrayref[index]
aaload (none) pops index and arrayref of an array of objectrefs, pushes arrayref[index]

The next table shows the opcodes that store a value into an array element. The value, index, and array reference are popped from the top of the stack.

Storing to an array element
Opcode Operand(s) Description
bastore (none) pops value, index, and arrayref of an array of bytes, assigns arrayref[index] = value
castore (none) pops value, index, and arrayref of an array of chars, assigns arrayref[index] = value
sastore (none) pops value, index, and arrayref of an array of shorts, assigns arrayref[index] = value
iastore (none) pops value, index, and arrayref of an array of ints, assigns arrayref[index] = value
lastore (none) pops value, index, and arrayref of an array of longs, assigns arrayref[index] = value
fastore (none) pops value, index, and arrayref of an array of floats, assigns arrayref[index] = value
dastore (none) pops value, index, and arrayref of an array of doubles, assigns arrayref[index] = value
aastore (none) inserisce valore, indice e arrayref di un array di objectref, assegna arrayref [index] = value

Array tridimensionale: una simulazione di macchina virtuale Java

L'applet seguente mostra una Java virtual machine che esegue una sequenza di bytecode. La sequenza del bytecode nella simulazione è stata generata da javacper il initAnArray()metodo della classe mostrato di seguito:

class ArrayDemo {static void initAnArray () {int [] [] [] threeD = new int [5] [4] [3]; for (int i = 0; i <5; ++ i) {for (int j = 0; j <4; ++ j) {for (int k = 0; k <3; ++ k) {treD [ i] [j] [k] = i + j + k; }}}}}

Di seguito sono riportati i bytecode generati da javacfor initAnArray():