Learn Object-Oriented Java the Hard Way

Exercise 3: Defining Objects in Separate Files

In the previous exercise, we defined three objects (actually four if you count the one that had main() in it), but they were all implemented in the same file. This is not typically how things are done. Usually Java puts the implementation for each class into its own file, and then there’s another file that just holds the main() method that instantiates the objects and makes them do their thing. This class is often called the “driver” class, so usually I’ll put the word “Driver” in the name of the file.

Type up the following code, and put each class into its own file, named as shown. Save them all in the same folder.

1 public class OldMacCow {
2     public void moo() {
3         System.out.println("Cow still says moo.");
4     }
5 }

After you’ve typed in and saved OldMacCow.java, you should probably try to compile it to make sure you haven’t made any mistakes before you move on.

1 public class OldMacDuck {
2     public void quack() {
3         System.out.println("Duck still says quack.");
4     }
5 }

Did you accidentally try to run this file or the first one? Neither one contains a main() method, and so executing it by itself won’t work.

 1 public class OldMacDriver {
 2     public static void main( String[] args ) {
 3         OldMacCow maudine = new OldMacCow();
 4         OldMacCow pauline = new OldMacCow();
 5         maudine.moo();
 6         pauline.moo();
 8         OldMacDuck ferdinand = new OldMacDuck();
 9         ferdinand.quack();
10     }
11 }

Ah, there’s the main() method. Once done you can compile these a few ways.


You can compile them one at a time. That works just fine.


Although you have probably only used the Java compiler on one file at a time, it will happily compile as many files as you give it, in order from left to right.

If there’s an error, though, you’ll have to pay attention to the filename in the error message. For example:

This error message is on line 2 in the file OldMacCow.java, whereas the next mistake is on line 6 or earlier in the file OldMacDuck.java for this error message:

So just watch for that.


If the filenames you’re trying to compile are similar, you can compile them all at once with something like this. The star/asterisk gets expanded by your terminal into all filenames in the current folder that begin with OldMac and which end in .java. (This includes the file OldMacDonald.java from the previous exercise. Which is fine, compiling doesn’t “combine” the files in any way, it just converts each file one at a time into its own bytecode (.class) file.)


So, what magic is going on here? Only one file name? Well, what happens is that javac starts compiling OldMacDriver.java. On line 3 we refer to an object called OldMacCow. There’s no object called that defined in this file. And there are no import statements to import a class called that, either.

So the Java compiler goes hunting. It knows it needs an object called OldMacCow, which would be implemented in a bytecode file named OldMacCow.class. If this file exists in the current folder, then it pulls the definitions from this bytecode file automatically! (This is a big deal for C++ programmers.)

And if there’s no bytecode file in the current folder, it then looks for a source code file called OldMacCow.java that it can compile to create that bytecode file. If such a file is in the current folder, it’ll just automatically compile it for you.

It does this for any objects referenced in the file you’re compiling. If it can resolve all the dependencies itself, it’ll do so. If not, it’ll throw a compiler error about the undefined symbol it couldn’t find.

So, to sum up, from here until the end of the book you should probably compile each file as you finish it to make sure there aren’t any mistakes. But if you’re lazy or just confident, it is usually okay to just compile the one file containing the main(), and let the compiler find the rest of the files for you.

What You Should See

As you might suspect, when executing the bytecode, you only need to run the file containing the main() method.


You’ll notice that the process of actually instantiating the objects or calling their methods isn’t any different. (See lines 3 through 9 in the driver file.) You just make an instance of an object, then call its method, just like before.

Hopefully this process of doing one program that is broken up into multiple files makes sense. Because that’s what we will be doing from here on out in the rest of the book!

(I’m not trying to be difficult; that’s just how object-oriented programming works. Code is broken up into classes/objects each in their own file and those objects are combined to make a working program. I’ll talk more about the reasons behind this in the chapters to come.)

“Learn Object-Oriented Java the Hard Way” is ©2015–2016 Graham Mitchell