![]() |
|||
![]() ![]() |
![]() |
![]()
|
![]() |
The next variant, Listing 4-2, was obtained by forcing the file open in a text processor (specifically BBEdit). The printout here has thrown away a few characters, such as page break (ASCII), that would have completely screwed up the formatting of this book. This looks awful, but its a quick-and-dirty way to get a look at the String constants in a file. I remember a stock trader in the early days of PCs who didnt like one of the messages that his program gave him, so he opened up the DOS .exe file in WordPerfect, searched for the offending string, replaced it, and saved the file. Amazingly, the program worked with his user modification. I do not recommend this as general practice. Theres not much else to be learned here, so lets move on. Listing 4-3 is composed of disassembled byte code. This is much more useful than Listing 4-2. You get to see the name of the class, all imported classes, and all methods and fields. With a little effort, you can learn to read the byte codes as you would read someone elses source code. This listing was produced with the JDKs javap program with the -c command line flag that is: % javap -c HelloWorld Ill develop a different byte code disassembler later in this chapter and the next. Why would you want to do this when you can look at the .java source code file instead? The short answer is that youll almost never do this instead of looking at the .java file. However, its not uncommon to want to investigate the code of a class for which you do not have original source code. Youve probably become accustomed to using your Web browsers View Source command to find out how someone did a neat HTML trick. With the techniques and tools youll develop in this chapter, youll have an effective View Source equivalent for Java .class files. Lets begin by looking at the source and byte codes for the main() method of the HelloWorld class. public static void main (String args[]) { Method void main() System.out.println(Hello World!); 0 getstatic #7 <Field java.lang.System.out Ljava/io/PrintStream;> 3 ldc #1 <String Hello World> 5 invokevirtual #8 <Method java.io.PrintStream.print(Ljava/lang/String;)V> 8 return } } Lining them up side-by-side, you can see that these four lines 0 getstatic #7 <Field java.lang.System.out Ljava/io/PrintStream;> 3 ldc #1 <String Hello World> 5 invokevirtual #8 <Method java.io.PrintStream.print(Ljava/lang/String;)V> 8 return are probably somehow equivalent to the single source code line: System.out.println(Hello World!); See if you can figure out how. The numbers on the left of each line start counting at zero. Theyre indices into the byte codes for this method. This is just a series of bytes in a particular place in memory. The first byte is an instruction: getstatic. The argument to this instruction is #7. This refers to the seventh entry in the constant pool for this class. It so happens that the seventh entry in this particular pool is java.lang.System.out, an instance of java.io.PrintStream. You know that System.out is a static field in the System class of type PrintStream, so it seems logical to interpret getstatic as a command to retrieve a reference to a static class. In this case, it retrieves a reference to the System.out class and places it on the stack. The next instruction is ldc, which stands for load constant. It has one argument: the integer constant #1. The 1 tells it which constant to load from the constant pool. In this case, it loads the first constant in the pool, which happens to be the string Hello World! Because Hello World is the only constant literal in HelloWorld.java, its not surprising that its the first one in the pool. A reference to this string is placed on the top of the stack. The next instruction is invokevirtual. This instruction calls instance methods. In this case, it calls the eighth entry in the constant pool, the method println() of the java.io.PrintStream class. The arguments for println() are taken from the stack. In this case, the top of the stack has a reference to the String object HelloWorld. The object whose method it should call is one level deeper in the stack. Thats the reference to System.out placed there by getstatic. The last instruction is return. There was no return statement in main(), but Java puts one here anyway. Writing return is optional in void methods. The compiler is smart enough to add a blank return for you if your void method requires one. However, in a non-void method, you have to return explicitly, because the compiler, although it knows you have to return, does not know what value you want to return. The next method is the constructor HelloWorld(). The .java source code file did not include a constructor. However, the compiler puts a default constructor that takes no arguments in the byte code anyway. Method HelloWorld() 0 aload_0 1 invokespecial #6 <Method java.lang.Object.<init>()V> 4 return } The aload instruction loads a reference from a local variable. In this case, it loads the zeroth local variable. This local variable is the string java.lang.Object(). This becomes the argument to the next instruction. The next instruction, invokespecial, is used to call the superclasss constructor from the subclasss constructor. Finally, the return instruction transfers control back to the calling method. Whats most interesting about this method is that none of it is in the Java source code. All Java classes have a constructor that takes no arguments if there are no other explicit constructors. Furthermore, all constructors call their superclasss constructor before they do anything else, even if there isnt an explicit super() call in the first line of the subclasss constructor. In the remainder of this chapter, youll explore the Java .class file format to see how you change raw hexadecimal bytes such as those in Listing 4-1 into something more intelligible like the byte code in Listing 4-4.
|
![]() |
|