Objets et tableaux

Bienvenue dans une autre édition de Under The Hood . Cette colonne se concentre sur les technologies sous-jacentes de Java. Il vise à donner aux développeurs un aperçu des mécanismes qui font fonctionner leurs programmes Java. L'article de ce mois-ci examine les bytecodes qui traitent des objets et des tableaux.

Machine orientée objet

La machine virtuelle Java (JVM) fonctionne avec des données sous trois formes: objets, références d'objet et types primitifs. Les objets résident sur le tas récupéré par la mémoire. Les références d'objets et les types primitifs résident soit sur la pile Java en tant que variables locales, sur le tas en tant que variables d'instance d'objets, soit dans la zone de méthode en tant que variables de classe.

Dans la machine virtuelle Java, la mémoire est allouée sur le tas récupéré uniquement en tant qu'objets. Il n'existe aucun moyen d'allouer de la mémoire pour un type primitif sur le tas, sauf dans le cadre d'un objet. Si vous souhaitez utiliser un type primitif où une Objectréférence est nécessaire, vous pouvez allouer un objet wrapper pour le type du java.langpackage. Par exemple, il existe une Integerclasse qui encapsule un inttype avec un objet. Seules les références d'objet et les types primitifs peuvent résider sur la pile Java en tant que variables locales. Les objets ne peuvent jamais résider sur la pile Java.

La séparation architecturale des objets et des types primitifs dans la JVM se reflète dans le langage de programmation Java, dans lequel les objets ne peuvent pas être déclarés en tant que variables locales. Seules les références d'objet peuvent être déclarées comme telles. Lors de la déclaration, une référence d'objet ne fait référence à rien. Ce n'est qu'après que la référence a été explicitement initialisée - soit avec une référence à un objet existant, soit avec un appel à new- que la référence fait référence à un objet réel.

Dans le jeu d'instructions JVM, tous les objets sont instanciés et accédés avec le même jeu d'opcodes, à l'exception des tableaux. En Java, les tableaux sont des objets à part entière et, comme tout autre objet d'un programme Java, sont créés de manière dynamique. Les références de tableau peuvent être utilisées partout où une référence à type Objectest appelée, et toute méthode de Objectpeut être appelée sur un tableau. Pourtant, dans la machine virtuelle Java, les tableaux sont gérés avec des bytecodes spéciaux.

Comme pour tout autre objet, les tableaux ne peuvent pas être déclarés en tant que variables locales; seules les références de tableau le peuvent. Les objets Array eux-mêmes contiennent toujours soit un tableau de types primitifs, soit un tableau de références d'objet. Si vous déclarez un tableau d'objets, vous obtenez un tableau de références d'objet. Les objets eux-mêmes doivent être explicitement créés avec newet affectés aux éléments du tableau.

Opcodes pour les objets

L'instanciation de nouveaux objets se fait via le

new

opcode. Deux opérandes d'un octet suivent le

new

opcode. Ces deux octets sont combinés pour former un index 16 bits dans le pool de constantes. L'élément de pool constant au décalage spécifié donne des informations sur la classe du nouvel objet. La machine virtuelle Java crée une nouvelle instance de l'objet sur le tas et pousse la référence vers le nouvel objet sur la pile, comme illustré ci-dessous.

Création d'objets
Opcode Opérande (s) La description
new indexbyte1, indexbyte2 crée un nouvel objet sur le tas, pousse la référence

Le tableau suivant montre les opcodes qui placent et obtiennent les champs d'objet. Ces opcodes, putfield et getfield, ne fonctionnent que sur des champs qui sont des variables d'instance. Les variables statiques sont accessibles par putstatic et getstatic, qui sont décrits plus loin. Les instructions putfield et getfield prennent chacune deux opérandes d'un octet. Les opérandes sont combinés pour former un index 16 bits dans le pool de constantes. L'élément de pool constant à cet index contient des informations sur le type, la taille et le décalage du champ. La référence d'objet est extraite de la pile dans les instructions putfield et getfield. L'instruction putfield prend la valeur de la variable d'instance de la pile, et l'instruction getfield pousse la valeur de la variable d'instance récupérée sur la pile.

Accéder aux variables d'instance
Opcode Opérande (s) La description
putfield indexbyte1, indexbyte2 définir le champ, indiqué par l'index, de l'objet à la valeur (tous deux extraits de la pile)
getfield indexbyte1, indexbyte2 pousse le champ, indiqué par l'index, de l'objet (extrait de la pile)

Les variables de classe sont accessibles via les opcodes getstatic et putstatic, comme indiqué dans le tableau ci-dessous. Getstatic et putstatic prennent deux opérandes d'un octet, qui sont combinés par la JVM pour former un décalage non signé de 16 bits dans le pool de constantes. L'élément de pool constant à cet emplacement donne des informations sur un champ statique d'une classe. Comme il n'y a pas d'objet particulier associé à un champ statique, il n'y a aucune référence d'objet utilisée par getstatic ou putstatic. L'instruction putstatic prend la valeur à affecter à partir de la pile. L'instruction getstatic pousse la valeur récupérée sur la pile.

Accéder aux variables de classe
Opcode Opérande (s) La description
putstatic indexbyte1, indexbyte2 définir le champ, indiqué par l'index, de l'objet à la valeur (tous deux extraits de la pile)
getstatic indexbyte1, indexbyte2 pousse le champ, indiqué par l'index, de l'objet (extrait de la pile)

Les opcodes suivants vérifient si la référence d'objet en haut de la pile fait référence à une instance de la classe ou de l'interface indexée par les opérandes suivant l'opcode. L'instruction checkcast lance CheckCastExceptionsi l'objet n'est pas une instance de la classe ou de l'interface spécifiée. Sinon, checkcast ne fait rien. La référence d'objet reste sur la pile et l'exécution se poursuit à l'instruction suivante. Cette instruction garantit que les moulages sont sûrs au moment de l'exécution et fait partie de la couverture de sécurité de la JVM.

L'instruction instanceof fait apparaître la référence d'objet du haut de la pile et pousse true ou false. Si l'objet est en effet une instance de la classe ou de l'interface spécifiée, alors true est poussé sur la pile, sinon, false est poussé sur la pile. L'instruction instanceof est utilisée pour implémenter le instanceofmot - clé de Java, qui permet aux programmeurs de tester si un objet est une instance d'une classe ou d'une interface particulière.

Vérification de type
Opcode Opérande (s) La description
checkcast indexbyte1, indexbyte2 Lève une exception ClassCastException si objectref sur la pile ne peut pas être converti en classe à l'index
instanceof indexbyte1, indexbyte2 Pousse true si objectref sur la pile est une instance de classe à l'index, sinon pousse false

Opcodes pour les tableaux

L'instanciation de nouveaux tableaux est réalisée via les opcodes newarray, anewarray et multianewarray. L'opcode newarray est utilisé pour créer des tableaux de types primitifs autres que les références d'objet. Le type primitif particulier est spécifié par un seul opérande d'un octet après l'opcode newarray. L'instruction newarray peut créer des tableaux pour byte, short, char, int, long, float, double ou boolean.

L'instruction anewarray crée un tableau de références d'objet. Deux opérandes d'un octet suivent l'opcode anewarray et sont combinés pour former un index 16 bits dans le pool de constantes. Une description de la classe d'objet pour laquelle le tableau doit être créé se trouve dans le pool de constantes à l'index spécifié. Cette instruction alloue de l'espace pour le tableau de références d'objet et initialise les références à 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) pops value, index et arrayref d'un tableau de objectrefs, assigne arrayref [index] = value

Tableau tridimensionnel: une simulation de machine virtuelle Java

L'applet ci-dessous illustre une machine virtuelle Java exécutant une séquence de bytecodes. La séquence de bytecode dans la simulation a été générée par javacpour la initAnArray()méthode de la classe indiquée ci-dessous:

class ArrayDemo {vide statique initAnArray () {int [] [] [] threeD = new int [5] [4] [3]; pour (int i = 0; i <5; ++ i) {pour (int j = 0; j <4; ++ j) {pour (int k = 0; k <3; ++ k) {troisD [ i] [j] [k] = i + j + k; }}}}}

Les codes d'octets générés par javacfor initAnArray()sont indiqués ci-dessous: