![]() |
|||
![]() ![]() |
![]() |
![]()
|
![]() |
lookupswitch A tableswitch jump table can use a lot of extra space needlessly. Consider the lswitch() method: public int lswitch(int i) { int j = 0; switch (i) { case 0: j+= 8; case -121: j -= 78; case 236: j /= 2; case 342: j *= 87; case 5: j -= 5; default: } return j; } Because the lowest value is -121 and the highest value is 342, compiling this with tableswitch would produce a jump table (342 - -121 + 1)*4 or 1856 bytes long for only five cases. This clearly is inefficient. Instead, a match-offset table is used. Heres how its set up. Immediately following the padding bytes are four bytes that make up a signed int called default. This has the same meaning as it does for tableswitch; that is, if the value popped from the stack does not match any of the cases, then it should add this int to the address of the lookupswitch instruction and jump to the resulting address. The four bytes after default are a signed int called npairs. This is the number of cases stored in the match-offset table and should be a positive number. Each entry in the match-offset table is composed of two 4-byte ints. The first int is the value to be matched; the second int is the offset from the lookupswitch instruction to jump to. Thus, if npairs is 5, as in the above example, then there are 5*4*2=40 bytes in the match offset table. This saves 1,816 bytes compared with using a jump table, quite a substantial savings. The entries in the match-offset table are sorted by the values to match. They are not necessarily in the order in which they appear in the source code. The lswitch() method listed earlier compiles into the byte codes in Listing 5-4. The lookup switch instruction falls on byte 3. Again, no padding is needed. The default value is in bytes 4 through 7 and is equal to 67. Therefore, when the default case is selected, control jumps to byte 70. Bytes 8 through 11 are the number of pairs, in this example 5-. The match-offset pairs themselves are in bytes 12 through 51. Notice that the first pair matches the value -121 (bytes 12 through 15) and jumps to instruction 55, 52 bytes after instruction 3 (bytes 16 through 19). The other four pairs are similar. Notice also that the matches in the byte code have been ordered as -121, 0, 5, 236, 342, even though the source code placed them out of order as 0, -121, 236, 342, 5-. On the other hand, the actions they take are in the same order in which they appeared in the source code. Listing 5-4 The lookupswitch public int lswitch(int) { 0 iconst_0 1 istore_2 2 iload_1 3 lookupswitch 4 0 5 0 6 0 7 67 8 0 9 0 10 0 11 5 12 -1 13 -1 14 -1 15 -121 16 0 17 0 18 0 19 52 20 0 21 0 22 0 23 0 24 0 25 0 26 0 27 49 28 0 29 0 30 0 31 5 32 0 33 0 34 0 35 64 36 0 37 0 38 0 39 -20 40 0 41 0 42 0 43 55 44 0 45 0 46 1 47 86 48 0 49 0 50 0 51 59 52 iinc 53 2 54 8 55 iinc 56 2 57 -78 58 iload_2 59 iconst_2 60 idiv 61 istore_2 62 iload_2 63 bipush 64 87 65 imul 66 istore_2 67 iinc 68 2 69 -5 70 iload_2 71 ireturn } ObjectsUntil now, this chapter has considered methods as self-contained entities. However, in real programs, methods call other methods. They use fields in the object to which they belong and in other objects to which they possess references. They can create new objects and access the methods and fields of those objects. All of this can be accomplished with only nine new instructions. In general, all of these instructions operate on or return a reference value. Recall that a reference is a 32-bit object identifier for an object. All instance methods have a reference to the object to which they belong stored in the zeroth position of the local variable array. This enables them to call other methods and to refer to fields in their own object. Static methods do not belong to a particular object, so they do not have such a reference in their local variable array. These instructions make frequent use of items in the constant pool. Keep in mind that items in the constant pool may themselves refer to other items in the constant pool.
|
![]() |
|