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.[12] Instead, we’ll provide some common-sense hints to get you started. The following general design guidelines will hopefully make more sense after you’ve read this chapter and the next:

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. It serves as a blueprint for making class instances, which are runtime objects (individual copies) that implement the class structure. You declare a class with the class keyword. Methods and variables of the class appear inside the braces of the class declaration:

    class Pendulum {
        float mass;
        float length = 1.0f;
        int cycles;

        float getPosition ( float time ) {
            ...
        }
        ...
    }

The Pendulum class contains three variables: mass, length, and cycles. It also defines a method called getPosition(), which takes a float value as an argument and returns a float 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. Once we’ve defined the Pendulum class, we can create a Pendulum object (an instance of that class) as follows:

    Pendulum p;
    p = new Pendulum();

Recall that our declaration of the variable p doesn’t create a Pendulum object; it simply creates a variable that refers to an object of type Pendulum. We still had to create the object, using the new keyword, as shown in the second line of the preceding code snippet. Now that we’ve created a Pendulum object, we can access its variables and methods, as we’ve already seen many times:

    p.mass = 5.0;
    float pos = p.getPosition( 1.0 );

Two kinds of variables can be defined in a class: instance variables and static variables. Every object instance has its own set of instance variables; the values of these variables in one instance of an object can differ from the values in another object. We’ll talk about static variables later, which, in contrast, are shared among all instances of an object. In either case, if you don’t initialize a variable when you declare it, it’s given a default value appropriate for its type (null, zero, or false).

Figure 5-1 shows a hypothetical TextBook application that uses two instances of Pendulum through the reference-type variables bigPendulum and smallPendulum. Each of these Pendulum objects has its own copy of mass, length, and cycles. As with variables, methods defined in a class may be instance methods or static methods. An instance method is associated with just one instance of the class, but the relationship isn’t quite as simple as it is for variables. Instance methods are accessed through an object instance, but the object doesn’t really have its own “copy” of the methods (there is no duplication of code). Instead, the association means that instance methods can “see” and operate on the values of the instance variables of the object. As you’ll see in Chapter 6 when we talk about subclassing, there’s more to learn about how methods see variables. In that chapter, we’ll also discuss how instance methods can be “overridden” in child classes—a very important feature of object-oriented design. Both aspects differ from static methods, which we’ll see are really more like global functions, as they are associated with a class by name only.

Accessing Fields and Methods

Inside a class, we can access variables and call methods of the class directly by name. Here’s an example that expands on our Pendulum:

    class Pendulum {
        ...
        void resetEverything() {
            mass = 1.0;
            length = 1.0;
            cycles = 0;
            ...
            float startingPosition = getPosition( 0.0 );
        }
        ...
    }
Instances of the Pendulum class
Figure 5-1. Instances of the Pendulum class

Other classes access members of an object through a reference, using the dot selector notation that we discussed in the last chapter:

    class TextBook {
        ...
        void showPendulum() {
            Pendulum bob = new Pendulum();
            ...
            int i = bob.cycles;
            bob.resetEverything();
            bob.mass = 1.01;
            ...
        }
        ...
    }

Here we have created a second class, TextBook, that uses a Pendulum object. It creates an instance in showPendulum() and then invokes methods and accesses variables of the object through the reference bob.

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 cycles to private:

    class Pendulum {
        ...
        private int cycles;
        ...

Now we can’t access cycles from TextBook:

    class TextBook {
        ...
        void showPendulum() {
            ...
            int i = bob.cycles;  // Compile-time error

If we still need to access cycles in some capacity, we might add a public getCycles() method to the Pendulum class. (Creating accessor methods like this is a good design rule because it allows future flexibility in changing the type or behavior of the value.) We’ll take a detailed look at packages, access modifiers, and how they affect the visibility of variables and methods in Chapter 6.

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 bob in the previous example). 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. We can add a static variable to our Pendulum example:

    class Pendulum {
        ...
        static float gravAccel = 9.80;
        ...

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 Pendulum), the value changes for all Pendulum objects, as shown in Figure 5-2.

Static variables shared by all instances of a class
Figure 5-2. Static variables shared by all instances of a class

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

    class Pendulum {
        ...
        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. We don’t need a Pendulum object to get or set the variable gravAccel; instead, we can use the class to select the variable:

    Pendulum.gravAccel = 8.76;

This changes the value of gravAccel as seen by all instances. Why would we want to change the value of gravAccel? Well, perhaps we want to explore how pendulums would work on different planets. Static variables are also very useful for other kinds 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 pendulums under the influence of the Earth’s gravitational pull, we might change Pendulum as follows:

    class Pendulum {
        ...
        static final float EARTH_G = 9.80;
        ...

We have followed a common convention here and named our constant with capital letters. The value of EARTH_G is a constant; it can be accessed through the class Pendulum 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. That’s because 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 useful as flags and identifiers, which can be accessed from anywhere. They 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 kinds of Pendulum objects:

    class Pendulum {
        ...
        static int SIMPLE = 0, ONE_SPRING = 1, TWO_SPRING = 2;
        ...

We might then use these flags in a method that sets the type of a Pendulum or in a special constructor, as we’ll discuss shortly:

    Pendulum pendy = new Pendulum();
    pendy.setType( Pendulum.ONE_SPRING );

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

    class Pendulum {
        ...
        void resetEverything() {
            setType ( SIMPLE );
            ...
        }
        ...
    }

Constants versus enumerations

In the previous section, we saw two uses for static final variables (constants). The first was to create true constants; in that case, it was the numeric constant EARTH_G, but it could easily have been a String or Date value. The second usage was to create a fixed set of identifiers, SIMPLE, ONE_SPRING, etc., whose actual values were not as important as their uniqueness and, perhaps, their particular order.

Enumerations were added to the Java language to replace this identifier usage with a mechanism that is both safer and, in some cases, more efficient. We could have declared our pendulum types as an enumeration like so:

    public enum PendulumTypes { Simple, OneSpring, TwoSpring }

This enumeration creates not only the values, but also a new type, PendulumTypes, whose value is limited to one of the three discrete identifiers. Calling code can refer to the values as it did through our class: PendulumTypes.Simple. We’ve changed our case convention here to diverge from the convention for integer constants, but you can stick with uppercase if you prefer.

Later, when we talk about importing classes and packages, we’ll discuss the static import feature of Java, which allows us to import static identifiers and enumerations (which, as we’ve seen, are related) into a class so that we can use them by their simple names. For example:

    new Pendulum(OneSpring );

We’ll go into detail about enumerations later in this chapter after we’ve covered objects in more depth.

Methods

Methods appear inside class bodies. They 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 itself at runtime. We provide more details later in this chapter.

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 with all objects in Java, 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 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. We’ll discuss object creation and garbage collection in more detail shortly.

Shadowing

If a local variable 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. In the following example, the local variables xPos and yPos hide the instance variables of the same name:

    class Bird {
        int xPos, yPos;
        int xNest, yNest;
        ...
        double flyToNest() {
            int xPos = xNest;
            int yPos = yNest:
            return ( fly( xPos, yPos ) );
        }
        ...
    }

When we set the values of the local variables in flyToNest(), it has no effect on the values of the instance variables.

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 fly() method with shadowed variables:

    class Bird {
        int xPos, yPos;

        double fly ( int xPos, int yPos ) {
            double distance = Math.sqrt( xPos*xPos + yPos*yPos );
            flap( distance );
            this.xPos = xPos;  // instance var = local vra
            this.yPos = yPos;
            return distance;
        }
        ...
    }

In this example, the expression this.xPos refers to the instance variable xPos and assigns it the value of the local variable xPos, which would otherwise hide its name. 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; we’ll show examples of that later.

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 fly() 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 11. For now, the important thing to note is that Math is the name of a class and not an instance of a Math object. (It so happens that you can’t even make an instance of the Math class.) 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 Bird class, we could enumerate all of the available types of birds that can be created:

    class Bird {
        ...
        static String [] getBirdTypes() { ... }

    }

Here, we’ve defined a static method, getBirdTypes(), that returns an array of strings containing bird names. We can use getBirdTypes() from within an instance of Bird, just like an instance method. However, we can also call it from other classes, using the Bird class name:

    String [] names = Bird.getBirdTypes();

Perhaps a special version of the Bird class constructor accepts the name of a bird type. We could use this list to decide what kind of bird to create.

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 later, but suffice it to say that it’s common to see usage like this:

    Bird bird = Bird.createBird( "pigeon" );

Initializing Local Variables

In the flyToNest() example, we made a point of initializing the local variables xPos and yPos. Unlike instance variables, 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:

    void myMethod() {
        int foo = 42;
        int bar;

        bar += 1;  // compile-time error, bar uninitialized

        bar = 99;
        bar += 1;  // would be OK here
    }

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 foo;
      if ( someCondition ) {
        foo = 42;
        ...
      }
      foo += 1;   // Compile-time error, foo may not be initialized
    }

In this example, foo is initialized only if someCondition is true. The compiler doesn’t let you make this wager, so it flags the use of foo 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 foo a value in both the if and else branch. Or we could return from the method abruptly:

    int foo;
    ...
    if ( someCondition ) {
        foo = 42;
        ...
    } else
        return;

    foo += 1;

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

Why is Java so picky about local variables? One of the most common (and insidious) sources of errors in C or C++ is forgetting to initialize local variables, so Java tries to help out. If it didn’t, Java would suffer the same potential irregularities as C or C++.[13]

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.addToList( this );
        }
    }

    class List {
        void addToList( Element element ) {
            ...
            element.nextElement = getNextElement();
        }
    }

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 addToList() with the argument this (which is, of course, an Element). addToList() can use the this reference to modify the Element’s nextElement instance variable. 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 11, 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 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 );

This example will make more sense as you read the next section on autoboxing and unboxing of primitive values.

Autoboxing and Unboxing of Primitives

The Java compiler automatically wraps primitives in their wrapper types and unwraps them where appropriate. This process is called autoboxing and unboxing the primitive. It happens when primitives are used as arguments and return values in methods and on simple assignment to variables. For example:

    // Simple assignments
    Integer integer = 5;
    int i = new Integer(5);

    // Method arguments and return types
    Double multiply( Double a, Double b ) {
        return a.doubleValue() * b.doubleValue();
    }

    double d = multiply( 5.0, 5.0 );

In the first case, Java simply wrapped the value 5 into an Integer for us. In the second case, it unwrapped our Integer object to its primitive value. Next, we have a method that multiplies two Double wrapper objects and returns the result as a Double wrapper. This example actually has three cases of boxing and one case of unboxing. First, the two double primitive values are boxed to Double types in order to call the method. Next, the return statement of the method is actually being called on a primitive double value, which the compiler turns into a Double before it leaves the method. Finally, the compiler unboxes the return value on assignment to the primitive double variable d.

Performance implications of boxing

Gauging performance is tricky. For the vast majority of applications, the time it takes to perform tasks like creating a small object or calling a method is miniscule compared to other factors, such as I/O, user interaction, or the actual logic of the application. As a general rule, it’s not wise to worry too much about these detailed performance issues until the application is mature (no premature optimization). However, we can anticipate that allowing Java to box and unbox primitives in performance-critical areas will not be as fast as using primitives directly. One aspect of this to consider is how many new objects are being created and reclaimed by the garbage collector. While in general Java may be forced to create a new object for each boxed primitive, there are optimizations for a small range of values. Java guarantees that the Boolean values true and false, as well as “small” valued numeric types ranging from 0 to 127 for bytes and chars and from –128 to 127 for shorts and integers, are interned. Saying that they are interned means that instead of creating a new object each time, Java reuses the same object on subsequent boxings. This is safe because primitive wrappers are immutable and cannot be changed.

    Integer i = 4;
    Integer j = 4;
    System.out.println( i == j ); // This object equality is true only for small
                                  // values.

The effect of this, as shown in this code snippet, is that for small identical values the boxed primitives are actually the same object. Java also attempts to intern string values in Java classes. We’ll talk about that in Chapter 10.

Variable-Length Argument Lists

As we mentioned earlier, Java methods may have variable-length argument lists or “varargs” that allow them to take any number of arguments when invoked. The most common example usage of varargs is for the printf() style printing method, which allows any number of tags to be embedded in a string and takes an argument for each tag to be printed. For example:

    System.out.printf("My name is %s and my age is %s\n", "Bob", 21 );
    System.out.printf("Get the %s out of %s before I %s\n", item, place, 
        action );

Varargs allow the printf() method to accept any number of items to print (from zero to dozens, as awkward as that would be).

A method accepting a variable argument list is equivalent to a method accepting an array of some type of object. The difference is that the compiler makes the method call accept individual, comma-separated values, and then packs them into the array for us. The syntax for declaring the varargs method uses ellipses (...) where the square brackets of an array might go. For example:

    void printObjects( Object ... list ) {
        // list is an Object []
        for( Object o : list )
            System.out.println( o );
    }

Inside the printObjects() method, the variable list is actually an Object [] type. We could find out how many arguments were passed to us by asking the array for its length in the usual way:

    System.out.println( "Number of arguments:" + list.length );

If the caller passed no arguments, the array will be empty.

In the case of our printObjects() method, we could pass a mix of primitive values as well as object types because the compiler would automatically box the primitives to their wrapper types for us before placing them into the Object [].

The variable argument list does not have to be of type Object. It can be of any type, including primitive types. For example:

    printInts( int ... list ) {
        // list is an int []
    }
    // usage
    printInts( 1, 2, 3, 4 );

    printStrings( String ... list ) {
        // list is a String []
    }
    // usage
    printStrings( "foo", "bar", "gee" );

The printInts() method receives an int [] array of primitive int values. The printStrings() method receives a String [] as its argument. The actual arguments must all be assignable (possibly after numeric promotion or boxing) to the type of the variable argument list. In other words, the printInts() method can only be called with numbers assignable to int, and the printStrings() method can only be called with Strings.

Varargs methods may also have any number of fixed arguments before the varargs declaration. This is how the printf() method guarantees that its first argument is the format string:

    void printf( String format, Object ... args ) { ... }

Of course, a method can have only one varargs declaration, and it must come last in the method signature.

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 Chapter 6, we’ll look at method overriding, which occurs when we declare methods with identical signatures in different classes.)

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, we try to print a subclass of String called MyString. (The String class is final so it can’t really be subclassed, but let’s use our imaginations.) MyString is assignable to either 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 MyString 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 to a subclass of String than the Object method 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 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 an object 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; we’ll discuss inheritance in Chapter 6).

    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, 2006");

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" string object. Setting the variable christmas to any other value would have the same effect. Unless the original string 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.

Static and Nonstatic Initializer Blocks

It’s possible to declare a block of code (some statements within curly braces) directly within the scope of a class. This code block doesn’t belong to any method; instead, it’s executed once, at the time the object is constructed, or, in the case of a code block marked static, at the time the class is loaded. These blocks can be used to do additional setup for the class or an object instance and are called initializer blocks.

Instance initializer blocks can be thought of as extensions of instance variable initialization. They’re called at the time the instance variable’s initializers are evaluated (after superclass construction, but before your constructor body), in the order in which they appear in the Java source:

    class MyClass {
        Properties myProps = new Properties();
        // set up myProps
        {
            myProps.put("foo", "bar");
            myProps.put("boo", "gee");
        }
        int a = 5;
    ...

Normally, this kind of setup could be done just as well in the object’s constructor. A notable exception is in the case of an anonymous inner class (see Chapter 6).

Similarly, you can use static initializer blocks to set up static class members. This more useful case allows the static members of a class to have complex initialization just like objects do with constructors:

    class ColorWheel {
        static Hashtable colors = new Hashtable();

        // set up colors
        static {
            colors.put("Red", Color.red );
            colors.put("Green", Color.green );
            colors.put("Blue", Color.blue );
            ...
        }
        ...
    }

The class ColorWheel provides a variable, colors, that maps the names of colors to Color objects in a Hashtable. The first time the class ColorWheel is referenced and loaded, the static components of ColorWheel are evaluated in the order they appear in the source. In this case, the static code block simply adds elements to the colors table.

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.[14]

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 traditional compiled languages.

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.

Finalization

Before an object is removed by garbage collection, its finalize() method is invoked to give it a last opportunity to clean up its act and free other kinds of resources it may be holding. While the garbage collector can reclaim memory resources, it may not take care of things such as closing files and terminating network connections as gracefully or efficiently as could your code. That’s what the finalize() method is for. An object’s finalize() method is called once and only once before the object is garbage-collected. However, there’s no guarantee when that will happen. Garbage collection may, in theory, never run on a system that is not short of memory. It is also interesting to note that finalization and collection occur in two distinct phases of the garbage-collection process. First, items are finalized; then they are collected. It is, therefore, possible that finalization can (intentionally or unintentionally) create a lingering reference to the object in question, postponing its garbage collection. The object is, of course, subject to collection later if the reference goes away, but its finalize() method isn’t called again.

The finalize() methods of superclasses are not invoked automatically for you. If you need to invoke the finalization routine of your parent classes, you should invoke the finalize() method of your superclass, using super.finalize(). We discuss inheritance and overridden methods in Chapter 6.

Weak and Soft References

In general, as we’ve described, Java’s garbage collector reclaims objects when they are unreachable. An unreachable object, again, is one that is no longer referenced by any variables within your application and that is not reachable through any chain of references by any running thread. Such an object cannot be used by the application any longer and is, therefore, a clear case where the object should be removed.

In some situations, however, it is advantageous to have Java’s garbage collector work with your application to decide when it is time to remove a particular object. For these cases, Java allows you to hold an object reference indirectly through a special wrapper object, a type of java.lang.ref.Reference. If Java then decides to remove the object, the reference the wrapper holds turns to null automatically. While the reference exists, you may continue to use it in the ordinary way and, if you wish, assign it elsewhere (using normal references), preventing its garbage collection.

There are two types of Reference wrappers that implement different schemes for deciding when to let their target references be garbage-collected. The first is called a WeakReference. Weak references are eligible for garbage collection immediately; they do not prevent garbage collection the way that ordinary “strong” references do. This means that if you have a combination of strong references and references contained in WeakReference wrappers in your application, the garbage collector waits until only WeakReferences remain and then collects the object. This is an essential feature that allows garbage collection to work with certain kinds of caching schemes. You’ll often want to cache an object reference for performance (to avoid creating it or looking it up). But unless you take specific action to remove unneeded objects from your cache, the cache keeps those objects alive forever by maintaining live references to them. By using weak references, you can implement a cache that automatically throws away references when the object would normally be garbage-collected. In fact, an implementation of HashMap called WeakHashMap is provided that does just this (see Chapter 11 for details).

The second type of reference wrapper is called SoftReference. A soft reference is similar to a weak reference, but it tells the garbage collector to be less aggressive about reclaiming its contents. Soft-referenced objects are collected only when and if Java runs short of memory. This is useful for a slightly different kind of caching where you want to keep some content around unless there is a need to get rid of it. For example, a web browser can use soft references to cache images or HTML strings internally, thus keeping them around as long as possible until memory constraints come into play. (A more sophisticated application might also use its own scheme based on a “least recently used” marking of some kind.)

The java.lang.ref package contains the WeakReference and SoftReference wrappers, as well as a facility called ReferenceQueue that allows your application to receive a list of references that have been collected. It’s important that your application use the queue or some other checking mechanism to remove the Reference objects themselves after their contents have been collected; otherwise, your cache will soon fill up with empty Reference object wrappers.

Enumerations

Now that we’ve covered the basics of classes, we can talk a bit more in depth about enumerations. As we’ve discussed, an enumeration is an object type in the Java language that is limited to an explicit set of values. The values have an order that is defined by their order of declaration in the code, and have a correspondence with a string name that is the same as their declared name in the source code.

We’ve already seen a couple of examples of enumerations used in place of static identifiers. For example:

    enum Weekday { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday,
        Saturday }

    // usage
    setDay( Weekday.Sunday );

Let’s take a look at what the Java compiler is actually generating for the enum. It is a regular compiled Java class, in this case named Weekday, so we can display it with the javap command like so:

    % javap Weekday

    public final class Weekday extends java.lang.Enum {
        public static final Weekday Sunday;
        public static final Weekday Monday;
        public static final Weekday Tuesday;
        public static final Weekday Wednesday;
        public static final Weekday Thursday;
        public static final Weekday Friday;
        public static final Weekday Saturday;

        public static final Weekday[] values();
        public static Weekday valueOf(java.lang.String);
    }

Weekday is a subclass of the Enum type with seven static, final, “constant” object references corresponding to our seven enumerated values. Each of the enumerated values is of type Weekday. The Java compiler does not let us extend this class or create any other instances of this type. The only instances of Weekday that will ever exist are the seven enumerated values. This is what gives enumerations their type safety. A method expecting a Weekday can be given one of only seven values. Unlike a numeric constant identifier, no value other than a Weekday will work. As we saw in Chapter 4, enumerations (unlike most objects) can also be used in switch statements with all the same benefits.

Because enumerations are static values, they can be imported with the Java static import, saving us some typing:

    import static mypackage.Weekday.*;
    ...
    setDay( Friday );
    setDeadline( Sunday );

We should also mention that enumerations can be declared not only at the “top level” alongside classes, but within classes or interfaces as well. In this case, they act just like inner classes (see Chapter 6).

Enum Values

You can get the ordered list of enum values for a type with the static values() method.

    Weekday [] weekdays = Weekday.values();

The compareTo() method of an enum compares an enum value to another value of the same enum type and returns an integer less than zero, zero, or greater than zero, indicating whether the target enum is “less than,” “equal to,” or “greater than” the order of the reference enum. This doesn’t mean much for our Weekdays, but it might be useful for values that have a more numeric meaning or a (noncyclic) scale of some kind. For example:

    Level level = Level.LOW;
    Level anotherLevel = Level.HIGH;
    if ( level.compareTo( anotherLevel ) > 0 ) // true
        doSomething();

We mentioned that enum values have a string correspondence for their names. You can get the string name of the value (which is exactly the same as it is declared in the source code) with the name() method. Going the other direction, you can “look up” any enum value by its class type and string name using the static Enum.valueOf() method:

    String mondayString = Weekday.Monday.name(); // "Monday"
    Weekday mondayWeekday = Enum.valueOf( Weekday.class, "Monday" );

The name() value is also used by the toString() method of the value, so printing an enum value does what you’d expect.

Customizing Enumerations

We said that the java.lang.Enum type cannot be directly extended and that you can’t create new instances of enum types. However, you can add things to the generated enumeration class when it’s declared. For example, the enumeration java.util.concurrent.TimeUnit, which has identifiers for time units such as SECONDS, MILLISECONDS, and MICROSECONDS, has a sleep() method that interprets its argument in the correct time scale:

    import static java.util.concurrent.TimeUnit.*;

    SECONDS.sleep( 5 ); // sleep 5 seconds

Enumerations can have values with constructors, methods, and fields just like other classes. For the most part, this is straightforward; you just add a semicolon after the enum values and then add your additional class members. Let’s add a “fun” value and accessor method to our weekdays:

    public enum Weekday
    {
        Sunday(8), Monday(0), Tuesday(1), Wednesday(2), Thursday(4),
            Friday(6), Saturday(10) ;

        int fun;

        Weekday( int fun ) { this.fun = fun; }

        public int getFun() { return fun; }
    }

Here, we’ve added an instance variable, fun, to the Weekday class, as well as a constructor and accessor method that work with the value. The declaration of our enum values each now accepts the constructor value, much like a constructor call without the new keyword. Note that the semicolon at the end of the values is mandatory. Each Weekday now has a fun attribute.

There is an odd special feature of enums that we didn’t show. In addition to adding features to the enum class as a whole (as in our example), we can add methods and variables to individual values of the enumeration by giving them a body with curly braces ({}). This is best served by an example:

    enum Cat {
        Himilayan, Siamese, Caleco,
        Persian {
            public void someMethod() { ... }
        }
    }

Now, only the Cat.Persian enum value has the method. In this case, the compiler generates a subclass of Cat as an inner class of the Persian type to hold the extra member. (We’ll talk about inner classes in Chapter 6.) You could use this to have the Persian member override a method in the base enum class.



[12] 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.

[13] As with malloc’ed storage in C or C++, Java objects and their instance variables are allocated on a heap, which allows them default values once, when they are created. Local variables, however, are allocated on the Java virtual machine stack. As with the stack in C and C++, failing to initialize these could mean successive method calls could receive garbage values, and program execution might be inconsistent or implementation-dependent.

[14] 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.