Chapter 9

javac : The Java Compiler


CONTENTS


The Java compiler (javac) is the component of the Java Developer's Kit used to transform Java source code files into bytecode executables that can be run in the Java runtime system. In this chapter, you learn all about the Java compiler and how it is used, including the different compilation options it supports. You also learn about an alternate version of the compiler that ships with some versions of the JDK. Toward the end of the chapter, you get a glimpse of a few of the bugs in the current version of the Java compiler.

Overview

In Java, source code files have the extension .java. Java source code files are standard ASCII text files, much like the source code files for other popular programming languages like C++. It is the job of the Java compiler to process Java source code files and create executable Java bytecode classes from them. Executable bytecode class files have the extension .class, and they represent a Java class in its useable form.

Java class files are generated on a one-to-one basis with the classes defined in the source code. In other words, the Java compiler generates exactly one .class file for each class you create. Technically, it is possible to define more than one class in a single source file; it is therefore possible for the compiler to generate multiple class files from a single source file. When this happens, it means that the source file contains multiple class definitions.

You may have heard something about just-in-time compilers in reference to Java. It's important not to get these compilers confused with the Java compiler and the role it plays. The Java compiler is responsible for turning Java source code into Java bytecodes that can be executed within the Java runtime system. The Java Virtual Machine, which is a component of the runtime system, is responsible for interpreting the bytecodes and making the appropriate system-level calls to the native platform. It is at this point where platform independence is achieved by Java; the bytecodes are in a generic form that is only converted to a native form when processed by the Virtual Machine. Figure 9.1 shows how the Java compiler and runtime system relate to each other.

Figure 9.1 : The relationship between the Java compiler and runtime system.

Just-in-time compilers remove the role of the runtime interpreter by converting Java bytecodes to native code on the fly before executing a Java program. In this way, just-in-time Java compilers work more like the back end of traditional language compilers in that they generate code for a native platform. Similarly, the Java compiler works more like the front end of a traditional compiler in that it parses Java source code and generates internally useful bytecode classes. Figure 9.2 shows the relationship between the Java compiler and just-in-time compilers.

Figure 9.2 : The difference between the Java compiler and java-in-time compilers.

Keep in mind that Java executables are still centered around the bytecode class format. Even with just-in-time compilers in the picture, all you must be concerned with as a developer is generating the appropriate bytecode classes using the Java compiler. If no just-in-time compiler is present on a user's system, the bytecode classes will be processed and executed by the runtime interpreter. On the other hand, if a just-in-time compiler happens to exist on the system, the bytecode classes will be converted to native code and then executed. Either way, the key to executing Java programs is the bytecode classes, which are created by the Java compiler.

Usage

The Java compiler is a command-line tool, meaning that it is invoked from a command prompt, such as the MS-DOS shell in Windows 95. The syntax for the Java compiler follows:

javac Options Filename

The Filename argument specifies the name of the source code file you want to compile. The compiler will generate bytecode classes for all classes defined in this file. Likewise, the compiler also will generate bytecode classes for any dependent classes that haven't been compiled yet. In other words, if you are compiling class A, which is derived from class B, and class B has not yet been compiled, the compiler will go ahead and compile both classes.

Options

The Options compiler argument specifies options related to how the compiler creates the executable Java classes. Following is a list of the compiler options:

-classpath Path
-d Dir
-g
-nowarn
-O
-verbose

The -classpath option tells the compiler to override the CLASSPATH environment variable with the path specified by Path. This causes the compiler to look for user-defined classes in the path specified by Path. Path is a semicolon-delimited list of directory paths taking the following form:

.;<your_path>

An example of a specific usage of -classpath follows:

javac -classpath .;\dev\animate\classes;\dev\render\classes A.java

In this case, the compiler is using a user-defined class path to access any classes it needs while compiling the source code file A.java. The -classpath option is sometimes useful when you want to try compiling something without taking the trouble to modify the CLASSPATH environment variable.

The -d option determines the root directory where compiled classes are stored. This is important because many times classes are organized in a hierarchical directory structure. With the -d option, the directory structure will be created beneath the directory specified by Dir.

The -g compiler option causes the compiler to generate debugging tables for the Java classes. Debugging tables are used by the Java debugger, and they contain information such as local variables and line numbers. The default action of the compiler is to only generate line numbers. If you are going to be using the Java debugger, you must use the -g option. Additionally, for debugging, make sure you don't use the -O option, which optimizes the code.

The -nowarn option turns off compiler warnings. Warnings are printed to standard output during compilation to inform you of potential problems with the source code. It is generally a good idea to keep warnings enabled, because they often signal problem areas in your code. However, you may run into a situation where warnings are getting in the way, in which case the -nowarn option might be useful.

The -O option causes the compiler to optimize the compiled code. In this case, optimization simply means that static, final, and private methods are compiled inline. When a method is compiled inline, it means that the entire body of the method is included in place of each call to the method. This speeds up execution because it eliminates the method call overhead. Optimized classes are usually larger in size, to accommodate the duplicate code. The -O optimization option also suppresses the default creation of line numbers by the compiler. Keep in mind that the -O option should not be used when you plan on debugging the compiled code using the Java debugger.

The -verbose option has somewhat of an opposite effect as the -nowarn option-it prints out extra information about the compilation process. You can use -verbose to see exactly what source files are being compiled and what class files are being loaded.

The Non-Optimizing Compiler

Some distributions of the Java Developer's Kit include an alternate Java compiler called javac_g. This version of the Java compiler generates code without some of the internal optimizations performed by the standard javac compiler. If this compiler is in your JDK distribution, be sure to use it when you are compiling code for debugging. Otherwise, stick with the javac compiler for all release code.

Bugs

As of this writing, the latest release of the Java Developer's Kit is 1.02, which contains some known bugs. More specifically, the following Java compiler bugs have been documented and acknowledged by the JavaSoft development team:

The first bug is only a problem if you are using different packages containing classes with the same name. Generally speaking, most programmers probably won't develop two packages with same-named classes in each. However, the problem can easily arise without your even realizing it; suppose you are using someone else's package that has a bunch of classes already defined, and a class name conflicts with one of your own. Or, for example, suppose you had your own package including the following source code:

package stuff;
import java.util.*;

public class Hashtable
{
  public Hashtable() {
    // initialize the hashtable
  }
}

A class called Hashtable already exists in the java.util package, so your Hashtable class would conflict with it upon compilation thanks to the compiler bug.

The second compiler bug also is related to class names, and this bug rears its head whenever you have two classes with names that differ only by case, as shown in the following code:

// File EncryptIt.java
class EncryptIt
{
  // encrypt something
}

// File encryptit.java
class encryptit
{
  // encrypt something else
}

Notice that the second class, which is defined in a different source code file, has the same name as the first class, with the exception of the case on two of the characters. The Java compiler will give an error while attempting to compile this code, although technically the class naming is legal. Keep in mind that this bug exists only on the Windows 95/NT platform.

Finally, the last bug deals with the number of local variables defined in a method. If a method defines more than 63 local variables, the Java compiler will not be able to compile the method. The Java language specification has yet to set a specific upper limit on the number of local variables allowed, so you can think of the number 63 as the working limit until a formal decision has been made.

Admittedly, none of these bugs are all that likely to occur, simply because most programmers give their classes unique names and typically use less than 63 local variables in each method! However, just in case you ever find yourself pulling your hair out over a strange compiler problem, these bugs might be good to keep in mind.

Summary

In this chapter, you learned all about the standard Java compiler that ships with the Java Developer's Kit. You learned its role in generating executable Java code, along with how it differs from just-in-time Java compilers. You then learned how to use the compiler and what options are available for generating executable Java classes. You finished up with a quick look at an alternate non-optimizing Java compiler that ships with some versions of the JDK, along with a few bugs that made their way into the compiler.