30. Multiple Source Files

Part of CS:2820 Object Oriented Software Development Notes, Fall 2015
by Douglas W. Jones
THE UNIVERSITY OF IOWA Department of Computer Science

 

Breaking up the Source File

The code for our road network simulator is getting large. At some point, it would make good sense to break it into multiple source files. The Java compiler allows you to say:

[HawkId ~]$ javac File1.java File2.java

This builds a project where the source is written in multiple files. However, if you had to type all the file names every time you compiled your program, it would get very tiring. Fortunately, the Unix/Linux shells (as well as the DOS/Windows command line) provide an abbreviation in the form of a wild-card character that matches essentially anything, so you can type:

[HawkId ~]$ javac *.java

This matches any file ending with .java, and it works well for a project where there is only one application in the directory.

The java compiler is fairly smart. If you type just this, the compiler will seek out other files in an intelligenty way:

[HawkId ~]$ javac RoadNetwork.java

Specifically, if the code in RoadNetwork.java refers to a class, for example, Simulation and that class is not defined in the current source file, the compiler will automatically look for a file named Simulation.java in the current directory and, if there is such a file, add it to the list of files it is compiling. It will not search outside the current directory.

If your project includes files that are in multiple directories, you can explicitly tell the compiler where they are using an @argfile. This tool allows you to have multiple main programs for a project, for example, main programs for testing classes in addition to the real main program, or code for shared components in other directories. An @argfile is a file with explicit lists of the source files to be used.

Suppose you wanted to call your argfile classes. You could construct such a file as follows:

Simulator.java
RoadNetwork.java

Given that this file exists, you can compile these two source files together with the command:

[HawkId ~]$ javac @classes

If the classes file gets big, it would be nice to be able to add comments to it. Unfortunately, Java does not have a standard way to put comments in @argfiles.

Breaking Out One Class

In the above example, we assumed that the classes file specified a separate source file for class Simulator. We can break this out of the original monolithic source file like this:

// Simulator.java

import java.util.PriorityQueue;

/**
 * Framework for discrete event simulation.
 * @author Douglas Jones
 * @version ...
 */
class Simulator {

	public interface Action {
		// actions contain the specific code of each event
		void trigger( float time );
	}

	... remainder of body of class ...
}

Note that our original source file had imported a large number of different classes. Of those, only class PriorityQueue is relevant to class Simulator, so that is the only import statement used in this source file.

Note also that the header comments from class Simulator have been expanded to include authorship and other information. When a source file is broken into multiple files, the relationship between those files needs documentation, and the Javadoc comment mechanism is reasonably good for this. Each source file is worthy of comments indicating authorship, version and the provenance of this file.

Class Simulator is almost the perfect size for a single class that fits comfortably in one source file. Some of our other classes are rather small, while others are a bit large.

The Remainder

Jave works nicely if all the classes you use are defined in one source file, and it works nicely if each class is in its own source file, so long as the source file has the same name as the class. Breaking up programs along other lines is more difficult. The problem is that if a class is needed, and the definition of that class is in a file with a different name, the compiler will not be able to find the file. It is only smart enough to look for a file with the same name as the class.

As a result, the "right" way to break up a large project into multiple source files puts one class in each file.

As a general rule, if the single-source file had a number of import statements at its head, when you break the program into multiple source files, each of those files is likely to have a much smaller number of import statements. You should never import files you don't need, since the import statements themselves serve as useful documentation of the relationship between the program and the tools in the Java library.