Obiekty i tablice

Witamy w kolejnej edycji Under The Hood . Ta kolumna skupia się na technologiach bazowych Java. Ma na celu dać programistom wgląd w mechanizmy, które powodują, że ich programy Java działają. Artykuł z tego miesiąca poświęcony jest kodom bajtowym, które dotyczą obiektów i tablic.

Maszyna zorientowana obiektowo

Wirtualna maszyna Java (JVM) pracuje z danymi w trzech formach: obiektów, odwołań do obiektów i typów pierwotnych. Obiekty znajdują się na stercie zbieranej bezużytecznie. Odwołania do obiektów i typy pierwotne znajdują się na stosie języka Java jako zmienne lokalne, na stercie jako zmienne instancji obiektów lub w obszarze metod jako zmienne klas.

W maszynie wirtualnej Java pamięć jest alokowana na stercie zbierania elementów bezużytecznych tylko jako obiekty. Nie ma sposobu na przydzielenie pamięci dla typu pierwotnego na stercie, z wyjątkiem tego, że jest częścią obiektu. Jeśli chcesz użyć typu pierwotnego, w którym Objectpotrzebne jest odwołanie, możesz przydzielić obiekt opakowania dla typu z java.langpakietu. Na przykład istnieje Integerklasa, która opakowuje inttyp w obiekt. Tylko odwołania do obiektów i typy pierwotne mogą znajdować się na stosie Java jako zmienne lokalne. Obiekty nigdy nie mogą znajdować się na stosie Java.

Architektoniczne rozdzielenie obiektów i typów pierwotnych w JVM znajduje odzwierciedlenie w języku programowania Java, w którym obiekty nie mogą być deklarowane jako zmienne lokalne. Jako takie można zadeklarować tylko odwołania do obiektów. Po deklaracji odwołanie do obiektu nie odnosi się do niczego. Dopiero po jawnym zainicjowaniu odwołania - albo z odniesieniem do istniejącego obiektu, albo z wywołaniem new- odwołuje się do rzeczywistego obiektu.

W zestawie instrukcji JVM wszystkie obiekty są tworzone i udostępniane za pomocą tego samego zestawu instrukcji, z wyjątkiem tablic. W Javie tablice są pełnoprawnymi obiektami i, jak każdy inny obiekt w programie Java, są tworzone dynamicznie. Odwołań do tablic można używać wszędzie tam, gdzie Objectjest wywoływane odwołanie do typu , a Objectna tablicy można wywołać dowolną metodę . Jednak w wirtualnej maszynie Javy tablice są obsługiwane za pomocą specjalnych kodów bajtowych.

Jak w przypadku każdego innego obiektu, tablice nie mogą być deklarowane jako zmienne lokalne; tylko odniesienia do tablic mogą. Same obiekty tablicowe zawsze zawierają tablicę typów pierwotnych lub tablicę odwołań do obiektów. Jeśli deklarujesz tablicę obiektów, otrzymasz tablicę odwołań do obiektów. Same obiekty muszą być jawnie utworzone newi przypisane do elementów tablicy.

Kody operacyjne dla obiektów

Tworzenie instancji nowych obiektów odbywa się za pośrednictwem

new

kod operacji. Dwa jednobajtowe operandy następują po

new

kod operacji. Te dwa bajty są łączone w celu utworzenia 16-bitowego indeksu w puli stałej. Stały element puli przy określonym przesunięciu dostarcza informacji o klasie nowego obiektu. JVM tworzy nową instancję obiektu na stercie i umieszcza odwołanie do nowego obiektu na stosie, jak pokazano poniżej.

Tworzenie obiektów
Kod operacji Operand (y) Opis
new bajt_indeksu1, bajt_indeksu2 tworzy nowy obiekt na stercie, wypycha odniesienie

Następna tabela pokazuje rozkazy, które umieszczają i pobierają pola obiektów. Te opkody, putfield i getfield, działają tylko na polach, które są zmiennymi instancji. Zmienne statyczne są dostępne przez putstatic i getstatic, które zostaną opisane później. Instrukcje putfield i getfield przyjmują po dwa jednobajtowe operandy. Operandy są łączone w celu utworzenia 16-bitowego indeksu w stałej puli. Stała pozycja puli w tym indeksie zawiera informacje o typie, rozmiarze i przesunięciu pola. Odniesienie do obiektu jest pobierane ze stosu w instrukcjach putfield i getfield. Instrukcja putfield pobiera wartość zmiennej instancji ze stosu, a instrukcja getfield wypycha pobraną wartość zmiennej instancji na stos.

Dostęp do zmiennych instancji
Kod operacji Operand (y) Opis
putfield bajt_indeksu1, bajt_indeksu2 ustaw pole, wskazane przez indeks, obiektu na wartość (oba pobrane ze stosu)
getfield bajt_indeksu1, bajt_indeksu2 wypycha pole, wskazane przez indeks, obiektu (pobranego ze stosu)

Dostęp do zmiennych klas uzyskuje się za pośrednictwem instrukcji getstatic i putstatic, jak pokazano w poniższej tabeli. Zarówno getstatic, jak i putstatic pobierają dwa jednobajtowe operandy, które są łączone przez maszynę JVM w celu utworzenia 16-bitowego przesunięcia bez znaku w stałej puli. Stała pozycja puli w tej lokalizacji dostarcza informacji o jednym statycznym polu klasy. Ponieważ nie ma określonego obiektu skojarzonego z polem statycznym, nie ma odwołania do obiektu używanego przez getstatic lub putstatic. Instrukcja putstatic pobiera wartość do przypisania ze stosu. Instrukcja getstatic umieszcza pobraną wartość na stosie.

Dostęp do zmiennych klas
Kod operacji Operand (y) Opis
putstatic bajt_indeksu1, bajt_indeksu2 ustaw pole, wskazane przez indeks, obiektu na wartość (oba pobrane ze stosu)
getstatic bajt_indeksu1, bajt_indeksu2 wypycha pole, wskazane przez indeks, obiektu (pobranego ze stosu)

Poniższe kody operacyjne sprawdzają, czy odwołanie do obiektu na szczycie stosu odnosi się do wystąpienia klasy lub interfejsu indeksowanego przez operandy następujące po kodzie operacyjnym. Instrukcja checkcast zgłasza, CheckCastExceptionjeśli obiekt nie jest instancją określonej klasy lub interfejsu. W przeciwnym razie checkcast nic nie robi. Odniesienie do obiektu pozostaje na stosie, a wykonanie jest kontynuowane od następnej instrukcji. Ta instrukcja zapewnia, że ​​rzutowanie jest bezpieczne w czasie wykonywania i stanowi część pakietu zabezpieczeń maszyny JVM.

Instrukcja instanceof zdejmuje odwołanie do obiektu z góry stosu i odkłada wartość true lub false. Jeśli obiekt jest rzeczywiście instancją określonej klasy lub interfejsu, to na stos jest umieszczana wartość true, w przeciwnym razie na stos jest umieszczana wartość false. Instrukcja instanceof służy do implementacji instanceofsłowa kluczowego języka Java, które umożliwia programistom sprawdzenie, czy obiekt jest instancją określonej klasy lub interfejsu.

Sprawdzanie typu
Kod operacji Operand (y) Opis
checkcast bajt_indeksu1, bajt_indeksu2 Zgłasza wyjątek ClassCastException, jeśli odwołanie obiektu na stosie nie może zostać rzutowane na klasę w indeksie
instanceof bajt_indeksu1, bajt_indeksu2 Wypycha true, jeśli objectref na stosie jest instancją klasy w indeksie, w przeciwnym razie wypycha false

Kody operacyjne dla tablic

Tworzenie instancji nowych tablic odbywa się za pośrednictwem opkodów newarray, anewarray i multianewarray. Kod operacji newarray służy do tworzenia tablic typów pierwotnych innych niż odwołania do obiektów. Konkretny typ pierwotny jest określany przez pojedynczy operand jednobajtowy następujący po kodzie operacyjnym newarray. Instrukcja newarray może tworzyć tablice dla bajtów, short, char, int, long, float, double lub boolean.

Instrukcja anewarray tworzy tablicę odniesień do obiektów. Dwa jednobajtowe operandy podążają za kodem operacyjnym anewarray i są łączone w celu utworzenia 16-bitowego indeksu w stałej puli. Opis klasy obiektu, dla którego ma zostać utworzona tablica, znajduje się w puli stałych o podanym indeksie. Ta instrukcja przydziela miejsce na tablicę odwołań do obiektów i inicjuje odwołania do wartości 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) pobiera wartość, indeks i odwołanie tablicy z tablicy odwołań do obiektu, przypisuje odwołanie do tablicy [indeks] = wartość

Tablica trójwymiarowa: symulacja maszyny wirtualnej Java

Poniższy aplet przedstawia wirtualną maszynę Java wykonującą sekwencję kodów bajtowych. Sekwencja kodu bajtowego w symulacji została wygenerowana javacza pomocą initAnArray()metody klasy pokazanej poniżej:

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) {threeD [ i] [j] [k] = i + j + k; }}}}}

Kody bajtowe wygenerowane przez javacfor initAnArray()są pokazane poniżej: