Chapter 5. Objects in Java

In this chapter, we get to the heart of Java and explore the object-oriented aspects of the language. The term object-oriented design refers to the art of decomposing an application into some number of objects, which are self-contained application components that work together. The goal is to break your problem down into a number of smaller problems that are simpler and easier to handle and maintain. Object-based designs have proven themselves over the years, and object-oriented languages such as Java provide a strong foundation for writing applications from the very small to the very large. Java was designed from the ground up to be an object-oriented language, and all of the Java APIs and libraries are built around solid object-based design patterns.

An object design “methodology” is a system or a set of rules created to help you break down your application into objects. Often this means mapping real-world entities and concepts (sometimes called the “problem domain”) into application components. Various methodologies attempt to help you factor your application into a good set of reusable objects. This is good in principle, but the problem is that good object-oriented design is still more art than science. While you can learn from the various off-the-shelf design methodologies, none of them will help you in all situations. The truth is that there is no substitute for experience.

We won’t try to push you into a particular methodology here; there are shelves full of books to do that.1 Instead, we’ll provide some common-sense hints along the way as you get started.

Classes

Classes are the building blocks of a Java application. A class can contain methods (functions), variables, initialization code, and, as we’ll discuss later, other classes. Separate classes that describe individual parts of a more complex idea are often bundled in packages which help you organize larger projects. (Every class belongs to some package, even the simple examples we’ve seen so far.) An interface can describe some specific commonalities between otherwise disparate classes. Classes can be related to each other by extension or to interfaces by implementation. Figure 5-1 illustrates the ideas in this very dense paragraph.

lj5e 0501
Figure 5-1. Class, interface, and package overview

In this figure, you can see the Object class in the upper-left corner. Object is the foundational class at the heart of every other class in Java. It is part of the core Java package, java.lang. Java also has a package for its graphical user interface elements called javax.swing. Inside that package the JComponent class defines all of the low-level, common properties of graphical things like frames and buttons and canvases. The JLabel class, for example, extends the JComponent class. That means JLabel inherits details from JComponent but adds things specific to labels. You might have noticed that JComponent itself extends from Object, or at least, it eventually extends back to Object. For brevity’s sake we left out the intermediate classes and packages inbetween.

We can define our own classes and packages as well. The ch05 package in the lower-right corner is a custom package we built. In it, we have our game classes like Apple and Field. You can also see the GamePiece interface that will contain some common, required elements for all game pieces and is implemented by the Apple, Tree, and Physicist classes. (In our game, the Field class is where all of the game pieces will be shown, but it is not a game piece itself. Notice that is does not implement the GamePiece interface.)

We’ll be going into much more detail with more examples of each concept as you continue through this chapter. It’s important to try the examples as you go and to use the jshell tool discussed in “Trying Java” to help cement your understanding of new topics.

Declaring and Instantiating Classes

A class serves as a blueprint for making instances, which are runtime objects (individual copies) that implement the class structure. You declare a class with the class keyword and a name of your choosing. For example, our game allows physicists to throw apples at trees. Each of the nouns in that sentence are good targets for becoming classes. Inside a class, we add variables that store details or other useful information and methods that describe what we can do to and with instances of this class.

Let’s get started with a class for our apples. By (strong!) convention, class names start with capital letters. That makes the word “Apple” a good name to use. We won’t try to get everything we need to know about our game apples into the class right away, just a few elements to help illustrate how a class, variables, and methods fit together.

package ch05;

class Apple {
    float mass;
    float diameter = 1.0f;
    int x, y;

    boolean isTouching(Apple other) {
        ...
    }
    ...
}

The Apple class contains four variables: mass, diameter, x, and y. It also defines a method called isTouching(), which takes a reference to another Apple as an argument and returns a boolean value as a result. Variables and method declarations can appear in any order, but variable initializers can’t make “forward references” to other variables that appear later. (In our little snippet, the diameter variable could use the mass variable to help calculate its initial value, but mass could not use the diameter variable to do the same.) Once we’ve defined the Apple class, we can create an Apple object (an instance of that class) as follows:

    Apple a1;
    a1 = new Apple();

    // Or all in one line...
    Apple a2 = new Apple();

Recall that our declaration of the variable a1 doesn’t create an Apple object; it simply creates a variable that refers to an object of type Apple. We still have to create the object, using the new keyword, as shown in the second line of the preceding code snippet. But you can combine those steps into a single line as we did for the a2 variable. The same separate actions occure under the hood, of course. Sometimes the combined declaration and initialization will feel more readable.

Now that we’ve created an Apple object, we can access its variables and methods, as we’ve seen in several of our examples from Chapter 4 or even our graphical “Hello” app from “HelloJava”. While not very exciting, we could now build another class, PrintAppleDetails that is a complete application to create an Apple instance and print its details:

package ch05;

public class PrintAppleDetails {
    public static void main(String args[]) {
        Apple a1 = new Apple();
        System.out.println("Apple a1:");
        System.out.println("  mass: " + a1.mass);
        System.out.println("  diameter: " + a1.diameter);
        System.out.println("  position: (" + a1.x + ", " + a1.y +")");
    }
}

If you compile and run this example, you should see the following output in your terminal or in the terminal window of your IDE:

Apple a1:
  mass: 0.0
  diameter: 1.0
  position: (0, 0)

But hmm, why don’t we have a mass? If you look back at how we declared the variables for our Apple class, we only initialized diameter. All the other variables will get the Java-assigned default value of 0 since they are numeric types. (Quickly, boolean variables get a default of false and reference types get a default of null.) We would ideally like to have a more interesting apple. Let’s see how to provide those interesting bits.

Accessing Fields and Methods

Once you have a reference to an object, you can use and manipulate its variables and methods using the dot notation we saw in Chapter 4. Let’ create a new class, PrintAppleDetails2, and provide some values for the mass and position of our a1 instance and then print the new details:

package ch05;

public class PrintAppleDetails2 {
    public static void main(String args[]) {
        Apple a1 = new Apple();
        System.out.println("Apple a1:");
        System.out.println("  mass: " + a1.mass);
        System.out.println("  diameter: " + a1.diameter);
        System.out.println("  position: (" + a1.x + ", " + a1.y +")");
        // fill in some information on a1
        a1.mass = 10.0f;
        a1.x = 20;
        a1.y = 42;
        System.out.println("Updated a1:");
        System.out.println("  mass: " + a1.mass);
        System.out.println("  diameter: " + a1.diameter);
        System.out.println("  position: (" + a1.x + ", " + a1.y +")");
    }
}

And the new output:

Apple a1:
  mass: 0.0
  diameter: 1.0
  position: (0, 0)
Updated a1:
  mass: 10.0
  diameter: 1.0
  position: (20, 42)

Great! a1 is looking a little better. But look at the code again. We had to repeat the three lines that print the object’s details. That type of exact replication calls out for a method. Methods allow us to “do stuff” inside a class. We’ll go into much more details below in “Methods”. We could improve the Apple class to provide these print statements:

public class Apple {
    float mass;
    float diameter = 1.0f;
    int x, y;

    // ...

    public void printDetails() {
        System.out.println("  mass: " + mass);
        System.out.println("  diameter: " + diameter);
        System.out.println("  position: (" + x + ", " + y +")");
    }

    // ...
}

With those detail statements relocated, we can create PrintAppleDetails3 more succinctly than its predecessor:

package ch05;

public class PrintAppleDetails3 {
    public static void main(String args[]) {
        Apple a1 = new Apple();
        System.out.println("Apple a1:");
        a1.printDetails();
        // fill in some information on a1
        a1.mass = 10.0f;
        a1.x = 20;
        a1.y = 42;
        System.out.println("Updated a1:");
        a1.printDetails();
    }
}

Take another look at the printDetails() method we added to the Apple class. Inside a class, we can access variables and call methods of the class directly by name. The print statements just use the simple names like mass and diameter. Or consider filling out the isTouching() method. We can use our own x and y coordinates without any special prefix. But to access the coordinates of some other apple, we need to go back to the dot notation. Here’s one way to write that method using some math (more of this in “The java.lang.Math Class” and the if/then statement we saw in “if/else conditionals”:

    // File: ch05/Apple.java

    public boolean isTouching(Apple other) {
        double xdiff = x - other.x;
        double ydiff = y - other.y;
        double distance = Math.sqrt(xdiff * xdiff + ydiff * ydiff);
        if (distance < diameter / 2 + other.diameter / 2) {
            return true;
        } else {
            return false;
        }
    }

Let’s fill out a bit more of our game and create our Field class that uses a few Apple objects. It creates instances as member variables, works with those objects in the setupApples() and detectCollision() methods, invoking Apple methods and accessing variables of those objects through the references a1 and a2, visualized in Figure 5-2.

package ch05;

public class Field {
    Apple a1 = new Apple();
    Apple a2 = new Apple();

    public void setupApples() {
        a1.diameter = 3.0f;
        a1.mass = 5.0f;
        a1.x = 20;
        a1.y = 40;
        a2.diameter = 8.0f;
        a2.mass = 10.0f;
        a2.x = 70;
        a2.y = 200;
    }

    public void detectCollisions() {
        if (a1.isTouching(a2)) {
            System.out.println("Collision detected!");
        } else {
            System.out.println("Apples are not touching.");
        }
    }
}
lj5e 0502
Figure 5-2. Instances of the Apple class

We can prove that Field has access to the apples’ variables and methods with another iteration of our application, PrintAppleDetails4:

package ch05;

public class PrintAppleDetails4 {
    public static void main(String args[]) {
        Field f = new Field();
        f.setupApples();
        System.out.println("Apple a1:");
        f.a1.printDetails();
        System.out.println("Apple a2:");
        f.a2.printDetails();
        f.detectCollisions();
    }
}

We should see the familiar apple details followed by an answer to whether or not the two apples are touching.

% java PrintAppleDetails4
Apple a1:
  mass: 5.0
  diameter: 3.0
  position: (20, 40)
Apple a2:
  mass: 10.0
  diameter: 8.0
  position: (70, 200)
Apples are not touching.

Great, just what we expected. Before reading further, try changing the positions of the apples to make them touch.

Access modifiers preview

Several factors affect whether class members can be accessed from another class. You can use the visibility modifiers public, private, and protected to control access; classes can also be placed into a package, which affects their scope. The private modifier, for example, designates a variable or method for use only by other members of the class itself. In the previous example, we could change the declaration of our variable diameter to private:

    class Apple {
        ...
        private float diameter;
        ...

Now we can’t access diameter from Field:

    class Field {
        Apple a1 = new Apple();
        Apple a2 = new Apple();
        ...
        void setupApples() {
            a1.diameter = 3.0f; // Comple-time error
            ...
            a2.diameter = 8.0f; // Comple-time error
            ...
        }
        ...
    }

If we still need to access diameter in some capacity, we would usually add public getDiameter() and setDiameter() methods to the Apple class.

    public class Apple {
        private float diameter = 1.0f;
        ...

        public void setDiameter(float newDiameter) {
            diameter = newDiameter;
        }

        public float getDiameter() {
            return diameter;
        }

        ...
    }

Creating methods like this is a good design rule because it allows future flexibility in changing the type or behavior of the value. We’ll look more at packages, access modifiers, and how they affect the visibility of variables and methods later in this chapter.

Static Members

As we’ve said, instance variables and methods are associated with and accessed through an instance of the class (i.e., through a particular object, like a1 or f in the previous examples). In contrast, members that are declared with the static modifier live in the class and are shared by all instances of the class. Variables declared with the static modifier are called static variables or class variables; similarly, these kinds of methods are called static methods or class methods. Static members are useful as flags and identifiers, which can be accessed from anywhere. We can add a static variable to our Apple example, maybe to store the value of acceleration due to gravity so we can calculate the trajectory of a tossed apple when we start animating our game:

    class Apple {
        ...
        static float gravAccel = 9.8f;
        ...

We have declared the new float variable gravAccel as static. That means that it is associated with the class, not with an individual instance and if we change its value (either directly or through any instance of a Apple), the value changes for all Apple objects, as shown in Figure 5-3.

lj5e 0503
Figure 5-3. Static variables shared by all instances of a class

Static members can be accessed like instance members. Inside our Apple class, we can refer to gravAccel like any other variable:

    class Apple {
        ...
        float getWeight () {
            return mass * gravAccel;
        }
        ...
    }

However, since static members exist in the class itself, independent of any instance, we can also access them directly through the class. If we wanted to toss apples on Mars, for example, we don’t need a Apple object like a1 or a2 to get or set the variable gravAccel. Instead, we can use the class to select the variable:

    Apple.gravAccel = 3.7;

This changes the value of gravAccel as seen by all instances. We don’t have to manually set each instance of Apple to fall on Mars. Static variables are useful for any kind of data that is shared among classes at runtime. For instance, you can create methods to register your object instances so that they can communicate, or so that you can keep track of all of them. It’s also common to use static variables to define constant values. In this case, we use the static modifier along with the final modifier. So, if we cared only about apples under the influence of the Earth’s gravitational pull, we might change Apple as follows:

    class Apple {
        ...
        static final float EARTH_ACCEL = 9.8f;
        ...

We have followed a common convention here and named our constant with capital letters and underscores (if the name has more than one word). The value of EARTH_ACCEL is a constant; it can be accessed through the class Apple or its instances, but its value can’t be changed at runtime.

It’s important to use the combination of static and final only for things that are really constant. The compiler is allowed to “inline” such values within classes that reference them. This means that if you change a static final variable, you may have to recompile all code that uses that class (this is really the only case where you have to do that in Java). Static members are also useful for values needed in the construction of an instance itself. In our example, we might declare a number of static values to represent various sizes of Apple objects:

    class Apple {
        ...
        static int SMALL = 0, MEDIUM = 1, LARGE = 2;
        ...

We might then use these options in a method that sets the size of an Apple or in a special constructor, as we’ll discuss shortly:

    Apple typicalApple = new Apple();
    typicalApple.setSize( Apple.MEDIUM );

Again, inside the Apple class, we can use static members directly by name, as well; there’s no need for the Apple. prefix:

    class Apple {
        ...
        void resetEverything() {
            setSize ( MEDIUM );
            ...
        }
        ...
    }

Methods

So far, our example classes have been fairly simple. We keep a few bits of information around—apples have mass, fields have a couple apples, etc. But we have also touched on the idea of making those classes do stuff. All of our various PrintAppleDetails classes have a list of steps that get executed when we run the program, for example. As we noted briefly before, in Java, those steps are bundled into a method. In the case of PrintAppleDetails, that is the main() method.

Everywhere you have steps to take or decisions to make, you need a method. In addition to storing variables like the mass and diameter in our Apple class, we also added a few pieces of code that contained actions and logic. Methods are so fundamental to classes that we had to create a few (think back to the printDetails() method in Apple or the setupApples() method in Field) even before getting here to the formal discussion of them! Hopefully the methods we have discussed so far have been straightforward enough to follow just from context. But methods can do much more than print out a few variables or calculate a distance. They can contain local variable declarations and other Java statements that are executed when the method is invoked. Methods may return a value to the caller. They always specify a return type, which can be a primitive type, a reference type, or the type void, which indicates no returned value. Methods may take arguments, which are values supplied by the caller of the method.

Here’s a simple example:

    class Bird {
        int xPos, yPos;

        double fly ( int x, int y ) {
            double distance = Math.sqrt( x*x + y*y );
            flap( distance );
            xPos = x;
            yPos = y;
            return distance;
        }
        ...
    }

In this example, the class Bird defines a method, fly(), that takes as arguments two integers: x and y. It returns a double type value as a result, using the return keyword.

Our method has a fixed number of arguments (two); however, methods can have variable-length argument lists, which allow the method to specify that it can take any number of arguments and sort them out itself at runtime.2

Local Variables

Our fly() method declares a local variable called distance, which it uses to compute the distance flown. A local variable is temporary; it exists only within the scope (the block) of its method. Local variables are allocated when a method is invoked; they are normally destroyed when the method returns. They can’t be referenced from outside the method itself. If the method is executing concurrently in different threads, each thread has its own version of the method’s local variables. A method’s arguments also serve as local variables within the scope of the method; the only difference is that they are initialized by being passed in from the caller of the method.

An object created within a method and assigned to a local variable may or may not persist after the method has returned. As we’ll see in detail in “Object Destruction”, it depends on whether any references to the object remain. If an object is created, assigned to a local variable, and never used anywhere else, that object is no longer referenced when the local variable disappears from scope, so garbage collection (more on this process in “Garbage Collection”) removes the object. If, however, we assign the object to an instance variable of an object, pass it as an argument to another method, or pass it back as a return value, it may be saved by another variable holding its reference.

Shadowing

If a local variable or method argument and an instance variable have the same name, the local variable shadows or hides the name of the instance variable within the scope of the method. This might sound like an odd situation, but it happens fairly often when the instance variable has a common or obvious name. For example, we could add a move method to our Apple class. Our method will need the new coordinate telling us where to place the apple. An easy choice for the coordinate arguments would be x and y. But we already have instance variables of the same name:

    class Apple {
        int x, y;
        ...

        public void moveTo(int x, int y) {
            System.out.println("Moving apple to " + x + ", " + y);
            ...
        }
        ...
    }

If the apple is currently at position (20, 40) and we call moveTo(40, 50) what do you think that println() statement will show? Inside moveTo(), the x and y names refer only to the arguments with those names. Our output would be:

Moving apple to 40, 50

If we can’t get to the x and y instance variables, how can we move the apple? Turns out Java understands shadowing and provides a mechanism for working around these situations.

The “this” reference

You can use the special reference this any time you need to refer explicitly to the current object or a member of the current object. Often you don’t need to use this, because the reference to the current object is implicit; such is the case when using unambiguously named instance variables inside a class. But we can use this to refer explicitly to instance variables in our object, even if they are shadowed. The following example shows how we can use this to allow argument names that shadow instance variable names. This is a fairly common technique because it saves having to make up alternative names. Here’s how we could implement our moveTo() method with shadowed variables:

    class Apple {
        int x, y;
        ...

        public void moveTo(int x, int y) {
            System.out.println("Moving apple to " + x + ", " + y);
            this.x = x;
            if (y > diameter / 2) {
                this.y = y;
            } else {
                this.y = (int)(diameter / 2);
            }
        }
        ...
    }

In this example, the expression this.x refers to the instance variable x and assigns it the value of the local variable x, which would otherwise hide its name. We do the same for this.y but add a little protection to make sure we don’t move the apple below our ground. The only reason we need to use this in the previous example is because we’ve used argument names that hide our instance variables, and we want to refer to the instance variables. You can also use the this reference any time you want to pass a reference to “the current” enclosing object to some other method like we did for the graphical version of our “Hello Java” application in “HelloJava2: The Sequel”.

Static Methods

Static methods (class methods), like static variables, belong to the class and not to individual instances of the class. What does this mean? Well, foremost, a static method lives outside of any particular class instance. It can be invoked by name, through the class name, without any objects around. Because it is not bound to a particular object instance, a static method can directly access only other static members (static variables and other static methods) of the class. It can’t directly see any instance variables or call any instance methods, because to do so we’d have to ask, “on which instance?” Static methods can be called from instances, syntactically just like instance methods, but the important thing is that they can also be used independently.

Our isTouching() method uses a static method: Math.sqrt(), which is defined by the java.lang.Math class; we’ll explore this class in detail in Chapter 8. For now, the important thing to note is that Math is the name of a class and not an instance of a Math object.3 Because static methods can be invoked wherever the class name is available, class methods are closer to C-style functions. Static methods are particularly useful for utility methods that perform work that is useful either independently of instances or in working on instances. For example, in our Apple class, we could enumerate all of the available sizes as human-readable strings from the constants we created in “Accessing Fields and Methods”:

    class Apple {
        ...
        public static String[] getAppleSizes() {
            // Return names for our constants
            // The index of the name should match the value of the constant
            return new String[] { "SMALL", "MEDIUM", "LARGE" };
        }
        ...
    }

Here, we’ve defined a static method, getAppleSizes(), that returns an array of strings containing apple size names. We make the method static because the list of sizes is the same regardless of what size any given instance of Apple might be. We can still use getAppleSizes() from within an instance of Apple if we wanted, just like an instance method. We could change the (non-static) printDetails method to print a size name rather than an exact diameter, for example:

    public void printDetails() {
        System.out.println("  mass: " + mass);
        // Print the exact diameter:
        //System.out.println("  diameter: " + diameter);
        // Or a nice, human-friendly approximate
        String niceNames[] = getAppleSizes();
        if (diameter < 5.0f) {
            System.out.println(niceNames[SMALL]);
        } else if (diameter < 10.0f) {
            System.out.println(niceNames[MEDIUM]);
        } else {
            System.out.println(niceNames[LARGE]);
        }
        System.out.println("  position: (" + x + ", " + y +")");
    }

However, we can also call it from other classes, using the Apple class name with the dot notation. For example, the very first PrintAppleDetails class could use similar logic to print a summary statement using our static method and static variables like so:

public class PrintAppleDetails {
    public static void main(String args[]) {
        String niceNames[] = Apple.getAppleSizes();
        Apple a1 = new Apple();
        System.out.println("Apple a1:");
        System.out.println("  mass: " + a1.mass);
        System.out.println("  diameter: " + a1.diameter);
        System.out.println("  position: (" + a1.x + ", " + a1.y +")");
        if (a1.diameter < 5.0f) {
            System.out.println("This is a " + niceNames[Apple.SMALL] + " apple.");
        } else if (a1.diameter < 10.0f) {
            System.out.println("This is a " + niceNames[Apple.MEDIUM] + " apple.");
        } else {
            System.out.println("This is a " + niceNames[Apple.LARGE] + " apple.");
        }
    }
}

Here we have our trusty instance of the Apple class, a1, but it is not needed to get the list of our sizes. Notice that we load the list of nice names before we create a1. But everything still works as seen in the output:

Apple a1:
  mass: 0.0
  diameter: 1.0
  position: (0, 0)
This is a SMALL apple.

Static methods also play an important role in various design patterns, where you limit the use of the new operator for a class to one method—a static method called a factory method. We’ll talk more about object construction in “Constructors”. There’s no naming convention for factory methods, but it is common to see usage like this:

    Apple bigApple = Apple.createApple(Apple.LARGE);

We won’t be writing any factory methods, but you’re likely to find them in the wild, especially when looking up questions on sites like Stack Overflow.

Initializing Local Variables

Unlike instance variables that receive default values if we don’t provide an explicit one, local variables must be initialized before they can be used. It’s a compile-time error to try to access a local variable without first assigning it a value:

    int foo;

    void myMethod() {
        int bar;

        foo += 1;  // This is ok, foo has the default 0
        bar += 1;  // compile-time error, bar is uninitialized

        bar = 99;
        bar += 1;  // Now this calculation would be ok
    }

Notice that this doesn’t imply local variables have to be initialized when declared, just that the first time they are referenced must be in an assignment. More subtle possibilities arise when making assignments inside conditionals:

    void myMethod {
      int bar;
      if ( someCondition ) {
        bar = 42;
        ...
      }
      bar += 1;   // Still a compile-time error, foo may not be initialized
    }

In this example, bar is initialized only if someCondition is true. The compiler doesn’t let you make this wager, so it flags the use of bar as an error. We could correct this situation in several ways. We could initialize the variable to a default value in advance or move the usage inside the conditional. We could also make sure the path of execution doesn’t reach the uninitialized variable through some other means, depending on what makes sense for our particular application. For example, we could simply make sure that we assign bar a value in both the if and else branch. Or we could return from the method abruptly:

    void myMethod {
        int bar;
        ...
        if ( someCondition ) {
            bar = 42;
            ...
        } else {
            return;
        }
        bar += 1;  // This is ok!
        ...
    }

In this case, there’s no chance of reaching bar in an uninitialized state, so the compiler allows the use of bar after the conditional.

Why is Java so picky about local variables? One of the most common (and insidious) sources of errors in some other languages like C or C++ is forgetting to initialize local variables, so Java tries to help out.

Argument Passing and References

In the beginning of Chapter 4, we described the distinction between primitive types, which are passed by value (by copying), and objects, which are passed by reference. Now that we’ve got a better handle on methods in Java, let’s walk through an example:

    void myMethod( int j, SomeKindOfObject o ) {
        ...
    }

    // use the method
    int i = 0;
    SomeKindOfObject obj = new SomeKindOfObject();
    myMethod( i, obj );

The chunk of code calls myMethod(), passing it two arguments. The first argument, i, is passed by value; when the method is called, the value of i is copied into the method’s parameter (a local variable to it) named j. If myMethod() changes the value of j, it’s changing only its copy of the local variable.

In the same way, a copy of the reference to obj is placed into the reference variable o of myMethod(). Both references refer to the same object, so any changes made through either reference affect the actual (single) object instance. If we change the value of, say, o.size, the change is visible both as o.size (inside myMethod()) or as obj.size (in the calling method). However, if myMethod() changes the reference o itself—to point to another object—it’s affecting only its local variable reference. It doesn’t affect the caller’s variable obj, which still refers to the original object. In this sense, passing the reference is like passing a pointer in C and unlike passing by reference in C++.

What if myMethod() needs to modify the calling method’s notion of the obj reference as well (i.e., make obj point to a different object)? The easy way to do that is to wrap obj inside some kind of object. For example, we could wrap the object up as the lone element in an array:

    SomeKindOfObject [] wrapper = new SomeKindOfObject [] { obj
    };

All parties could then refer to the object as wrapper[0] and would have the ability to change the reference. This is not aesthetically pleasing, but it does illustrate that what is needed is the level of indirection.

Another possibility is to use this to pass a reference to the calling object. In that case, the calling object serves as the wrapper for the reference. Let’s look at a piece of code that could be from an implementation of a linked list:

    class Element {
        public Element nextElement;

        void addToList( List list ) {
            list.insertElement( this );
        }
    }

    class List {
        void insertElement( Element element ) {
            ...
            element.nextElement = getFirstElement();
            setFirstElement(element);
        }
    }

Every element in a linked list contains a pointer to the next element in the list. In this code, the Element class represents one element; it includes a method for adding itself to the list. The List class itself contains a method for adding an arbitrary Element to the list. The method addToList() calls insertElement() with the argument this (which is, of course, an Element). insertElement() can use the this reference which was passed in to modify the Element’s nextElement instance variable and then again to update the start of the list. The same technique can be used in conjunction with interfaces to implement callbacks for arbitrary method invocations.

Wrappers for Primitive Types

As we described in Chapter 4, there is a schism in the Java world between class types (i.e., objects) and primitive types (i.e., numbers, characters, and Boolean values). Java accepts this tradeoff simply for efficiency reasons. When you’re crunching numbers, you want your computations to be lightweight; having to use objects for primitive types complicates performance optimizations. For the times you want to treat values as objects, Java supplies a standard wrapper class for each of the primitive types, as shown in Table 5-1.

Table 5-1. Primitive type wrappers
Primitive Wrapper

void

java.lang.Void

boolean

java.lang.Boolean

char

java.lang.Character

byte

java.lang.Byte

short

java.lang.Short

int

java.lang.Integer

long

java.lang.Long

float

java.lang.Float

double

java.lang.Double

An instance of a wrapper class encapsulates a single value of its corresponding type. It’s an immutable object that serves as a container to hold the value and let us retrieve it later. You can construct a wrapper object from a primitive value or from a String representation of the value. The following statements are equivalent:

    Float pi = new Float( 3.14 );
    Float pi = new Float( "3.14" );

The wrapper constructors throw a NumberFormatException when there is an error in parsing a string.

Each of the numeric type wrappers implements the java.lang.Number interface, which provides “value” methods access to its value in all the primitive forms. You can retrieve scalar values with the methods doubleValue(), floatValue(), longValue(), intValue(), shortValue(), and byteValue():

    Double size = new Double ( 32.76 );

    double d = size.doubleValue();     // 32.76
    float f = size.floatValue();       // 32.76
    long l = size.longValue();         // 32
    int i = size.intValue();           // 32

This code is equivalent to casting the primitive double value to the various types.

The most common need for a wrapper is when you want to pass a primitive value to a method that requires an object. For example, in Chapter 7, we’ll look at the Java Collections API, a sophisticated set of classes for dealing with object groups, such as lists, sets, and maps. All the Collections APIs work on object types, so primitives must be wrapped when stored in them. We’ll see in the next section that Java makes this wrapping process automatic. For now, however, let’s do it ourselves. As we’ll see, a List is an extensible collection of Objects. We can use wrappers to hold numbers in a List (along with other objects):

    // Simple Java code
    List myNumbers = new ArrayList();
    Integer thirtyThree = new Integer( 33 );
    myNumbers.add( thirtyThree );

Here, we have created an Integer wrapper object so that we can insert the number into the List, using the add() method, which accepts an object. Later, when we are extracting elements from the List, we can recover the int value as follows:

    // Simple Java code
    Integer theNumber = (Integer)myNumbers.get(0);
    int n = theNumber.intValue();           // 33

As we alluded to earlier, allowing Java to do this for us (“autoboxing”) makes the code more concise and safer. The usage of the wrapper class is mostly hidden from us by the compiler, but it is still being used internally:

    // Java code using autoboxing and generics
    List<Integer> myNumbers = new ArrayList<Integer>();
    myNumbers.add( 33 );
    int n = myNumbers.get( 0 );

We’ll see more of generics later.

Method Overloading

Method overloading is the ability to define multiple methods with the same name in a class; when the method is invoked, the compiler picks the correct one based on the arguments passed to the method. This implies that overloaded methods must have different numbers or types of arguments. (In “Overriding Methods”, we’ll look at method overriding, which occurs when we declare methods with identical signatures in subclasses.)

Method overloading (also called ad-hoc polymorphism ) is a powerful and useful feature. The idea is to create methods that act in the same way on different types of arguments. This creates the illusion that a single method can operate on many types of arguments. The print() method in the standard PrintStream class is a good example of method overloading in action. As you’ve probably deduced by now, you can print a string representation of just about anything using this expression:

    System.out.print( argument )

The variable out is a reference to an object (a PrintStream) that defines nine different, “overloaded” versions of the print() method. The versions take arguments of the following types: Object, String, char[], char, int, long, float, double, and boolean.

    class PrintStream {
        void print( Object arg ) { ... }
        void print( String arg ) { ... }
        void print( char [] arg ) { ... }
        ...
    }

You can invoke the print() method with any of these types as an argument, and it’s printed in an appropriate way. In a language without method overloading, this requires something more cumbersome, such as a uniquely named method for printing each type of object. In that case, it’s your responsibility to figure out what method to use for each data type.

In the previous example, print() has been overloaded to support two reference types: Object and String. What if we try to call print() with some other reference type? Say, a Date object? When there’s not an exact type match, the compiler searches for an acceptable, assignable match. Since Date, like all classes, is a subclass of Object, a Date object can be assigned to a variable of type Object. It’s therefore an acceptable match, and the Object method is selected.

What if there’s more than one possible match? For example, what if we want to print the literal "Hi there"? That literal is assignable to either String (since it is a String) or to Object. Here, the compiler makes a determination as to which match is “better” and selects that method. In this case, it’s the String method.

The intuitive explanation for this is that the String class is “closer” to the literal "Hi there" in the inheritance hierarchy. It is a more specific match. A slightly more rigorous way of specifying it would be to say that a given method is more specific than another method if the argument types of the first method are all assignable to the argument types of the second method. In this case, the String method is more specific because type String is assignable to type Object. The reverse is not true.

If you’re paying close attention, you may have noticed we said that the compiler resolves overloaded methods. Method overloading is not something that happens at runtime; this is an important distinction. It means that the selected method is chosen once, when the code is compiled. Once the overloaded method is selected, the choice is fixed until the code is recompiled, even if the class containing the called method is later revised and an even more specific overloaded method is added. This is in contrast to overridden methods, which are located at runtime and can be found even if they didn’t exist when the calling class was compiled. In practice, this distinction will not usually be relevant to you, as you will likely recompile all of the necessary classes at the same time. We’ll talk about method overriding later in the chapter.

Object Creation

Objects in Java are allocated on a system “heap” memory space. Unlike some other languages, however, we needn’t manage that memory ourselves. Java takes care of memory allocation and deallocation for you. Java explicitly allocates storage for an object when you create it with the new operator. More importantly, objects are removed by garbage collection when they’re no longer referenced.

Constructors

Objects are allocated with the new operator using a constructor. A constructor is a special method with the same name as its class and no return type. It’s called when a new class instance is created, which gives the class an opportunity to set up the object for use. Constructors, like other methods, can accept arguments and can be overloaded (they are not, however, inherited like other methods).

    class Date {
        long time;

        Date() {
            time = currentTime();
        }

        Date( String date ) {
            time = parseDate( date );
        }
        ...
    }

In this example, the class Date has two constructors. The first takes no arguments; it’s known as the default constructor. Default constructors play a special role: if we don’t define any constructors for a class, an empty default constructor is supplied for us. The default constructor is what gets called whenever you create an object by calling its constructor with no arguments. Here we have implemented the default constructor so that it sets the instance variable time by calling a hypothetical method, currentTime(), which resembles the functionality of the real java.util.Date class. The second constructor takes a String argument. Presumably, this String contains a string representation of the time that can be parsed to set the time variable. Given the constructors in the previous example, we create a Date object in the following ways:

    Date now = new Date();
    Date christmas = new Date("Dec 25, 2020");

In each case, Java chooses the appropriate constructor at compile time based on the rules for overloaded method selection.

If we later remove all references to an allocated object, it’ll be garbage-collected, as we’ll discuss shortly:

    christmas = null;          // fair game for the garbage collector

Setting this reference to null means it’s no longer pointing to the "Dec 25, 2006" date object. Setting the variable christmas to any other value would have the same effect. Unless the original date object is referenced by another variable, it’s now inaccessible and can be garbage-collected. We’re not suggesting that you have to set references to null to get the values garbage-collected. Often this just happens naturally when local variables fall out of scope, but items referenced by instance variables of objects live as long as the object itself lives (through references to it) and static variables live effectively forever.

A few more notes: constructors can’t be declared abstract, synchronized, or final (we’ll define the rest of those terms later). Constructors can, however, be declared with the visibility modifiers public, private, or protected, just like other methods, to control their accessibility. We’ll talk in detail about visibility modifiers in the next chapter.

Working with Overloaded Constructors

A constructor can refer to another constructor in the same class or the immediate superclass using special forms of the this and super references. We’ll discuss the first case here and return to that of the superclass constructor after we have talked more about subclassing and inheritance. A constructor can invoke another overloaded constructor in its class using the self-referential method call this() with appropriate arguments to select the desired constructor. If a constructor calls another constructor, it must do so as its first statement:

    class Car {
        String model;
        int doors;

        Car( String model, int doors ) {
            this.model = model;
            this.doors = doors;
            // other, complicated setup
            ...
        }

        Car( String model ) {
            this( model, 4 /* doors */ );
        }
        ...
    }

In this example, the class Car has two constructors. The first, more explicit, one accepts arguments specifying the car’s model and its number of doors. The second constructor takes just the model as an argument and, in turn, calls the first constructor with a default value of four doors. The advantage of this approach is that you can have a single constructor do all the complicated setup work; other auxiliary constructors simply feed the appropriate arguments to that constructor.

The special call to this() must appear as the first statement in our delegating constructor. The syntax is restricted in this way because there’s a need to identify a clear chain of command in the calling of constructors. At the end of the chain, Java invokes the constructor of the superclass (if we don’t do it explicitly) to ensure that inherited members are initialized properly before we proceed.

There’s also a point in the chain, just after invoking the constructor of the superclass, where the initializers of the current class’s instance variables are evaluated. Before that point, we can’t even reference the instance variables of our class. We’ll explain this situation again in complete detail after we have talked about inheritance.

For now, all you need to know is that you can invoke a second constructor (delegate to it) only as the first statement of your constructor. For example, the following code is illegal and causes a compile-time error:

    Car( String m ) {
        int doors = determineDoors();
        this( m, doors );   // Error: constructor call
                            // must be first statement
    }

The simple model name constructor can’t do any additional setup before calling the more explicit constructor. It can’t even refer to an instance member for a constant value:

    class Car {
        ...
        final int default_doors = 4;
        ...

        Car( String m ) {
            this( m, default_doors ); // Error: referencing
                                      // uninitialized variable
        }
        ...
    }

The instance variable defaultDoors is not initialized until a later point in the chain of constructor calls setting up the object, so the compiler doesn’t let us access it yet. Fortunately, we can solve this particular problem by using a static variable instead of an instance variable:

    class Car {
        ...
        static final int DEFAULT_DOORS = 4;
        ...

        Car( String m ) {
            this( m, DEFAULT_DOORS );  // Okay!
        }
        ...
    }

The static members of a class are initialized when the class is first loaded into the virtual machine, so it’s safe to access them in a constructor.

Object Destruction

Now that we’ve seen how to create objects, it’s time to talk about their destruction. If you’re accustomed to programming in C or C++, you’ve probably spent time hunting down memory leaks in your code. Java takes care of object destruction for you; you don’t have to worry about traditional memory leaks, and you can concentrate on more important programming tasks.4

Garbage Collection

Java uses a technique known as garbage collection to remove objects that are no longer needed. The garbage collector is Java’s grim reaper. It lingers in the background, stalking objects and awaiting their demise. It finds and watches them, periodically counting references to them to see when their time has come. When all references to an object are gone and it’s no longer accessible, the garbage-collection mechanism declares the object unreachable and reclaims its space back to the available pool of resources. An unreachable object is one that can no longer be found through any combination of “live” references in the running application.

Garbage collection uses a variety of algorithms; the Java virtual machine architecture doesn’t require a particular scheme. It’s worth noting, however, how some implementations of Java have accomplished this task. In the beginning, Java used a technique called “mark and sweep.” In this scheme, Java first walks through the tree of all accessible object references and marks them as alive. Java then scans the heap, looking for identifiable objects that aren’t marked. In this technique, Java is able to find objects on the heap because they are stored in a characteristic way and have a particular signature of bits in their handles unlikely to be reproduced naturally. This kind of algorithm doesn’t become confused by the problem of cyclic references, in which objects can mutually reference each other and appear alive even when they are dead (Java handles this problem automatically). This scheme wasn’t the fastest method, however, and caused pauses in the program. Since then, implementations have become much more sophisticated.

Modern Java garbage collectors effectively run continuously without forcing any lengthy delay in execution of the Java application. Because they are part of a runtime system, they can also accomplish some things that could not be done statically. Sun’s Java implementation divides the memory heap into several areas for objects with different estimated lifespans. Short-lived objects are placed on a special part of the heap, which reduces the time to recycle them drastically. Objects that live longer can be moved to other, less volatile parts of the heap. In recent implementations, the garbage collector can even “tune” itself by adjusting the size of parts of the heap based on the actual application performance. The improvement in Java’s garbage collection since the early releases has been remarkable and is one of the reasons that Java is now roughly equivalent in speed to many traditional languages which place the burden of memory management on the shoulders of the programmer.

In general, you do not have to concern yourself with the garbage-collection process. But one garbage-collection method can be useful for debugging. You can prompt the garbage collector to make a clean sweep explicitly by invoking the System.gc() method. This method is completely implementation-dependent and may do nothing, but it can be used if you want some guarantee that Java has cleaned up before you do an activity.

Packages

Even sticking to simpler examples, you may have noticed that solving problems in Java requires creating a number of classes. For our game classes above, we have our apples and our physicists and our playing field, just to name a few. For more complex applications or libraries, you can have hundreds or even thousands of classes. You need a way to organize things and Java uses the notion of a package to accomplish this task.

Recall our second Hello World example in Chapter 2. The first few lines in the file show us a lot of information on where the code will live:

    import javax.swing.*;

    public class HelloJava {
      public static void main( String[] args ) {
        JFrame frame = new JFrame("Hello, Java!");
        JLabel label = new JLabel("Hello, Java!", JLabel.CENTER );
        ...

We named the Java file according to the main class in that file. When we talk about organizing things that go in files, you might naturally think of using folders to organize those files in turn. That is essentially what Java does. Packages map onto folder names much the way classes map onto file names. If you were looking at the Java source code for the Swing components we used in HelloJava, for example, you would find a folder named javax, and under that, one named swing, and under that you would find files like JFrame.java and JLabel.java.

Importing Classes

One of Java’s biggest strengths lies in the vast collection of supporting libraries available under both commercial and open source licensing. Need to output a PDF? There’s a library for that. Need to import a spreadsheet? There’s a library for that. Need to turn on that smart lightbulb in the basement from your web server? There’ a library for that, too. If computers are doing some task or other, you will almost always find a Java library to help you write code to perform that task as well.

Importing Individual Classes

In programming, you’ll often hear the maxim that “less is more”. Less code is more maintainable. Less overhead means more throughput, etc. etc. (Although in pursuing this way of coding, we do want to remind you to follow another famous quote from no less a thinker than Einstein: “Everything should be made as simple as possible, but no simpler.”) If you only need one or two classes from an external package, you can import exactly those classes. This makes your code a little more readable—others know exactly what classes you’ll be using.

Let’s re-examine that snippet of HelloJava above. We used a blanket import (more on that in the next section) but we could tighten things up a bit by importing just the classes we need, like so:

    import javax.swing.JFrame;
    import javax.swing.JLabel;

    public class HelloJava {
      public static void main( String[] args ) {
        JFrame frame = new JFrame("Hello, Java!");
        JLabel label = new JLabel("Hello, Java!", JLabel.CENTER );
        ...

This type of import setup is certainly more verbose when writing and reading, but again, it means anyone reading or compiling your code knows exactly what other dependencies exist. Many IDEs even have an “Optimize Imports” function that will automatically find those dependencies and list them individually. Once you get in the habit of listing and seeing these explicit imports, it is suprising how useful they become when orienting yourself in a new (or perhaps long forgotten) class.

Importing Entire Packages

Of course, not every package lends itself to onesy-twosy imports. That same Swing package, javax.swing, is a great example. If you are writing a graphical desktop application, you’ almost certainly use Swing—and lots and lots of its components. You can import every class in the package using the syntax we glossed over earlier:

    import javax.swing.*;

    public class HelloJava {
      public static void main( String[] args ) {
        JFrame frame = new JFrame("Hello, Java!");
        JLabel label = new JLabel("Hello, Java!", JLabel.CENTER );
        ...

The * is a sort of wild card for class imports. This version of the import statement tells the compiler to have every class in the package ready to use. You’ll see this type of import quite often for many of the common Java packages such as AWT, Swing, Utils, and I/O. Again, it works for any package, but where it makes sense to be more specific, you’ll gain some compile-time performance boosts and improve the readability of your code.

Skipping Imports

You have another option for using external classes from other packages; you do not have to import them at all. You can use their fully-qualified names right in your code. For example, our HelloJava class used the JFrame and JLabel classes from the javax.swing package. We could import only the JLabel class if we wanted:

    import javax.swing.JLabel;

    public class HelloJava {
      public static void main( String[] args ) {
        javax.swing.JFrame frame = new javax.swing.JFrame("Hello, Java!");
        JLabel label = new JLabel("Hello, Java!", JLabel.CENTER );
        ...

This might seem overly verbose for one line where we creage our frame, but in larger classes with an already lengthy list of imports, one-off usages can actually make your code more readable. Such a fully-qualified entry often points to the sole use of this class within a file. If you were using it many times, you would import it. This type of usage is never a requirement, but you will see it in the wild from time to time.

Custom Packages

As you continue learning Java and write more code and solve larger problems, you will undoubtedly start to collect a larger and larger number of classes. You can use packages yourself to help organize that collection. You use the package keyword to declare a custom package. As noted at the top of this section, you then place the file with your class inside a folder structure corresponding to package name. As a quick reminder, packages use all lower-case names (by convention) separated by periods such as in our graphical interface package, javax.swing.

Another convention applied widely to package names is something called “reverse domain name” naming. Apart from the packages associated directly with Java, third-party libraries and other contributed code is usually organized using the domain name of the company or individual’s email address. For example, the Mozilla Foundation has contributed a variety of Java libraries to the open source community. Most of those libraries and utilities will be in packages starting with Mozilla’s domain, mozilla.org, in reverse order: org.mozilla. This reverse naming has the handy (and intended) side effect of keeping the folder structure at the top fairly small. It is not uncommon to have good-sized projects that use libraries from only the com and org top-level domains.

If you are building your own packages separate from any company or contract work, you can use your email address and reverse it similar to company domain names. Another popular option for code distributed online is to use the domain of your hosting provider. GitHub, for example, hosts many, many Java projects for hobbyists and enthusiasts. You might create a package named com.github.myawesomeproject where “myawesomeproject” would obviously be replaced by your actual project name. Be aware that repositories at sites like GitHub often allow names that are not valid in package names. You might have a project named my-awesome-project, but dashes are not allowed in any portion of a package name. Often such illegal characters are simply omitted to create a valid name.

You may have already taken a peek at more of the examples in the code archive for this book. If so, you will have noticed we placed them in packages. While the organizing of classes within packages is a wooly topic with no great best practices available, we’ve taken an approach designed to make the examples easy to locate as you’re reading the book. For small examples in a chapter, you’ll see a package like ch05. For the on-going game example, we use game. We could rewrite our very first examples to fit into this scheme quite easily:

    package ch02;

    import javax.swing.*;

    public class HelloJava {
      public static void main( String[] args ) {
        JFrame frame = new JFrame("Hello, Java!");
        JLabel label = new JLabel("Hello, Java!", JLabel.CENTER );
        ...

We would need to create the folder structure ch02 and then place our HelloJava.java file in that ch02 folder. We could then compile and run the example at the command line by staying at the top of the folder hierarchy and using the fully-qualified path of the file and name of the class like so:

    %javac ch02/HelloJava.java
    %java ch02.HelloJava
    

If you are using an IDE, it will happily manage these package issues for you. Simply create and organize your classes and continue to identify the main class that kicks of your program.

Member Visibility and Access

We’ve talked a bit about the access modifiers you can use when declaring variables and methods. Making something public means anyone, anywhere can see your variable or call your method. Making something protected means any subclass can access the variable, call the method or override the method to provide some alternate functionality more appropriate to your subclass. The private modifier means the variable or method is only available within the class itself.

Packages affect protected members. In addition to being accessible by any subclass, such members are visible and overridable by other classes in that package. Packages also come into play if you leave off the modifier altogether. Consider some example text components in the custom package mytools.text such as shown in Figure 5-4:

lj5e 0504
Figure 5-4. Packages and class visibility

The class TextComponent has no modifier. It has default visibility or “package private” visibility. This means that other classes in the same package can access the class, but those classes outside the package cannot. This can be very useful for implementation-specific classes or internal helpers. You can use the package private elements freely, but other programmers will use only your public and protected elements. Figure 5-5 shows some more details with variables and methods being used by both subclasses and external code.

lj5e 0505
Figure 5-5. Packages and member visibility

Notice that extending the TextArea class gives you access to the public getText() and setText() methods as well as the protected method formatText(). But MyTextDisplay (more on subclasses and extends shortly in “Subclassing and Inheritance”) does not have access to the package private variable linecount. Within the mytools.text package where we create the TextEditor class, however, we can get to linecount as well those methods that are public or protected. Our internal storage for the content, text, remains private and unavailable to anyone other than the TextArea class itself.

Table 5-2 summarizes the levels of visibility available in Java; it runs generally from most to least restrictive. Methods and variables are always visible within a declaring class itself, so the table doesn’t address that scope.

Table 5-2. Visibility modifiers
Modifier Visibility outside the class

private

None

No modifier (default)

Classes in the package

protected

Classes in package and subclasses inside or outside the package

public

All classes

Compiling With Packages

You’ve already seen a few examples of using a fully-qualified class name to compile a simple example. If you’re not using an IDE, you have other options available to you. For example, you may wish to compile all of the classes in a given package. If so, you can

    %javac ch02/*.java
    %java ch02.HelloJava
    

Note that for commercial applications, you often see more complex package names meant to avoid collisions. A common practice is to reverse the Internet domain name of your company. For example, this book from O’Reilly might more appropriately use a full package prefix such as com.oreilly.learningjava5e. Each chapter would be a subpackage under that prefix. Compiling and running classes in such packages is fairly straightforward if a bit verbose:

    %javac com/oreilly/learningjava5e/ch02/*.java
    %java com.oreilly.learningjava5e.ch02.HelloJava
    

The javac command also understands basic class dependency. If your main class uses a few other classes in the same source hierarchy—even if they are not all in the same package—compiling that main class will “pick up” the other, dependent classes and compile them as well.

Beyond simple programs with a few classes, though, you really are more likely to rely on your IDE or a build management tool such as gradle or maven. Those tools are outside the scope of this book, but there are many references for them online. Maven in particular is great for managing large projects with many dependencies. See Maven, The Definitive Guide by Maven creator Jason Van Zyl and his team at Sonatype for a true exploration of the features and capabilities of this popular tool.5

Advanced Class design

You may recall from “HelloJava2: The Sequel” that we had two classes in the same file. That simplified the compiling process but didn’t grant either class any special access to the other. As you start thinking about more complex problems, you will encounter cases where more advanced class design that does grant special access is not just handy but critical to writing maintainable code.

Subclassing and Inheritance

Classes in Java exist in a hierarchy. A class in Java can be declared as a subclass of another class using the extends keyword. A subclass inherits variables and methods from its superclass and can use them as if they were declared within the subclass itself:

    class Animal {
        float weight;
        ...
        void eat() {
            ...
        }
        ...
    }

    class Mammal extends Animal {
        // inherits weight
        int heartRate;
        ...

        // inherits eat()
        void breathe() {
            ...
        }
    }

In this example, an object of type Mammal has both the instance variable weight and the method eat(). They are inherited from Animal.

A class can extend only one other class. To use the proper terminology, Java allows single inheritance of class implementation. Later in this chapter, we’ll talk about interfaces, which take the place of multiple inheritance as it’s primarily used in other languages.

A subclass can be further subclassed. Normally, subclassing specializes or refines a class by adding variables and methods (you cannot remove or hide variables or methods by subclassing). For example:

    class Cat extends Mammal {
        // inherits weight and heartRate
        boolean longHair;
        ...

        // inherits eat() and breathe()
        void purr() {
            ...
        }
    }

The Cat class is a type of Mammal that is ultimately a type of Animal. Cat objects inherit all the characteristics of Mammal objects and, in turn, Animal objects. Cat also provides additional behavior in the form of the purr() method and the longHair variable. We can denote the class relationship in a diagram, as shown in Figure 5-6.

A subclass inherits all members of its superclass not designated as private. As we’ll discuss shortly, other levels of visibility affect which inherited members of the class can be seen from outside of the class and its subclasses, but at a minimum, a subclass always has the same set of visible members as its parent. For this reason, the type of a subclass can be considered a subtype of its parent, and instances of the subtype can be used anywhere instances of the supertype are allowed. Consider the following example:

    Cat simon = new Cat();
    Animal creature = simon;
lj5e 0506
Figure 5-6. A class hierarchy

The Cat instance simon in this example can be assigned to the Animal type variable creature because Cat is a subtype of Animal. Similarly, any method accepting an Animal object would accept an instance of a Cat or any Mammal type as well. This is an important aspect of polymorphism in an object-oriented language such as Java. We’ll see how it can be used to refine a class’s behavior, as well as add new capabilities to it.

Shadowed Variables

We have seen that a local variable of the same name as an instance variable shadows (hides) the instance variable. Similarly, an instance variable in a subclass can shadow an instance variable of the same name in its parent class, as shown in Figure 5-7. We’re going to cover the details of this variable hiding now for completeness and in preparation for more advanced topics, but in practice you should almost never do this. It is much better in practice to structure your code to clearly differentiate variables using different names or naming conventions.

In Figure 5-7, the variable weight is declared in three places: as a local variable in the method foodConsumption() of the class Mammal, as an instance variable of the class Mammal, and as an instance variable of the class Animal. The actual variable selected when you reference it in the code would depend on the scope in which we are working and how you qualify the reference to it.

lj5e 0507
Figure 5-7. The scope of shadowed variables

In the previous example, all variables were of the same type. A slightly more plausible use of shadowed variables would involve changing their types. We could, for example, shadow an int variable with a double variable in a subclass that needs decimal values instead of integer values. We can do this without changing the existing code because, as its name suggests, when we shadow variables, we don’t replace them but instead mask them. Both variables still exist; methods of the superclass see the original variable, and methods of the subclass see the new version. The determination of what variables the various methods see occurs at compile time.

Here’s a simple example:

    class IntegerCalculator {
        int sum;
        ...
    }

    class DecimalCalculator extends IntegerCalculator {
        double sum;
        ...
    }

In this example, we shadow the instance variable sum to change its type from int to double.6 Methods defined in the class IntegerCalculator see the integer variable sum, while methods defined in DecimalCalculator see the floating-point variable sum. However, both variables actually exist for a given instance of DecimalCalculator, and they can have independent values. In fact, any methods that DecimalCalculator inherits from IntegerCalculator actually see the integer variable sum.

Because both variables exist in DecimalCalculator, we need a way to reference the variable inherited from IntegerCalculator. We do that using the super keyword as a qualifier on the reference:

    int s = super.sum;

Inside of DecimalCalculator, the super keyword used in this manner selects the sum variable defined in the superclass. We’ll explain the use of super more fully in a bit.

Another important point about shadowed variables has to do with how they work when we refer to an object by way of a less derived type (a parent type). For example, we can refer to a DecimalCalculator object as an IntegerCalculator by using it via a variable of type IntegerCalculator. If we do so and then access the variable sum, we get the integer variable, not the decimal one:

    DecimalCalculator dc = new DecimalCalculator();
    IntegerCalculator ic = dc;

    int s = ic.sum;       // accesses IntegerCalculator sum

The same would be true if we accessed the object using an explicit cast to the IntegerCalculator type or when passing an instance into a method that accepts that parent type.

To reiterate, the usefulness of shadowed variables is limited. It’s much better to abstract the use of variables like this in other ways than to use tricky scoping rules. However, it’s important to understand the concepts here before we talk about doing the same thing with methods. We’ll see a different and more dynamic type of behavior when methods shadow other methods, or to use the correct terminology, override other methods.

Overriding Methods

We have seen that we can declare overloaded methods (i.e., methods with the same name but a different number or type of arguments) within a class. Overloaded method selection works in the way we described on all methods available to a class, including inherited ones. This means that a subclass can define additional overloaded methods that add to the overloaded methods provided by a superclass.

A subclass can do more than that; it can define a method that has exactly the same method signature (name and argument types) as a method in its superclass. In that case, the method in the subclass overrides the method in the superclass and effectively replaces its implementation, as shown in Figure 5-8. Overriding methods to change the behavior of objects is called subtype polymorphism. It’s the usage most people think of when they talk about the power of object-oriented languages.

lj5e 0508
Figure 5-8. Method overriding

In Figure 5-8, Mammal overrides the reproduce() method of Animal, perhaps to specialize the method for the behavior of mammals giving birth to live young.7 The Cat object’s sleeping behavior is also overridden to be different from that of a general Animal, perhaps to accommodate cat naps. The Cat class also adds the more unique behaviors of purring and hunting mice.

From what you’ve seen so far, overridden methods probably look like they shadow methods in superclasses, just as variables do. But overridden methods are actually more powerful than that. When there are multiple implementations of a method in the inheritance hierarchy of an object, the one in the “most derived” class (the furthest down the hierarchy) always overrides the others, even if we refer to the object through a reference of one of the superclass types.8

For example, if we have a Cat instance assigned to a variable of the more general type Animal, and we call its sleep() method, we still get the sleep() method implemented in the Cat class, not the one in Animal:

    Cat simon = new Cat();
    Animal creature = simon;
      ...
    creature.sleep();       // accesses Cat sleep();

In other words, for purposes of behavior (invoking methods), a Cat acts like a Cat, regardless of whether you refer to it as such. In other respects, the variable creature here may behave like an Animal reference. As we explained earlier, access to a shadowed variable through an Animal reference would find an implementation in the Animal class, not the Cat class. However, because methods are located dynamically, searching subclasses first, the appropriate method in the Cat class is invoked, even though we are treating it more generally as an Animal object. This means that the behavior of objects is dynamic. We can deal with specialized objects as if they were more general types and still take advantage of their specialized implementations of behavior.

Interfaces

Java expands on the concept of abstract methods with interfaces. It’s often desirable to specify a group of abstract methods defining some behavior for an object without tying it to any implementation at all. In Java, this is called an interface. An interface defines a set of methods that a class must implement. A class in Java can declare that it implements an interface if it implements the required methods. Unlike extending an abstract class, a class implementing an interface doesn’t have to inherit from any particular part of the inheritance hierarchy or use a particular implementation.

Interfaces are kind of like Boy Scout or Girl Scout merit badges. A scout who has learned to build a birdhouse can walk around wearing a little sleeve patch with a picture of one. This says to the world, “I know how to build a birdhouse.” Similarly, an interface is a list of methods that define some set of behavior for an object. Any class that implements each method listed in the interface can declare at compile time that it implements the interface and wear, as its merit badge, an extra type—the interface’s type.

Interface types act like class types. You can declare variables to be of an interface type, you can declare arguments of methods to accept interface types, and you can specify that the return type of a method is an interface type. In each case, what is meant is that any object that implements the interface (i.e., wears the right merit badge) can fill that role. In this sense, interfaces are orthogonal to the class hierarchy. They cut across the boundaries of what kind of object an item is and deal with it only in terms of what it can do. A class can implement as many interfaces as it desires. In this way, interfaces in Java replace much of the need for multiple inheritance in other languages (and all its messy complications).

An interface looks, essentially, like a purely abstract class (i.e., a class with only abstract methods). You define an interface with the interface keyword and list its methods with no bodies, just prototypes (signatures):

    interface Driveable {
        boolean startEngine();
        void stopEngine();
        float accelerate( float acc );
        boolean turn( Direction dir );
    }

The previous example defines an interface called Driveable with four methods. It’s acceptable, but not necessary, to declare the methods in an interface with the abstract modifier; we haven’t done that here. More importantly, the methods of an interface are always considered public, and you can optionally declare them as so. Why public? Well, the user of the interface wouldn’t necessarily be able to see them otherwise, and interfaces are generally intended to describe the behavior of an object, not its implementation.

Interfaces define capabilities, so it’s common to name interfaces after their capabilities. Driveable, Runnable, and Updateable are good interface names. Any class that implements all the methods can then declare that it implements the interface by using a special implements clause in its class definition. For example:

    class Automobile implements Driveable {
        ...
        public boolean startEngine() {
            if ( notTooCold )
                engineRunning = true;
            ...
        }

        public void stopEngine() {
            engineRunning = false;
        }

        public float accelerate( float acc ) {
            ...
        }

        public boolean turn( Direction dir ) {
            ...
        }
        ...
    }

Here, the class Automobile implements the methods of the Driveable interface and declares itself a type of Driveable using the implements keyword.

As shown in Figure 5-9, another class, such as Lawnmower, can also implement the Driveable interface. The figure illustrates the Driveable interface being implemented by two different classes. While it’s possible that both Automobile and Lawnmower could derive from some primitive kind of vehicle, they don’t have to in this scenario.

After declaring the interface, we have a new type, Driveable. We can declare variables of type Driveable and assign them any instance of a Driveable object:

    Automobile auto = new Automobile();
    Lawnmower mower = new Lawnmower();
    Driveable vehicle;

    vehicle = auto;
    vehicle.startEngine();
    vehicle.stopEngine();

    vehicle = mower;
    vehicle.startEngine();
    vehicle.stopEngine();
lj5e 0509
Figure 5-9. Implementing the Driveable interface

Both Automobile and Lawnmower implement Driveable, so they can be considered interchangeable objects of that type.

Inner classes

All of the classes we’ve seen so far in this book have been top-level, “freestanding” classes declared at the file and package level. But classes in Java can actually be declared at any level of scope, within any set of curly braces (i.e., almost anywhere that you could put any other Java statement). These inner classes belong to another class or method as a variable would and may have their visibility limited to its scope in the same way. Inner classes are a useful and aesthetically pleasing facility for structuring code. Their cousins, anonymous inner classes, are an even more powerful shorthand that make it seem as if you can create new kinds of objects dynamically within Java’s statically typed environment. In Java, anonymous inner classes play part of the role of closures in other languages, giving the effect of handling state and behavior independently of classes.

However, as we delve into their inner workings, we’ll see that inner classes are not quite as aesthetically pleasing or dynamic as they seem. Inner classes are pure syntactic sugar; they are not supported by the VM and are instead mapped to regular Java classes by the compiler. As a programmer, you may never need be aware of this; you can simply rely on inner classes like any other language construct. However, you should know a little about how inner classes work to better understand the compiled code and a few potential side effects.

Inner classes are essentially nested classes, for example:

    Class Animal {
        Class Brain {
            ...
        }
    }

Here, the class Brain is an inner class: it is a class declared inside the scope of class Animal. Although the details of what that means require a bit of explanation, we’ll start by saying that Java tries to make the meaning, as much as possible, the same as for the other members (methods and variables) living at that level of scope. For example, let’s add a method to the Animal class:

    Class Animal {
        Class Brain {
            ...
        }
        void performBehavior() { ... }
    }

Both the inner class Brain and the method performBehavior() are within the scope of Animal. Therefore, anywhere within Animal, we can refer to Brain and performBehavior() directly, by name. Within Animal, we can call the constructor for Brain (new Brain()) to get a Brain object or invoke performBehavior() to carry out that method’s function. But neither Brain nor performBehavior() are generally accessible outside of the class Animal without some additional qualification.

Within the body of the inner Brain class and the body of the performBehavior() method, we have direct access to all the other methods and variables of the Animal class. So, just as the performBehavior() method could work with the Brain class and create instances of Brain, methods within the Brain class can invoke the performBehavior() method of Animal as well as work with any other methods and variables declared in Animal. The Brain class “sees” all of the methods and variables of the Animal class directly in its scope.

That last bit has important consequences. From within Brain, we can invoke the method performBehavior(); that is, from within an instance of Brain, we can invoke the performBehavior() method of an instance of Animal. Well, which instance of Animal? If we have several Animal objects around (say, a few Cats and Dogs), we need to know whose performBehavior() method we are calling. What does it mean for a class definition to be “inside” another class definition? The answer is that a Brain object always lives within a single instance of Animal: the one that it was told about when it was created. We’ll call the object that contains any instance of Brain its enclosing instance.

A Brain object cannot live outside of an enclosing instance of an Animal object. Anywhere you see an instance of Brain, it will be tethered to an instance of Animal. Although it is possible to construct a Brain object from elsewhere (i.e., another class), Brain always requires an enclosing instance of Animal to “hold” it. We’ll also say now that if Brain is to be referred to from outside of Animal, it acts something like an Animal.Brain class. And just as with the performBehavior() method, modifiers can be applied to restrict its visibility. All of the usual visibility modifiers apply, and inner classes can also be declared static, as we’ll discuss later.

Anonymous inner classes

Now we get to the best part. As a general rule, the more deeply encapsulated and limited in scope our classes are, the more freedom we have in naming them. We saw this in our earlier iterator example. This is not just a purely aesthetic issue. Naming is an important part of writing readable, maintainable code. We generally want to use the most concise, meaningful names possible. A corollary to this is that we prefer to avoid doling out names for purely ephemeral objects that are going to be used only once.

Anonymous inner classes are an extension of the syntax of the new operation. When you create an anonymous inner class, you combine a class declaration with the allocation of an instance of that class, effectively creating a “one-time only” class and a class instance in one operation. After the new keyword, you specify either the name of a class or an interface, followed by a class body. The class body becomes an inner class, which either extends the specified class or, in the case of an interface, is expected to implement the interface. A single instance of the class is created and returned as the value.

For example, we could revisit the graphical application from “HelloJava2: The Sequel” that creates a HelloComponent2 that extends JComponent and implements the MouseMotionListener interface. Looking at the example a little more closely, we never expect HelloComponent2 to respond to mouse motion events coming from other components. It might make more sense to create an anonymous inner class specifically to move our “Hello” label around. Indeed, since HelloComponent2 is really meant for use only by our demo. We could refactor (a common developer process done to optimize or improve code that is already working) that separate class into an inner class. Now that we know a little more about constructors and inheritence, we could also make our class an extension of JFrame rather than building a frame inside our main() method.

Here’s our HelloJava3 with just these refactorings in place.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class HelloJava3 extends JFrame {
    public static void main( String[] args ) {
        HelloJava3 demo = new HelloJava3();
        demo.setVisible( true );
    }

    public HelloJava3() {
        super( "HelloJava3" );
        add( new HelloComponent3("Hello, Inner Java!") );
        setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
        setSize( 300, 300 );
    }

    class HelloComponent3 extends JComponent {
        String theMessage;
        int messageX = 125, messageY = 95; // Coordinates of the message

        public HelloComponent3( String message ) {
            theMessage = message;
            addMouseMotionListener(new MouseMotionListener() {
                public void mouseDragged(MouseEvent e) {
                    messageX = e.getX();
                    messageY = e.getY();
                    repaint();
                }

                public void mouseMoved(MouseEvent e) { }
            });
        }

        public void paintComponent( Graphics g ) {
            g.drawString( theMessage, messageX, messageY );
        }
    }
}

Try compiling and running this example. It should behave exactly as the original HelloJava2 application does. The real difference is how we have organized the classes and who can access them (and the variables and methods inside them).

Organizing content and planning for failure

Classes are the single most important idea in Java. They form the core of every executable program, portable library, or helper. We looked at the contents of classes and how classes relate to eachother in a larger project. We know more about how to create and destroy objects based on the classes we write. And we’ve seen how inner classes (and anonymous inner classes) can help us write more maintainable code. We’ll be seeing more of these inner classes as we get into deeper topics such as threads in Chapter 9 and Swing Chapter 10.

As you build your classes, there are a few guidelines to keep in mind:

  • Hide as much of your implementation as possible. Never expose more of the internals of an object than you need to. This is key to building maintainable, reusable code. Avoid public variables in your objects, with the possible exception of constants. Instead define accessor methods to set and return values (even if they are simple types). Later, when you need to, you’ll be able to modify and extend the behavior of your objects without breaking other classes that rely on them.

  • Specialize objects only when you have to—use composition instead of inheritance. When you use an object in its existing form, as a piece of a new object, you are composing objects. When you change or refine the behavior of an object (by subclassing), you are using inheritance. You should try to reuse objects by composition rather than inheritance whenever possible because when you compose objects, you are taking full advantage of existing tools. Inheritance involves breaking down the encapsulation of an object and should be done only when there’s a real advantage. Ask yourself if you really need to inherit the whole class (do you want to be a “kind” of that object?) or whether you can just include an instance of that class in your own class and delegate some work to the included object.

  • Minimize relationships between objects and try to organize related objects in packages. Classes that work closely together can be grouped using Java packages (recall Figure 5-1) which can also hide those that are not of general interest. Only expose classes that you intend other people to use. The more loosely coupled your objects are, the easier it will be to reuse them later.

We can apply these principles even on small projects. The ch05 examples folder contains simple versions of the classes and interfaces we’ll use to create our apple tossing game. Take a moment to see how the Apple, Tree, and Physicist classes implement the GamePiece interface—like the draw() method every class includes. Notice how Field extends JComponent and how the main game class, AppleToss, extends JFrame. You can see these pieces playing together in the admittedly simple Figure 5-10. To try it yourself, compile and run the ch05.AppleToss class using the steps discussed earlier in “Custom Packages”.

lj5e 0510
Figure 5-10. Our very first game classes in action

Look over the comments in the classes. Try tweaking a few things. Add another tree. More play is always good. We’ll be building on these classes throughout the remaining chapters so getting comfortable with how they fit together will make it easier to read through upcoming discussions.

Regardless of how you organize the members in your classes, the classes in your packages, or the packages in your project, you’ll have to contend with errors cropping up. Some of those errors are simple syntax errors you’ll fix in your editor. Other errors are more interesting and may only crop up while your program is actually running. The next chapter will cover Java’ notion of these problems and help you handle them.

1 Once you have some experience with basic object-oriented concepts, you might want to look at Design Patterns: Elements of Reusable Object-Oriented Software by Gamma, Helm, Johnson, and Vlissides (Addison-Wesley). This book catalogs useful object-oriented designs that have been refined over the years by experience. Many appear in the design of the Java APIs.

2 We don’t go into the details of such argument lists, but if you’re curious and would like to do a little reading on your own, search online for the programmer-speak keyword “varargs”.

3 It turns out the Math class cannot be instantiated at all. It contains only static methods and has no public constructor. Trying to call new Math() would result in a compiler error.

4 It’s still possible in Java to write code that holds onto objects forever, consuming more and more memory. This isn’t really a leak so much as it is hoarding memory. It is also usually much easier to track down with the correct tools and techniques.

5 Maven sufficiently changed the landscape for dependency management in Java and even other JVM-based languages that you can now find tools such as Gradle which build on Maven’s success.

6 Note that a better way to design our calculators would be to have an abstract Calculator class with two subclasses: IntegerCalculator and DecimalCalculator.

7 The Platypus is a highly unusual egg-laying Mammal. We could override the reproduce() behavior again for it in its own subclass of Mammal.

8 An overridden method in Java acts like a virtual method in C++.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.178.133