Chapter    3

Discovering Classes and Objects

In Chapter 2 I introduced you to the fundamentals of the Java language. You now know how to write simple applications by inserting statements into a class’s main() method. However, when you try to develop complex applications in this manner, you’re bound to find development tedious, slow, and prone to error. Classes and objects address these problems by simplifying application architecture.

In Chapter 3 I introduce you to Java’s support for classes and objects. You learn how to declare a class and instantiate objects from the class, how to declare fields within the class and access these fields, how to declare methods within the class and call them, how to initialize classes and objects, and how to get rid of objects when they are no longer needed.

While discussing variables in Chapter 2, I introduced you to arrays. You learned about array variables and discovered a simple way to create an array. However, Java also provides a more powerful and more flexible way to create arrays, which is somewhat similar to the manner in which objects are created. This chapter also extends Chapter 2’s array coverage by introducing you to this capability.

Declaring Classes and Instantiating Objects

Before the modern approach to programming that involves classes and objects, applications adhered to structured programming in which data structures were created to organize and store data items, and functions (named code sequences that return values) and procedures (named code sequences that don’t return values) were used to manipulate data structure content. This separation of data from code made it difficult to model real-world entities (such as a bank accounts and employees) and often led to maintenance headaches for complex applications.

Computer scientists such as Bjarne Stroustrup (the creator of the C++ programming language) found that this complexity could be simplified by merging data structures with functions and procedures into discrete units known as classes. These classes could describe real-world entities and be instantiated. The resulting objects proved to be an effective way to model these entities.

You first learn how to declare a class and then learn how to create objects from this class with the help of the new operator and a constructor. Last, you learn about constructor parameters and how to specify them for initializing objects and about local variables and how to specify them for helping to control the flow of code within a constructor.

Declaring Classes

A class is a template for manufacturing objects (named groupings of code and data), which are also known as class instances, or instances for short. Classes generalize real-world entities, and objects are specific manifestations of these entities at the application level. You might think of classes as cookie cutters and objects as the cookies that cookie cutters create.

Because you cannot instantiate objects from a class that doesn’t exist, you must first declare the class. The declaration consists of a header followed by a body. At minimum, the header consists of reserved word class followed by a name that identifies the class (so that it can be referred to from elsewhere in the source code). The body starts with an open brace character ({) and ends with a close brace (}). Sandwiched between these delimiters are various kinds of declarations. Consider Listing 3-1.

Listing 3-1.  Declaring a Skeletal Image Class

class Image
{
   // various member declarations
}

Listing 3-1 declares a class named Image, which presumably describes some kind of image for displaying on the screen. By convention, a class’s name begins with an uppercase letter. Furthermore, the first letter of each subsequent word in a multiword class name is capitalized. This is known as camelcasing.

Instantiating Objects with the New Operator and a Constructor

Image is an example of a user-defined type from which objects can be created. You create these objects by using the new operator with a constructor, as follows:

Image image = new Image();

The new operator allocates memory to store the object whose type is specified by new’s solitary operand, which happens to be Image() in this example. The object is stored in a region of memory known as the heap.

The parentheses (round brackets) that follow Image signify a constructor, which is a block of code for constructing an object by initializing it in some manner. The new operator invokes (calls) the constructor immediately after allocating memory to store the object.

When the constructor ends, new returns a reference (a memory address or other identifier) to the object so that it can be accessed elsewhere in the application. Regarding the newly created Image object, its reference is stored in a variable named image whose type is specified as Image. (It’s common to refer to the variable as an object, as in the image object, although it stores only an object’s reference and not the object itself.)

Note   new’s returned reference is represented in source code by keyword this. Wherever this appears, it represents the current object. Also, variables that store references are called reference variables.

Image doesn’t explicitly declare a constructor. When a class doesn’t declare a constructor, Java implicitly creates a constructor for that class. The created constructor is known as the default noargument constructor because no arguments (discussed shortly) appear between its (and) characters when the constructor is invoked.

Note   Java doesn’t create a default noargument constructor when at least one constructor is declared.

Specifying Constructor Parameters and Local Variables

You explicitly declare a constructor within a class’s body by specifying the name of the class followed by a parameter list, which is a round bracket-delimited and comma-separated list of zero or more parameter declarations. A parameter is a constructor or method variable that receives an expression value passed to the constructor or method when it is called. This expression value is known as an argument.

Listing 3-2 enhances Listing 3-1’s Image class by declaring three constructors with parameter lists that declare zero, one, or two parameters and a main() method for testing this class.

Listing 3-2.  Declaring an Image Class with Three Constructors and a main() Method

public class Image
{
   Image()
   {
      System.out.println("Image() called");
   }

   Image(String filename)
   {
      this(filename, null);
      System.out.println("Image(String filename) called");
   }

   Image(String filename, String imageType)
   {
      System.out.println("Image(String filename, String imageType) called");
      if (filename != null)
      {
         System.out.println("reading " + filename);
         if (imageType != null)
            System.out.println("interpreting " + filename + " as storing a " +
                               imageType + " image");
      }
      // Perform other initialization here.
   }

   public static void main(String[] args)
   {
      Image image = new Image();
      System.out.println();
      image = new Image("image.png");
      System.out.println();
      image = new Image("image.png", "PNG");
   }
}

Listing 3-2’s Image class first declares a noargument constructor for initializing an Image object to default values (whatever they may be). This constructor simulates default initialization. It does so by invoking System.out.println() to output a message signifying that it’s been called.

Image next declares an Image(String filename) constructor whose parameter list consists of a single parameter declaration—a variable’s type followed by the variable’s name. The java.lang.String parameter is named filename, signifying that this constructor obtains image content from a file.

Note   Throughout this chapter and remaining chapters, I typically prefix the first use of a predefined type (such as String) with the package hierarchy in which the type is stored. For example, String is stored in the lang subpackage of the java package. I do so to help you learn where types are stored so that you can more easily specify import statements for importing these types (without having to first search for a type’s package) into your source code—you don’t have to import types that are stored in the java.lang package, but I still prefix the java.lang package to the type name for completeness. I will have more to say about packages and the import statement in Chapter 5.

Some constructors rely on other constructors to help them initialize their objects. This is done to avoid redundant code, which increases the size of an object and unnecessarily takes memory away from the heap that could be used for other purposes. For example, Image(String filename) relies on Image(String filename, String imageType) to read the file’s image content into memory.

Although it appears otherwise, constructors don’t have names (however, it is common to refer to a constructor by specifying the class name and parameter list). A constructor calls another constructor by using keyword this and a round bracket-delimited and comma-separated list of arguments. For example, Image(String filename) executes this(filename, null); to execute Image(String filename, String imageType).

Caution   You must use keyword this to call another constructor—you cannot use the class’s name, as in Image(). The this() constructor call (when present) must be the first code that is executed within the constructor—this rule prevents you from specifying multiple this() constructor calls in the same constructor. Finally, you cannot specify this() in a method—constructors can be called only by other constructors and during object creation. (I discuss methods later in this chapter.)

When present, the constructor call must be the first code that is specified within a constructor; otherwise, the compiler reports an error. For this reason, a constructor that calls another constructor can perform additional work only after the other constructor has finished. For example, Image(String filename) executes System.out.println("Image(String filename) called"); after the invoked Image(String filename, String imageType) constructor finishes.

The Image(String filename, String imageType) constructor declares an imageType parameter that signifies the kind of image stored in the file—a Portable Network Graphics (PNG) image, for example. Presumably, the constructor uses imageType to speed up processing by not examining the file’s contents to learn the image format. When null is passed to imageType, as happens with the Image(String filename) constructor, Image(String filename, String imageType) examines file contents to learn the format. If null was also passed to filename, Image(String filename, String imageType) wouldn’t read the file but would presumably notify the code attempting to create the Image object of an error condition.

After declaring the constructors, Listing 3-2 declares a main() method that lets you create Image objects and view output messages. main() creates three Image objects, calling the first constructor with no arguments, the second constructor with argument "image.png", and the third constructor with arguments "image.png" and "PNG".

Note   The number of arguments passed to a constructor or method, or the number of operator operands, is known as the constructor’s, method’s, or operator’s arity.

Each object’s reference is assigned to a reference variable named image, replacing the previously stored reference for the second and third object assignments. (Each occurrence of System.out.println(); outputs a blank line to make the output easier to read.)

The presence of main() changes Image from only a class to an application. You typically place main() in classes that are used to create objects to test such classes. When constructing an application for use by others, you usually declare main() in a class where the intent is to run an application and not to create an object from that class—the application is then run from only that class. See Chapter 1’s DumpArgs and EchoText classes for examples.

After saving Listing 3-2 to Image.java, compile this file by executing javac Image.java at the command line. Assuming that there are no error messages, execute the application by specifying java Image. You should observe the following output:

Image() called

Image(String filename, String imageType) called
reading image.png
Image(String filename) called

Image(String filename, String imageType) called
reading image.png
interpreting image.png as storing a PNG image

The first output line indicates that the noargument constructor has been called. Subsequent output lines indicate that the second and third constructors have been called.

As well as declaring parameters, a constructor can declare variables within its body to help it perform various tasks. For example, the previously presented Image(String filename, String imageType) constructor might create an object from a (hypothetical) File class that provides the means to read a file’s contents. At some point, the constructor instantiates this class and assigns the instance’s reference to a variable, as demonstrated by the following code:

Image(String filename, String imageType)
{
   System.out.println("Image(String filename, String imageType) called");
   if (filename != null)
   {
      System.out.println("reading " + filename);
      File file = new File(filename);
      // Read file contents into object.
      if (imageType != null)
         System.out.println("interpreting " + filename + " as storing a " +
                            imageType + " image");
      else
         // Inspect image contents to learn image type.
         ; // Empty statement is used to make if-else syntactically valid.
   }
   // Perform other initialization here.
}

As with the filename and imageType parameters, file is a variable that is local to the constructor and is known as a local variable to distinguish it from a parameter. Although all three variables are local to the constructor, there are two key differences between parameters and local variables:

  • The filename and imageType parameters come into existence at the point where the constructor begins to execute and exist until execution leaves the constructor. In contrast, file comes into existence at its point of declaration and continues to exist until the block in which it is declared is terminated (via a closing brace character). This property of a parameter or a local variable is known as lifetime.
  • The filename and imageType parameters can be accessed from anywhere in the constructor. In contrast, file can be accessed only from its point of declaration to the end of the block in which it is declared. It cannot be accessed before its declaration or after its declaring block, but nested subblocks can access the local variable. This property of a parameter or a local variable is known as scope.

Note   The lifetime and scope (also known as visibility) properties also apply to classes, objects, and fields (discussed later). Classes come into existence when loaded into memory and cease to exist when unloaded from memory, typically when an application exits. Also, loaded classes are typically visible to other classes.

An object’s lifetime ranges from its creation via the new operator until the moment when it is removed from memory by the garbage collector (discussed later in this chapter). Its scope depends on various factors, such as when its reference is assigned to a local variable or to a field. I discuss fields later in this chapter.

The lifetime of a field depends on whether it is an instance field or a class field. When the field belongs to an object (an instance field), it comes into existence when the object is created and dies when the object disappears from memory. When the field belongs to a class (a class field), the field begins its existence when the class is loaded and disappears when the class is removed from memory. As with an object, a field’s scope depends on various factors, such as whether the field is declared to have private access or not—you’ll learn about private access later in this chapter.

A local variable cannot have the same name as a parameter because a parameter always has the same scope as the local variable. However, a local variable can have the same name as another local variable provided that both variables are located within different scopes (that is, within different blocks). For example, you could specify int x = 1; within an if-else statement’s if block and specify double x = 2.0; within the statement’s corresponding else block, and each local variable would be distinct.

Note   The discussion of constructor parameters, arguments, and local variables also applies to method parameters, arguments, and local variables—I discuss methods later in this chapter.

Encapsulating State and Behaviors

Classes model real-world entities from a template perspective, for example, car and savings account. Objects represent specific entities, for example, John’s red Toyota Camry (a car instance) and Cuifen’s savings account with a balance of twenty thousand dollars (a savings account instance).

Entities have attributes, such as color red, make Toyota, model Camry, and balance twenty thousand dollars. An entity’s collection of attributes is referred to as its state. Entities also have behaviors, such as open car door, drive car, display fuel consumption, deposit, withdraw, and show account balance.

A class and its objects model an entity by combining state with behaviors into a single unit—the class abstracts state, whereas its objects provide concrete state values. This bringing together of state and behaviors is known as encapsulation. Unlike in structured programming, where the developer focuses on separately modeling behaviors through structured code and modeling state through data structures that store data items for the structured code to manipulate, the developer working with classes and objects focuses on templating entities by declaring classes that encapsulate state and behaviors, instantiating objects with specific state values from these classes to represent specific entities and interacting with objects through their behaviors.

In this section, I first introduce you to Java’s language features for representing state, and then I introduce you to its language features for representing behaviors. Because some state and behaviors support the class’s internal architecture and should not be visible to those wanting to use the class, I conclude this section by presenting the important concept of information hiding.

Representing State via Fields

Java lets you represent state via fields, which are variables declared within a class’s body. Entity attributes are described via instance fields. Because Java also supports state that’s associated with a class and not with an object, Java provides class fields to describe this class state.

You first learn how to declare and access instance fields and then learn how to declare and access class fields. After discovering how to declare read-only instance and class fields, you review the rules for accessing fields from different contexts.

Declaring and Accessing Instance Fields

You can declare an instance field by minimally specifying a type name, followed by an identifier that names the field, followed by a semicolon character (;). Listing 3-3 presents a Car class with three instance field declarations.

Listing 3-3.  Declaring a Car Class with make, model, and numDoors Instance Fields

class Car
{
   String make;
   String model;
   int numDoors;
}

Listing 3-3 declares two String instance fields named make and model. It also declares an int instance field named numDoors. By convention, a field’s name begins with a lowercase letter, and the first letter of each subsequent word in a multiword field name is capitalized.

When an object is created, instance fields are initialized to default zero values, which you interpret at the source code level as literal value false, 'u0000', 0, 0L, 0.0, 0.0F, or null (depending on element type). For example, if you were to execute Car car = new Car();, make and model would be initialized to null and numDoors would be initialized to 0.

You can assign values to or read values from an object’s instance fields by using the member access operator (.); the left operand specifies the object’s reference and the right operand specifies the instance field to be accessed. Listing 3-4 uses this operator to initialize a Car object’s make, model, and numDoors instance fields.

Listing 3-4.  Initializing a Car Object’s Instance Fields

public class Car
{
   String make;
   String model;
   int numDoors;

   public static void main(String[] args)
   {
      Car car = new Car();
      car.make = "Toyota";
      car.model = "Camry";
      car.numDoors = 4;
   }
}

Listing 3-4 presents a main() method that instantiates Car. The car instance’s make instance field is assigned the "Toyota" string, its model instance field is assigned the "Camry" string, and its numDoors instance field is assigned integer literal 4. (A string’s double quotes delimit a string’s sequence of characters but are not part of the string.)

You can explicitly initialize an instance field when declaring that field to provide a nonzero default value, which overrides the default zero value. Listing 3-5 demonstrates this point.

Listing 3-5.  Initializing Car’s numDoors Instance Field to a Default Nonzero Value

public class Car
{
   String make;
   String model;
   int numDoors = 4;

   Car()
   {
   }

   public static void main(String[] args)
   {
      Car johnDoeCar = new Car();
      johnDoeCar.make = "Chevrolet";
      johnDoeCar.model = "Volt";
   }
}

Listing 3-5 explicitly initializes numDoors to 4 because the developer has assumed that most cars being modeled by this class have four doors. When Car is initialized via the Car() constructor, the developer only needs to initialize the make and model instance fields for those cars that have four doors.

It is usually not a good idea to directly initialize an object’s instance fields, and you will learn why when I discuss information hiding (later in this chapter). Instead, you should perform this initialization in the class’s constructor(s)—see Listing 3-6.

Listing 3-6.  Initializing Car’s Instance Fields via Constructors

public class Car
{
   String make;
   String model;
   int numDoors;

   Car(String make, String model)
   {
      this(make, model, 4);
   }

   Car(String make, String model, int nDoors)
   {
      this.make = make;
      this.model = model;
      numDoors = nDoors;
   }

   public static void main(String[] args)
   {
      Car myCar = new Car("Toyota", "Camry");
      Car yourCar = new Car("Mazda", "RX-8", 2);
   }
}

Listing 3-6’s Car class declares Car(String make, String model) and Car(String make, String model, int nDoors) constructors. The first constructor lets you specify the make and model, whereas the second constructor lets you specify values for the three instance fields.

The first constructor executes this(make, model, 4); to pass the values of its make and model parameters along with a default value of 4 to the second constructor. Doing so demonstrates an alternative to explicitly initializing an instance field and is preferable from a code maintenance perspective.

The Car(String make, String model, int numDoors) constructor demonstrates another use for keyword this. Specifically, it demonstrates a scenario where constructor parameters have the same names as the class’s instance fields. Prefixing a variable name with “this.” causes the Java compiler to create bytecode that accesses the instance field. For example, this.make = make; assigns the make parameter’s String object reference to this (the current) Car object’s make instance field. If make = make; was specified instead, it would accomplish nothing by assigning make’s value to itself; a Java compiler might not generate code to perform the unnecessary assignment. In contrast, “this.” isn’t necessary for the numDoors = nDoors; assignment, which initializes the numDoors field from the nDoors parameter value.

Note   To minimize error (by forgetting to prefix a field name with “this.”), it’s preferable to keep field names and parameter names distinct (e.g., numDoors and nDoors). Alternatively, you might prefix a field name with an underscore (e.g., _nDoors). Either way, you wouldn’t have to worry about the “this.” prefix (and forgetting to specify it).

Declaring and Accessing Class Fields

In many situations, instance fields are all that you need. However, you might encounter a situation where you need a single copy of a field no matter how many objects are created.

For example, suppose you want to track the number of Car objects that have been created and introduce a counter instance field (initialized to 0) into this class. You also place code in the class’s constructor that increases counter’s value by 1 when an object is created. However, because each object has its own copy of the counter instance field, this field’s value never advances past 1. Listing 3-7 solves this problem by declaring counter to be a class field by prefixing the field declaration with the static keyword.

Listing 3-7.  Adding a counter Class Field to Car

public class Car
{
   String make;
   String model;
   int numDoors;
   static int counter;

   Car(String make, String model)
   {
      this(make, model, 4);
   }

   Car(String make, String model, int numDoors)
   {
      this.make = make;
      this.model = model;
      this.numDoors = numDoors;
      counter++; // This code is unsafe because counter can be accessed directly.
   }

   public static void main(String[] args)
   {
      Car myCar = new Car("Toyota", "Camry");
      Car yourCar = new Car("Mazda", "RX-8", 2);
      System.out.println(Car.counter);
   }
}

Listing 3-7’s static prefix implies that there is only one copy of the counter field and not one copy per object. When a class is loaded into memory, class fields are initialized to default zero values. For example, counter is initialized to 0. (As with an instance field, you can alternatively assign a value to a class field in its declaration.) Each time an object is created, counter will increase by 1 thanks to the counter++ expression in the Car(String make, String model, int numDoors) constructor.

Unlike instance fields, class fields are normally accessed directly via the member access operator. Although you could access a class field via an object reference (as in myCar.counter), it is conventional to access a class field by using the class’s name, as in Car.counter. (It is also easier to tell that the code is accessing a class field.)

Note   Because the main() method is a member of Listing 3-7’s Car class, you could access counter directly, as in System.out.println(counter);. To access counter in the context of another class’s main() method, however, you would have to specify Car.counter.

If you run Listing 3-7, you’ll notice that it outputs 2, because two Car objects have been created.

Declaring Read-Only Instance and Class Fields

The previously declared fields can be written to as well as read from. However, you might want to declare a field that is read-only, for example, a field that names a constant value such as pi (3.14159…). Java lets you accomplish this task by providing reserved word final.

Each object receives its own copy of a read-only instance field. This field must be initialized as part of the field’s declaration or in the class’s constructor. When initialized in the constructor, the read-only instance field is known as a blank final because it doesn’t have a value until one is assigned to it in the constructor. Because a constructor can potentially assign a different value to each object’s blank final, these read-only variables are not truly constants.

If you want a true constant, which is a single read-only value that is available to all objects, you need to create a read-only class field. You can accomplish this task by including the reserved word static with final in that field’s declaration.

Listing 3-8 shows how to declare a read-only class field.

Listing 3-8.  Declaring a True Constant in the Employee Class

class Employee
{
   final static int RETIREMENT_AGE = 65;
}

Listing 3-8’s RETIREMENT_AGE declaration is an example of a compile-time constant. Because there is only one copy of its value (thanks to the static keyword), and because this value will never change (thanks to the final keyword), the compiler is free to optimize the compiled code by inserting the constant value into all calculations where it is used. The code runs faster because it doesn’t have to access a read-only class field.

Reviewing Field-Access Rules

The previous examples of field access may seem confusing because you can sometimes specify the field’s name directly, whereas you need to prefix a field name with an object reference or a class name and the member access operator at other times. The following rules dispel this confusion by giving you guidance on how to access fields from the various contexts:

  • Specify the name of a class field as is from anywhere within the same class as the class field declaration. Example: counter
  • Specify the name of a class field’s class, followed by the member access operator, followed by the name of the class field from outside the class. Example: Car.counter
  • Specify the name of an instance field as is from any instance method, constructor, or instance initializer (discussed later) in the same class as the instance field declaration. Example: numDoors
  • Specify an object reference, followed by the member access operator, followed by the name of the instance field from any class method or class initializer (discussed later) within the same class as the instance field declaration or from outside the class. Example: Car car = new Car(); car.numDoors = 2;

Although the final rule might seem to imply that you can access an instance field from a class context, this is not the case. Instead, you are accessing the field from an object context.

The previous access rules are not exhaustive because there are two more field-access scenarios to consider: declaring a local variable (or even a parameter) with the same name as an instance field or as a class field. In either scenario, the local variable/parameter is said to shadow (hide or mask) the field.

If you find that you have declared a local variable or a parameter that shadows a field, you can rename the local variable/parameter, or you can use the member access operator with reserved word this (instance field) or class name (class field) to explicitly identify the field. For example, Listing 3-6’s Car(String make, String model, int nDoors) constructor demonstrated this latter solution by specifying statements such as this.make = make; to distinguish an instance field from a same-named parameter.

Representing Behaviors via Methods

Java lets you represent behaviors via methods, which are named blocks of code declared within a class’s body. Entity behaviors are described via instance methods. Because Java also supports behaviors that are associated with a class and not with an object, Java provides class methods to describe these class behaviors.

You first learn how to declare and invoke instance methods, and then learn how to create instance method call chains. Next, you discover how to declare and invoke class methods, encounter additional details about passing arguments to methods, and explore Java’s return statement. After learning how to invoke methods recursively as an alternative to iteration, and how to overload methods, you review the rules for invoking methods from different contexts.

Declaring and Invoking Instance Methods

You can declare an instance method by minimally specifying a return type name, followed by an identifier that names the method, followed by a parameter list, followed by a brace-delimited body. Listing 3-9 presents a Car class with a printDetails() instance method.

Listing 3-9.  Declaring a printDetails() Instance Method in the Car Class

public class Car
{
   String make;
   String model;
   int numDoors;

   Car(String make, String model)
   {
      this(make, model, 4);
   }

   Car(String make, String model, int numDoors)
   {
      this.make = make;
      this.model = model;
      this.numDoors = numDoors;
   }

   void printDetails()
   {
      System.out.println("Make = " + make);
      System.out.println("Model = " + model);
      System.out.println("Number of doors = " + numDoors);
      System.out.println();
   }

   public static void main(String[] args)
   {
      Car myCar = new Car("Toyota", "Camry");
      myCar.printDetails();
      Car yourCar = new Car("Mazda", "RX-8", 2);
      yourCar.printDetails();
   }
}

Listing 3-9 declares an instance method named printDetails(). By convention, a method’s name begins with a lowercase letter, and the first letter of each subsequent word in a multiword method name is capitalized.

Methods are like constructors in that they have parameter lists. You pass arguments to these parameters when you call the method. Because printDetails() doesn’t take arguments, its parameter list is empty.

Note   A method’s name and the number, types, and order of its parameters are known as its signature.

When a method is invoked, the code within its body is executed. In the case of printDetails(), this method’s body executes a sequence of System.out.println() method calls to output the values of its make, model, and numDoors instance fields.

Unlike constructors, methods are declared to have return types. A return type identifies the kind of values returned by the method (e.g., int count() returns 32-bit integers). When a method doesn’t return a value (and printDetails() doesn’t), its return type is replaced with keyword void, as in void printDetails().

Note   Constructors don’t have return types because they cannot return values. If a constructor could return an arbitrary value, how would Java return that value? After all, the new operator returns a reference to an object; how could new also return a constructor value?

A method is invoked by using the member access operator: the left operand specifies the object’s reference and the right operand specifies the method to be called. For example, the myCar.printDetails() and yourCar.printDetails() expressions invoke the printDetails() instance method on the myCar and yourCar objects.

Compile Listing 3-9 (javac Car.java) and run this application (java Car). You should observe the following output, whose different instance field values prove that printDetails() associates with an object:

Make = Toyota
Model = Camry
Number of doors = 4

Make = Mazda
Model = RX-8
Number of doors = 2

When an instance method is invoked, Java passes a hidden argument to the method (as the leftmost argument in a list of arguments). This argument is the reference to the object on which the method is invoked. It is represented at the source code level via reserved word this. You don’t need to prefix an instance field name with “this.” from within the method whenever you attempt to access an instance field name that isn’t also the name of a parameter because the Java compiler ensures that the hidden argument is used to access the instance field.

METHOD-CALL STACK

Method invocations require a method-call stack (also known as a method-invocation stack) to keep track of the statements to which execution must return. Think of the method-call stack as a simulation of a pile of clean trays in a cafeteria—you pop (remove) the clean tray from the top of the pile and the dishwasher will push (insert) the next clean tray onto the top of the pile.

When a method is invoked, the virtual machine pushes its arguments and the address of the first statement to execute following the invoked method onto the method-call stack. The virtual machine also allocates stack space for the method’s local variables. When the method returns, the virtual machine removes local variable space, pops the address and arguments off of the stack, and transfers execution to the statement at this address.

Chaining Together Instance Method Calls

Two or more instance method calls can be chained together via the member access operator, which results in more compact code. To accomplish instance method call chaining, you need to rearchitect your instance methods somewhat differently, which Listing 3-10 reveals.

Listing 3-10.  Implementing Instance Methods so That Calls to These Methods Can Be Chained Together

public class SavingsAccount
{
   int balance;

   SavingsAccount deposit(int amount)
   {
      balance += amount;
      return this;
   }

   SavingsAccount printBalance()
   {
      System.out.println(balance);
      return this;
   }

   public static void main(String[] args)
   {
      new SavingsAccount().deposit(1000).printBalance();
   }
}

Listing 3-10 shows that you must specify the class’s name as the instance method’s return type. Each of deposit() and printBalance() must specify SavingsAccount as the return type. Also, you must specify return this; (return current object’s reference) as the last statement—I discuss the return statement later.

For example, new SavingsAccount().deposit(1000).printBalance(); creates a SavingsAccount object, uses the returned SavingsAccount reference to invoke SavingsAccount’s deposit() instance method, to add one thousand dollars to the savings account (I’m ignoring cents for convenience), and finally uses deposit()’s returned SavingsAccount reference (which is the same SavingsAccount instance) to invoke SavingsAccount’s printBalance() instance method to output the account balance.

Declaring and Invoking Class Methods

In many situations, instance methods are all that you need. However, you might encounter a situation where you need to describe a behavior that is independent of any object.

For example, suppose you would like to introduce a utility class (a class consisting of static [class] methods) whose class methods perform various kinds of conversions (such as converting from degrees Celsius to degrees Fahrenheit). You don’t want to create an object from this class to perform a conversion. Instead, you simply want to call a method and obtain its result. Listing 3-11 addresses this requirement by presenting a Conversions class with a pair of class methods. These methods can be called without having to create a Conversions object.

Listing 3-11.  A Conversions Utility Class with a Pair of Class Methods

class Conversions
{
   static double c2f(double degrees)
   {
      return degrees * 9.0 / 5.0 + 32;
   }

   static double f2c(double degrees)
   {
      return (degrees - 32) * 5.0 / 9.0;
   }
}

Listing 3-11’s Conversions class declares c2f() and f2c() methods for converting from degrees Celsius to degrees Fahrenheit and vice-versa and returning the results of these conversions. Each method header (method signature and other information) is prefixed with keyword static to turn the method into a class method.

To execute a class method, you typically prefix its name with the class name. For example, you can execute Conversions.c2f(100.0); to find out the Fahrenheit equivalent of 100 degrees Celsius, and Conversions.f2c(98.6); to discover the Celsius equivalent of the normal body temperature. You don’t need to instantiate Conversions and then call these methods via that instance, although you could do so (but that isn’t good form).

Note   Every application has at least one class method. Specifically, an application must specify public static void main(String[] args) to serve as the application’s entry point. The static reserved word makes this method a class method. (I will explain reserved word public later in this chapter.)

Because class methods are not called with a hidden argument that refers to the current object, c2f(), f2c(), and main() cannot access an object’s instance fields or call its instance methods. These class methods can only access class fields and call class methods.

Passing Arguments to Methods

A method call includes a list of (zero or more) arguments being passed to the method. Java passes arguments to methods via a style of argument passing called pass-by-value, which the following example demonstrates:

Employee emp = new Employee("John ");
int recommendedAnnualSalaryIncrease = 1000;
printReport(emp, recommendAnnualSalaryIncrease);
printReport(new Employee("Cuifen"), 1500);

Pass-by-value passes the value of a variable (the reference value stored in emp or the 1000 value stored in recommendedAnnualSalaryIncrease, for example) or the value of some other expression (such as new Employee("Cuifen") or 1500) to the method.

Because of pass-by-value, you cannot assign a different Employee object’s reference to emp from inside printReport() via the printReport() parameter for this argument. After all, you have only passed a copy of emp’s value to the method.

Many methods and constructors require you to pass a fixed number of arguments when they are called. However, Java also provides the ability to pass a variable number of arguments—such methods/constructors are often referred to as varargs methods/constructors. To declare a method or constructor that takes a variable number of arguments, specify three consecutive periods after the type name of the method’s/constructor’s rightmost parameter. The following example presents a sum() method that accepts a variable number of arguments:

double sum(double. . . values)
{
   int total = 0;
   for (int i = 0; i < values.length; i++)
      total += values[i];
   return total;
}

sum()’s implementation totals the number of arguments passed to this method, for example, sum(10.0, 20.0) or sum(30.0, 40.0, 50.0). (Behind the scenes, these arguments are stored in a one-dimensional array, as evidenced by values.length and values[i].) After these values have been totaled, this total is returned via the return statement.

Returning from a Method via the Return Statement

The execution of statements within a method that doesn’t return a value (its return type is set to void) flows from the first statement to the last statement. However, Java’s return statement lets a method or a constructor exit before reaching the last statement. As Listing 3-12 shows, this form of the return statement consists of reserved word return followed by a semicolon.

Listing 3-12.  Using the Return Statement to Return Prematurely from a Method

public class Employee
{
   String name;

   Employee(String name)
   {
      setName(name);
   }

   void setName(String name)
   {
      if (name == null)
      {
         System.out.println("name cannot be null");
         return;
      }
      else
         this.name = name;
   }

   public static void main(String[] args)
   {
      Employee john = new Employee(null);
   }
}

Listing 3-12’s Employee(String name) constructor invokes the setName() instance method to initialize the name instance field. Providing a separate method for this purpose is a good idea because it lets you initialize the instance field at construction time and also at a later time. (Perhaps the employee changes his or her name.)

Note   When you invoke a class’s instance or class method from a constructor or method within the same class, you specify only the method’s name. You don’t prefix the method invocation with the member access operator and an object reference or class name.

setName() uses an if statement to detect an attempt to assign the null reference to the name field. When such an attempt is detected, it outputs the “name cannot be null” error message and returns prematurely from the method so that the null value cannot be assigned (and replace a previously assigned name).

Caution   When using the return statement, you might run into a situation where the compiler reports an “unreachable code” error message. It does so when it detects code that will never be executed and occupies memory unnecessarily. One area where you might encounter this problem is the switch statement. For example, suppose you specify case 2: printUsageInstructions(); return; break; as part of this statement. The compiler reports an error when it detects the break statement following the return statement because the break statement is unreachable; it never can be executed.

The previous form of the return statement is not legal in a method that returns a value. For such methods, Java provides an alternate version of return that lets the method return a value (whose type must match the method’s return type). The following example demonstrates this version:

double divide(double dividend, double divisor)
{
   if (divisor == 0.0)
   {
      System.out.println("cannot divide by zero");
      return 0.0;
   }
   return dividend / divisor;
}

divide() uses an if statement to detect an attempt to divide its first argument by 0.0 and outputs an error message when this attempt is detected. Furthermore, it returns 0.0 to signify this attempt. If there is no problem, the division is performed and the result is returned.

Caution   You cannot use this form of the return statement in a constructor because constructors don’t have return types.

Invoking Methods Recursively

A method normally executes statements that may include calls to other methods, such as printDetails() invoking System.out.println(). However, it is occasionally convenient to have a method call itself. This scenario is known as recursion.

For example, suppose you need to write a method that returns a factorial (the product of all the positive integers up to and including a specific integer). For example, 3! (the ! is the mathematical symbol for factorial) equals 3×2×1 or 6.

Your first approach to writing this method might consist of the code presented in the following example:

int factorial(int n)
{
   int product = 1;
   for (int i = 2; i <= n; i++)
      product *= i;
   return product;
}

Although this code accomplishes its task (via iteration), factorial() could also be written according to the following example’s recursive style:

int factorial(int n)
{
   if (n == 1)
      return 1; // base problem
   else
      return n * factorial(n - 1);
}

The recursive approach takes advantage of being able to express a problem in simpler terms of itself. According to this example, the simplest problem, which is also known as the base problem, is 1! (1).

When an argument greater than 1 is passed to factorial(), this method breaks the problem into a simpler problem by calling itself with the next smaller argument value. Eventually, the base problem will be reached.

For example, calling factorial(4) results in the following stack of expressions:

4 * factorial(3)
3 * factorial(2)
2 * factorial(1)

This last expression is at the top of the stack. When factorial(1) returns 1, these expressions are evaluated as the stack begins to unwind:

  • 2 * factorial(1) now becomes 2*1 (2)
  • 3 * factorial(2) now becomes 3*2 (6)
  • 4 * factorial(3) now becomes 4*6 (24)

Recursion provides an elegant way to express many problems. Additional examples include searching tree-based data structures for specific values and, in a hierarchical file system, finding and outputting the names of all files that contain specific text.

Caution   Recursion consumes stack space, so make sure that your recursion eventually ends in a base problem; otherwise, you will run out of stack space and your application will be forced to terminate.

Overloading Methods

Java lets you introduce methods with the same name but different parameter lists into the same class. This feature is known as method overloading. When the compiler encounters a method invocation expression, it compares the called method’s arguments list with each overloaded method’s parameter list as it looks for the correct method to invoke.

Two same-named methods are overloaded when their parameter lists differ in number or order of parameters. For example, Java’s String class provides overloaded int indexOf(int ch) and int indexOf(int ch, int fromIndex) methods. These methods differ in parameter counts. (I explore String in Chapter 7.)

Two same-named methods are overloaded when at least one parameter differs in type. For example, Java’s java.lang.Math class provides overloaded static double abs(double a) and static int abs(int a) methods. One method’s parameter is a double; the other method’s parameter is an int. (I explore Math in Chapter 7.)

You cannot overload a method by changing only the return type. For example, double sum(double. . . values) and int sum(double. . . values) are not overloaded. These methods are not overloaded because the compiler doesn’t have enough information to choose which method to call when it encounters sum(1.0, 2.0) in source code.

Reviewing Method-Invocation Rules

The previous examples of method invocation may seem confusing because you can sometimes specify the method’s name directly, whereas you need to prefix a method name with an object reference or a class name and the member access operator at other times. The following rules dispel this confusion by giving you guidance on how to invoke methods from the various contexts:

  • Specify the name of a class method as is from anywhere within the same class as the class method. Example: c2f(37.0);
  • Specify the name of the class method’s class, followed by the member access operator, followed by the name of the class method from outside the class. Example: Conversions.c2f(37.0); (You can also invoke a class method via an object instance, but that is considered bad form because it hides from casual observation the fact that a class method is being invoked.)
  • Specify the name of an instance method as is from any instance method, constructor, or instance initializer in the same class as the instance method. Example: setName(name);
  • Specify an object reference, followed by the member access operator, followed by the name of the instance method from any class method or class initializer within the same class as the instance method or from outside the class. Example: Car car = new Car("Toyota", "Camry"); car.printDetails();

Although the latter rule might seem to imply that you can call an instance method from a class context, this is not the case. Instead, you call the method from an object context.

Also, don’t forget to make sure that the number of arguments passed to a method, along with the order in which these arguments are passed, and the types of these arguments agree with their parameter counterparts in the method being invoked.

Note   Field access and method call rules are combined in expression System.out.println();, where the leftmost member access operator accesses the out class field (of type java.io.PrintStream) in the java.lang.System class, and where the rightmost member access operator calls this field’s println() method. You’ll learn about PrintStream in Chapter 11 and System in Chapter 8.

Hiding Information

Every class X exposes an interface (a protocol consisting of constructors, methods, and [possibly] fields that are made available to objects created from other classes for use in creating and communicating with X’s objects).

An interface serves as a one-way contract between a class and its clients, which are the external constructors, methods, and other (initialization-oriented) class entities (discussed later in this chapter) that communicate with the class’s instances by calling constructors and methods and by accessing fields (typically public static final fields, or constants). The contract is such that the class promises to not change its interface, which would break clients that depend on the interface.

X also provides an implementation (the code within exposed methods along with optional helper methods and optional supporting fields that should not be exposed) that codifies the interface. Helper methods are methods that assist exposed methods and should not be exposed.

When designing a class, your goal is to expose a useful interface while hiding details of that interface’s implementation. You hide the implementation to prevent developers from accidentally accessing parts of your class that don’t belong to the class’s interface so that you are free to change the implementation without breaking client code. Hiding the implementation is often referred to as information hiding. Furthermore, many developers consider implementation hiding to be part of encapsulation.

Java supports implementation hiding by providing four levels of access control, where three of these levels are indicated via a reserved word. You can use the following access control levels to control access to fields, methods, and constructors and two of these levels to control access to classes:

  • Public: A field, method, or constructor that is declared public is accessible from anywhere. Classes can be declared public as well.
  • Protected: A field, method, or constructor that is declared protected is accessible from all classes in the same package as the member’s class as well as subclasses of that class regardless of package. (I discuss packages in Chapter 5.)
  • Private: A field, method, or constructor that is declared private cannot be accessed from beyond the class in which it is declared.
  • Package-private: In the absence of an access-control reserved word, a field, method, or constructor is only accessible to classes within the same package as the member’s class. The same is true for non-public classes. The absence of public, protected, or private implies package-private.

Note   A class that is declared public must be stored in a file with the same name. For example, a public Image class must be stored in Image.java. A source file can declare one public top-level class only. (It’s also possible to declare nested classes that are public, and you will learn how to do so in Chapter 5.)

You will often declare your class’s instance fields to be private and provide special public instance methods for setting and getting their values. By convention, methods that set field values have names starting with set and are known as setters. Similarly, methods that get field values have names with get (or is, for Boolean fields) prefixes and are known as getters. Listing 3-13 demonstrates this pattern in the context of an Employee class declaration.

Listing 3-13.  Separation of Interface from Implementation

public class Employee
{
   private String name;

   public Employee(String name)
   {
      setName(name);
   }

   public void setName(String empName)
   {
      name = empName; // Assign the empName argument to the name field.
   }

   public String getName()
   {
      return name;
   }
}

Listing 3-13 presents an interface consisting of the public Employee class, its public constructor, and its public setter/getter methods. This class and these members can be accessed from anywhere. The implementation consists of the private name field and constructor/method code, which is only accessible within the Employee class.

It might seem pointless to go to all this bother when you could simply omit private and access the name field directly. However, suppose you are told to introduce a new constructor that takes separate first and last name arguments and new methods that set/get the employee’s first and last names into this class. Furthermore, suppose that it has been determined that the first and last names will be accessed more often than the entire name. Listing 3-14 reveals these changes.

Listing 3-14.  Revising Implementation Without Affecting Existing Interface

public class Employee
{
   private String firstName;
   private String lastName;

   public Employee(String name)
   {
      setName(name);
   }

   public Employee(String firstName, String lastName)
   {
      setName(firstName + " " + lastName);
   }

   public void setName(String name)
   {
      // Assume that the first and last names are separated by a
      // single space character. indexOf() locates a character in a
      // string; substring() returns a portion of a string.
      setFirstName(name.substring(0, name.indexOf(' ')));
      setLastName(name.substring(name.indexOf(' ') + 1));
   }

   public String getName()
   {
      return getFirstName() + " " + getLastName();
   }

   public void setFirstName(String empFirstName)
   {
      firstName = empFirstName;
   }

   public String getFirstName()
   {
      return firstName;
   }

   public void setLastName(String empLastName)
   {
      lastName = empLastName;
   }

   public String getLastName()
   {
      return lastName;
   }
}

Listing 3-14 reveals that the name field has been removed in favor of new firstName and lastName fields, which were added to improve performance. Because setFirstName() and setLastName() will be called more frequently than setName(), and because getFirstName() and getLastName() will be called more frequently than getName(), it is more performant (in each case) to have the first two methods set/get firstName’s and lastName’s values rather than merging either value into/extracting this value from name’s value.

Listing 3-14 also reveals setName() calling setFirstName() and setLastName(), and getName() calling getFirstName() and getLastName(), rather than directly accessing the firstName and lastName fields. Although avoiding direct access to these fields is not necessary in this example, imagine another implementation change that adds more code to setFirstName(), setLastName(), getFirstName(), and getLastName(); not calling these methods will result in the new code not executing.

Client code (code that instantiates and uses a class, such as Employee) will not break when Employee’s implementation changes from that shown in Listing 3-13 to that shown in Listing 3-14, because the original interface remains intact, although the interface has been extended. This lack of breakage results from hiding Listing 3-13’s implementation, especially the name field.

Note   setName() invokes the String class’s indexOf() and substring() methods. You’ll learn about these and other String methods in Chapter 7.

Java provides a little known information hiding-related language feature that lets one object (or class method/initializer) access another object’s private fields or invoke its private methods. Listing 3-15 provides a demonstration.

Listing 3-15.  One Object Accessing Another Object’s private Field

public class PrivateAccess
{
   private int x;

   PrivateAccess(int x)
   {
      this.x = x;
   }

   boolean equalTo(PrivateAccess pa)
   {
      return pa.x == x;
   }

   public static void main(String[] args)
   {
      PrivateAccess pa1 = new PrivateAccess(10);
      PrivateAccess pa2 = new PrivateAccess(20);
      PrivateAccess pa3 = new PrivateAccess(10);
      System.out.println("pa1 equal to pa2: " + pa1.equalTo(pa2));
      System.out.println("pa2 equal to pa3: " + pa2.equalTo(pa3));
      System.out.println("pa1 equal to pa3: " + pa1.equalTo(pa3));
      System.out.println(pa2.x);
   }
}

Listing 3-15’s PrivateAccess class declares a private int field named x. It also declares an equalTo() method that takes a PrivateAccess argument. The idea is to compare the argument object with the current object to determine if they are equal.

The equality determination is made by using the == operator to compare the value of the argument object’s x instance field with the value of the current object’s x instance field, returning Boolean true when they are the same. What may seem baffling is that Java lets you specify pa.x to access the argument object’s private instance field. Also, main() is able to directly access x via the pa2 object.

I previously presented Java’s four access-control levels and presented the following statement regarding the private access-control level: “A field, method, or constructor that is declared private cannot be accessed from beyond the class in which it is declared.” When you carefully consider this statement and examine Listing 3-15, you will realize that x is not being accessed from beyond the PrivateAccess class in which it is declared. Therefore, the private access-control level is not being violated.

The only code that can access this private instance field is code located within the PrivateAccess class. If you attempted to access x via a PrivateAccess object that was created in the context of another class, the compiler would report an error.

Being able to directly access x from within PrivateAccess is a performance enhancement; it is faster to directly access this implementation detail than to call a method that returns its value.

Compile PrivateAccess.java (javac PrivateAccess.java) and run the application (java PrivateAccess). You should observe the following output:

pa1 equal to pa2: false
pa2 equal to pa3: false
pa1 equal to pa3: true
20

Tip   Get into the habit of developing useful interfaces while hiding implementations because it will save you much trouble when maintaining your classes.

Initializing Classes and Objects

Classes and objects need to be properly initialized before they are used. You’ve already learned that class fields are initialized to default zero values after a class loads and can be subsequently initialized by assigning values to them in their declarations via class field initializers, for example, static int counter = 1;. Similarly, instance fields are initialized to default values when an object’s memory is allocated via new and can be subsequently initialized by assigning values to them in their declarations via instance field initializers; for example, int numDoors = 4;.

Another aspect of initialization that’s already been discussed is the constructor, which is used to initialize an object, typically by assigning values to various instance fields, but is also capable of executing arbitrary code such as code that opens a file and reads the file’s contents.

Java provides two additional initialization features: class initializers and instance initializers. After introducing you to these features in this section, I discuss the order in which all of Java’s initializers perform their work.

Class Initializers

Constructors perform initialization tasks for objects. Their counterpart from a class initialization perspective is the class initializer.

A class initializer is a static-prefixed block that is introduced into a class body. It is used to initialize a loaded class via a sequence of statements. For example, I once used a class initializer to load a custom database driver class. Listing 3-16 shows the loading details.

Listing 3-16.  Loading a Database Driver via a Class Initializer

class JDBCFilterDriver implements Driver
{
   static private Driver d;

   static
   {
      // Attempt to load JDBC-ODBC Bridge Driver and register that
      // driver.
      try
      {
         Class c = Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
         d = (Driver) c.newInstance();
         DriverManager.registerDriver(new JDBCFilterDriver());
      }
      catch (Exception e)
      {
         System.out.println(e);
      }
   }
   //. . .
}

Listing 3-16’s JDBCFilterDriver class uses its class initializer to load and instantiate the class that describes Java’s JDBC-ODBC Bridge Driver and to register a JDBCFilterDriver instance with Java’s database driver. Although this listing’s JDBC-oriented code is probably meaningless to you right now, the listing illustrates the usefulness of class initializers. (I discuss JDBC in Chapter 14.)

A class can declare a mix of class initializers and class field initializers as demonstrated in Listing 3-17.

Listing 3-17.  Mixing Class Initializers with Class Field Initializers

class C
{
   static
   {
      System.out.println("class initializer 1");
   }

   static int counter = 1;

   static
   {
      System.out.println("class initializer 2");
      System.out.println("counter = " + counter);
   }
}

Listing 3-17 declares a class named C that specifies two class initializers and one class field initializer. When the Java compiler compiles into a classfile a class that declares at least one class initializer or class field initializer, it creates a special void <clinit>() class method that stores the bytecode equivalent of all class initializers and class field initializers in the order they occur (from top to bottom).

Note   <clinit> is not a valid Java method name but is a valid name from the runtime perspective. The angle brackets were chosen as part of the name to prevent a name conflict with any clinit() methods that you might declare in the class.

For class C, <clinit>() would first contain the bytecode equivalent of System.out.println("class initializer 1");, it would next contain the bytecode equivalent of static int counter = 1;, and it would finally contain the bytecode equivalent of System.out.println("class initializer 2"); System.out.println("counter = " + counter);.

When class C is loaded into memory, <clinit>() executes immediately and generates the following output:

class initializer 1
class initializer 2
counter = 1

Instance Initializers

Not all classes can have constructors, as you will discover in Chapter 5 when I present anonymous classes. For these classes, Java supplies the instance initializer to take care of instance initialization tasks.

An instance initializer is a block that is introduced into a class body as opposed to being introduced as the body of a method or a constructor. The instance initializer is used to initialize an object via a sequence of statements as demonstrated in Listing 3-18.

Listing 3-18.  Initializing a Pair of Arrays via an Instance Initializer

class Graphics
{
   double[] sines;
   double[] cosines;

   {
      sines = new double[360];
      cosines = new double[sines.length];
      for (int degree = 0; degree < sines.length; degree++)
      {
         sines[degree] = Math.sin(Math.toRadians(degree));
         cosines[degree] = Math.cos(Math.toRadians(degree));
      }
   }
}

Listing 3-18’s Graphics class uses an instance initializer to create an object’s sines and cosines arrays and to initialize these arrays’ elements to the sines and cosines of angles ranging from 0 through 359 degrees. It does so because it’s faster to read array elements than to repeatedly call Math.sin() and Math.cos() elsewhere; performance matters. (In Chapter 7 I introduce Math.sin() and Math.cos().)

A class can declare a mix of instance initializers and instance field initializers as shown in Listing 3-19.

Listing 3-19.  Mixing Instance Initializers with Instance Field Initializers

class C
{
   {
      System.out.println("instance initializer 1");
   }

   int counter = 1;

   {
      System.out.println("instance initializer 2");
      System.out.println("counter = " + counter);
   }
}

Listing 3-19 declares a class named C that specifies two instance initializers and one instance field initializer. When the Java compiler compiles a class into a classfile, it creates a special void <init>() method representing the default noargument constructor when no constructor is explicitly declared; otherwise, it creates an <init>() method for each encountered constructor. Furthermore, it stores in each <init>() method the bytecode equivalent of all instance initializers and instance field initializers in the order they occur (from top to bottom) and before the constructor code.

Note   <init> is not a valid Java method name but is a valid name from the runtime perspective. The angle brackets were chosen as part of the name to prevent a name conflict with any init() methods that you might declare in the class.

For class C, <init>() would first contain the bytecode equivalent of System.out.println("instance initializer 1");, it would next contain the bytecode equivalent of int counter = 1;, and it would finally contain the bytecode equivalent of System.out.println("instance initializer 2"); System.out.println("counter = " + counter);.

When new C() executes, <init>() executes immediately and generates the following output:

instance initializer 1
instance initializer 2
counter = 1

Note   You should rarely need to use the instance initializer, which is not commonly used in industry. Other developers would likely miss the instance initializer while scanning the source code and might find it confusing.

Initialization Order

A class’s body can contain a mixture of class field initializers, class initializers, instance field initializers, instance initializers, and constructors. (You should prefer constructors to instance field initializers, although I am guilty of not doing so consistently, and restrict your use of instance initializers to anonymous classes, discussed in Chapter 5.) Furthermore, class fields and instance fields initialize to default values. Understanding the order in which all of this initialization occurs is necessary to preventing confusion, so check out Listing 3-20.

Listing 3-20.  A Complete Initialization Demo

public class InitDemo
{
   static double double1;
   double double2;
   static int int1;
   int int2;
   static String string1;
   String string2;

   static
   {
      System.out.println("[class] double1 = " + double1);
      System.out.println("[class] int1 = " + int1);
      System.out.println("[class] string1 = " + string1);
      System.out.println();
   }

   {
      System.out.println("[instance] double2 = " + double2);
      System.out.println("[instance] int2 = " + int2);
      System.out.println("[instance] string2 = " + string2);
      System.out.println();
   }

   static
   {
      double1 = 1.0;
      int1 = 1000000000;
      string1 = "abc";
   }

   {
      double2 = 1.0;
      int2 = 1000000000;
      string2 = "abc";
   }

   InitDemo()
   {
      System.out.println("InitDemo() called");
      System.out.println();
   }

   static double double3 = 10.0;
   double double4 = 10.0;

   static
   {
      System.out.println("[class] double3 = " + double3);
      System.out.println();
   }

   {
      System.out.println("[instance] double4 = " + double3);
      System.out.println();
   }

   public static void main(String[] args)
   {
      System.out.println ("main() started");
      System.out.println();
      System.out.println("[class] double1 = " + double1);
      System.out.println("[class] double3 = " + double3);
      System.out.println("[class] int1 = " + int1);
      System.out.println("[class] string1 = " + string1);
      System.out.println();
      for (int i = 0; i < 2; i++)
      {
         System.out.println("About to create InitDemo object");
         System.out.println();
         InitDemo id = new InitDemo();
         System.out.println("id created");
         System.out.println();
         System.out.println("[instance] id.double2 = " + id.double2);
         System.out.println("[instance] id.double4 = " + id.double4);
         System.out.println("[instance] id.int2 = " + id.int2);
         System.out.println("[instance] id.string2 = " + id.string2);
         System.out.println();
      }
   }
}

Listing 3-20’s InitDemo class declares two class fields and two instance fields for the double precision floating-point primitive type, one class field and one instance field for the integer primitive type, and one class field and one instance field for the String reference type. It also introduces one explicitly initialized class field, one explicitly initialized instance field, three class initializers, three instance initializers, and one constructor. If you compile and run this code, you will observe the following output:

[class] double1 = 0.0
[class] int1 = 0
[class] string1 = null

[class] double3 = 10.0

main() started

[class] double1 = 1.0
[class] double3 = 10.0
[class] int1 = 1000000000
[class] string1 = abc

About to create InitDemo object

[instance] double2 = 0.0
[instance] int2 = 0
[instance] string2 = null

[instance] double4 = 10.0

InitDemo() called

id created

[instance] id.double2 = 1.0
[instance] id.double4 = 10.0
[instance] id.int2 = 1000000000
[instance] id.string2 = abc

About to create InitDemo object

[instance] double2 = 0.0
[instance] int2 = 0
[instance] string2 = null

[instance] double4 = 10.0

InitDemo() called

id created

[instance] id.double2 = 1.0
[instance] id.double4 = 10.0
[instance] id.int2 = 1000000000
[instance] id.string2 = abc

As you study this output in conjunction with the aforementioned discussion of class initializers and instance initializers, you’ll discover some interesting facts about initialization:

  • Class fields initialize to default or explicit values just after a class is loaded. Immediately after a class loads, all class fields are zeroed to default values. Code within the <clinit>() method performs explicit initialization.
  • All class initialization occurs prior to the <clinit>() method returning.
  • Instance fields initialize to default or explicit values during object creation. When new allocates memory for an object, it zeros all instance fields to default values. Code within an <init>() method performs explicit initialization.
  • All instance initialization occurs prior to the <init>() method returning.

Additionally, because initialization occurs in a top-down manner, attempting to access the contents of a class field before that field is declared or attempting to access the contents of an instance field before that field is declared causes the compiler to report an illegal forward reference.

Collecting Garbage

Objects are created via reserved word new, but how are they destroyed? Without some way to destroy objects, they will eventually fill up the heap’s available space and the application will not be able to continue. Java doesn’t provide the developer with the ability to remove them from memory. Instead, Java handles this task by providing a garbage collector, which is code that runs in the background and occasionally checks for unreferenced objects. When the garbage collector discovers an unreferenced object (or multiple objects that reference each other and where there are no other references to each other—only A references B and only B references A, for example), it removes the object from the heap, making more heap space available.

An unreferenced object is an object that cannot be accessed from anywhere within an application. For example, new Employee("John", "Doe"); is an unreferenced object because the Employee reference returned by new is thrown away. In contrast, a referenced object is an object where the application stores at least one reference. For example, Employee emp = new Employee("John", "Doe"); is a referenced object because variable emp contains a reference to the Employee object.

A referenced object becomes unreferenced when the application removes its last stored reference. For example, if emp is a local variable that contains the only reference to an Employee object, this object becomes unreferenced when the method in which emp is declared returns. An application can also remove a stored reference by assigning null to its reference variable. For example, emp = null; removes the reference to the Employee object that was previously stored in emp.

Java’s garbage collector eliminates a form of memory leakage in C++ implementations that do not rely on a garbage collector. In these C++ implementations, the developer must destroy dynamically created objects before they go out of scope. If they vanish before destruction, they remain in the heap. Eventually, the heap fills and the application halts.

Although this form of memory leakage is not a problem in Java, a related form of leakage is problematic: continually creating objects and forgetting to remove even one reference to each object causes the heap to fill up and the application to eventually come to a halt. This form of memory leakage typically occurs in the context of collections (object-based data structures that store objects) and is a major problem for applications that run for lengthy periods of time—a web server is one example. For shorter-lived applications, you will normally not notice this form of memory leakage.

Consider Listing 3-21.

Listing 3-21.  A Memory-Leaking Stack

public class Stack
{
   private Object[] elements;
   private int top;

   public Stack(int size)
   {
      elements = new Object[size];
      top =1; // indicate that stack is empty
   }

   public void push(Object o)
   {
      if (top + 1 == elements.length)
      {
         System.out.println("stack is full");
         return;
     }
     elements[++top] = o;
   }

   public Object pop()
   {
      if (top ==1)
      {
         System.out.println("stack is empty");
         return null;
      }
      Object element = elements[top--];
//      elements[top + 1] = null;
      return element;
   }

   public static void main(String[] args)
   {
      Stack stack = new Stack(2);
      stack.push("A");
      stack.push("B");
      stack.push("C");
      System.out.println(stack.pop());
      System.out.println(stack.pop());
      System.out.println(stack.pop());
   }
}

Listing 3-21 describes a collection known as a stack, a data structure that stores elements in last-in, first-out order. Stacks are useful for remembering things, such as the instruction to return to when a method stops executing and must return to its caller.

Stack provides a push() method for pushing arbitrary objects onto the top of the stack and a pop() method for popping objects off of the stack’s top in the reverse order to which they were pushed.

After creating a Stack object that can store a maximum of two objects, main() invokes push() three times to push three String objects onto the stack. Because the stack’s internal array can store two objects only, push() outputs an error message when main() tries to push "C".

At this point, main() attempts to pop three Objects off of the stack, outputting each object to the standard output device. The first two pop() method calls succeed, but the final method call fails and outputs an error message because the stack is empty when it is called.

When you run this application, it generates the following output:

stack is full
B
A
stack is empty
null

There is a problem with the Stack class: it leaks memory. When you push an object onto the stack, its reference is stored in the internal elements array. When you pop an object off of the stack, the object’s reference is obtained and top is decremented, but the reference remains in the array (until you invoke push()).

Imagine a scenario where the Stack object’s reference is assigned to a class field, which means that the Stack object hangs around for the life of the application. Furthermore, suppose that you have pushed three 50-megabyte Image objects onto the stack and then subsequently popped them off of the stack. After using these objects, you assign null to their reference variables, thinking that they will be garbage collected the next time the garbage collector runs. However, this won’t happen because the Stack object still maintains its references to these objects, and so 150 megabytes of heap space will not be available to the application, and maybe the application will run out of memory.

The solution to this problem is for pop() to explicitly assign null to the elements entry prior to returning the reference. Simply uncomment the elements[top + 1] = null; line in Listing 3-21 to make this happen.

You might think that you should always assign null to reference variables when their referenced objects are no longer required. However, doing so often doesn’t improve performance or free up significant amounts of heap space and can lead to thrown instances of the java.lang.NullPointerException class when you’re not careful. (I discuss NullPointerException in the context of Chapter 5’s coverage of Java’s exceptions-oriented language features). You typically nullify reference variables in classes that manage their own memory, such as the aforementioned Stack class.

Note   To learn more about garbage collection in a Java 5 context, check out Oracle’s “Memory Management in the Java HotSpot Virtual Machine” whitepaper (www.oracle.com/technetwork/java/javase/tech/memorymanagement-whitepaper-1-150020.pdf).

Revisiting Arrays

In Chapter 2 I introduced you to arrays, which are regions of memory (specifically, the heap) that store values in equal-size and contiguous slots, known as elements. I also presented several examples, including the following example:

char gradeLetters[] = { 'A', 'B', 'C', 'D', 'F' };

Here you have an array variable named gradeLetters that stores a reference to a five-element region of memory, which stores the characters A, B, C, D, and F in contiguous and equal-size (16-bit) memory locations.

Note   I’ve placed the [] brackets after gradeLetters. Although this is legal, it’s conventional to place these brackets after the type name, as in char[] gradeLetters = { 'A', 'B', 'C', 'D', 'F' };. I demonstrate both approaches in this section.

You access an element by specifying gradeLetters[ x ], where x is an integer that identifies an array element and is known as an index; the first array element is always located at index 0. The following example shows you how to output and change the first element’s value:

System.out.println(gradeLetters[0]); // Output the first grade letter.
gradeLetters[0] = 'a'; // Perhaps you prefer lowercase grade letters.

The { 'A', 'B', 'C', 'D', 'F' } array-creation syntax is an example of syntactic sugar (syntax that simplifies a language, making it “sweeter” to use). Behind the scenes, the array is created with the new operator and initializes to these values, as follows:

char gradeLetters[] = new char[] { 'A', 'B', 'C', 'D', 'F' };

First, a five-character region of memory is allocated. Next, the region’s five character elements are initialized to A, B, C, D, and F. Finally, a reference to these elements is stored in array variable gradeLetters.

Caution   It’s an error to place an integer value between the square brackets following char. For example, the compiler reports an error when it encounters the 5 in new char[5] { 'A', 'B', 'C', 'D', 'F' };.

You can think of an array as a special kind of object, although it’s not an object in the same sense that a class instance is an object. This pseudo-object has a solitary and read-only length field that contains the array’s size (the number of elements). For example, gradeLetters.length returns the number of elements (5) in the gradeLetters array.

Although you can use either of the previous two approaches to create an array, you will often specify a third approach that doesn’t involve explicit element initialization and subsequently initialize the array. This approach is demonstrated by the following code:

char gradeLetters[] = new char[5];

You specify the number of elements as a positive integer between the square brackets. Operator new zeros the bits in each array element’s storage location, which you interpret at the source code level as literal value false, 'u0000', 0, 0L, 0.0, 0.0F, or null (depending on element type).

You can then initialize the array, as follows:

gradeLetters[0] = 'A';
gradeLetters[1] = 'B';
gradeLetters[2] = 'C';
gradeLetters[3] = 'D';
gradeLetters[4] = 'F';

However, you will probably find it more convenient to use a loop for this task, as follows:

for (int i = 0; i < gradeLetters.length; i++)
   gradeLetters[i] = 'A' + i;

The previous examples focused on creating an array whose values share a common primitive type (character, represented by the char keyword). You can also create an array of object references. For example, you can create an array to store three Image object references, as follows:

Image[] imArray = { new Image("image0.png"), new Image("image1.png"), new Image("image2.png") };

Here you have an array variable named imArray that stores a reference to a three-element region of memory, where each element stores a reference to an Image object. The Image object is located elsewhere in memory.

You access an Image element by specifying imArray[ x ]. The following example assumes the existence of a getLength() method that returns the image’s length (in bytes) and calls this method on the first Image object to return the first image’s length, which is subsequently output:

System.out.println(imArray[0].getLength());

As with the previous gradeLetters example, you can combine the new operator with the syntactic sugar initializer, as follows:

Image[] imArray = new Image[] { new Image("image0.png"), new Image("image1.png"),
                                new Image("image2.png") };

Finally, you can use the third approach, which initializes each object reference to the null reference by setting all of the bits in each element to 0. This approach is demonstrated following:

Image[] imArray = new Image[3];

Because new initializes each element to the null reference, you must explicitly initialize this array, and you can conveniently do so as follows:

for (int i = 0; i < imArray.length; i++)
   imArray[i] = new Image("image" + i + ".png"); // image0.png, image1.png, and so on

The "image" + i + ".png" expression uses the string concatenation operator (+) to combine image with the string equivalent of the integer value stored in variable i with .png. The resulting string is passed to Image’s Image(String filename) constructor, and the resulting reference is stored in one of the array elements.

Note   Use of the string concatenation operator in a loop context can result in a lot of unnecessary String object creation, depending on the length of the loop. I will discuss this topic in Chapter 7 when I introduce you to the String class.

The previous examples have focused on creating one-dimensional arrays. However, you can also create multidimensional arrays (that is, arrays with two or more dimensions). For example, consider a two-dimensional array of temperature values.

Although you can use any of the three approaches to create the temperatures array, the third approach is preferable when the values vary greatly. The following example creates this array as a three-row-by-two-column table of double precision floating-point temperature values:

double[][] temperatures = new double[3][2];

Notice the two sets of square brackets between double and temperatures. These two sets of brackets signify the array as two-dimensional (a table). Also notice the two sets of square brackets following new and double. Each set contains a positive integer value signifying the number of rows (3) or the number of columns (2) for each row.

Note   When creating a multidimensional array, the number of square bracket pairs that are associated with the array variable and the number of square bracket pairs that follow new and the type name must be the same.

After creating the array, you can populate its elements with suitable values. The following example initializes each temperatures element, which is accessed as temperatures[row][col], to a randomly generated temperature value via Math.random(), which I’ll explain in Chapter 7:

for (int row = 0; row < temperatures.length; row++)
   for (int col = 0; col < temperatures[row].length; col++)
      temperatures[row][col] = Math.random() * 100;

The outer for loop selects each row from row 0 to the length of the array (which identifies the number of rows in the array). The inner for loop selects each column from 0 to the length of the current row array (which identifies the number of columns represented by that array). In essence, you are looking at a one-dimensional row array where each element references a one-dimensional column array.

You can subsequently output these values in a tabular format by using another for loop as demonstrated by the following example—the code makes no attempt to align the temperature values in perfect columns:

for (int row = 0; row < temperatures.length; row++)
{
   for (int col = 0; col < temperatures[row].length; col++)
      System.out.print(temperatures[row][col] + " ");
   System.out.println();
}

Java provides an alternative for creating a multidimensional array in which you create each dimension separately. For example, to create the previous two-dimensional temperatures array via new in this manner, first create a one-dimensional row array (the outer array), and then create a one-dimensional column array (the inner array), as demonstrated by the following code:

// Create the row array.
double[][] temperatures = new double[3][]; // Note the extra empty pair of brackets.
// Create a column array for each row.
for (int row = 0; row < temperatures.length; row++)
   temperatures[row] = new double[2]; // 2 columns per row

This kind of an array is known as a ragged array because each row can have a different number of columns; the array is not rectangular, but is ragged.

Note   When creating the row array, you must specify an extra pair of empty brackets as part of the expression following new. (For a three-dimensional array—a one-dimensional array of tables, where this array’s elements reference row arrays—you must specify two pairs of empty brackets as part of the expression following new.)

EXERCISES

The following exercises are designed to test your understanding of Chapter 3’s content:

  1. What is a class?
  2. How do you declare a class?
  3. What is an object?
  4. How do you instantiate an object?
  5. What is a constructor?
  6. True or false: Java creates a default noargument constructor when a class declares no constructors.
  7. What is a parameter list and what is a parameter?
  8. What is an argument list and what is an argument?
  9. True or false: You invoke another constructor by specifying the name of the class followed by an argument list.
  10. Define arity.
  11. What is a local variable?
  12. Define lifetime.
  13. Define scope.
  14. What is encapsulation?
  15. Define field.
  16. What is the difference between an instance field and a class field?
  17. What is a blank final and how does it differ from a true constant?
  18. How do you prevent a field from being shadowed?
  19. Define method.
  20. What is the difference between an instance method and a class method?
  21. Define recursion.
  22. How do you overload a method?
  23. What is a class initializer, and what is an instance initializer?
  24. Define garbage collector.
  25. True or false: String[ ] letters = new String[2] { "A", "B" }; is correct syntax.
  26. What is a ragged array?
  27. The factorial() method provides an example of tail recursion, a special case of recursion in which the method’s last statement contains a recursive call, which is known as a tail call. Provide another example of tail recursion.
  28. Create a Book class with name, author, and International Standard Book Number (ISBN) fields. Provide a suitable constructor and getter methods that return field values. Introduce a main() method into this class that creates an array of Book objects and iterates over this array outputting each book’s name, author, and ISBN.

Summary

A class is a template for manufacturing objects, which are named aggregates of code and data. Classes generalize real-world entities, and objects are specific manifestations of these entities at the application level.

The new operator allocates memory to store the object whose type is specified by new’s solitary operand. This operator is followed by a constructor, which is a block of code for initializing an object. new calls the constructor immediately after allocating memory to store the object.

Java lets you represent an entity’s state via fields, which are variables declared within a class’s body. Entity attributes are described via instance fields. Because Java also supports state that’s associated with a class and not with an object, Java provides class fields to describe this class state.

Java lets you represent an entity’s behaviors via methods, which are named blocks of code declared within a class’s body. Entity behaviors are described via instance methods. Because Java also supports behaviors that are associated with classes and not with objects, Java provides class methods to describe these class behaviors.

Classes and objects need to be properly initialized before they are used. You’ve already learned that class fields are initialized to default zero values after a class loads and can be subsequently initialized by assigning values to them in their declarations via class field initializers. Similarly, instance fields are initialized to default values when an object’s memory is allocated via new and can be subsequently initialized by assigning values to them in their declarations via instance field initializers or via constructors.

Java also supports class initializers and instance initializers for this task. A class initializer is a static-prefixed block that is introduced into a class body. It is used to initialize a loaded class via a sequence of statements. An instance initializer is a block that is introduced into a class body, as opposed to being introduced as the body of a method or a constructor. The instance initializer is used to initialize an object via a sequence of statements.

Objects are created via reserved word new, but how are they destroyed? Without some way to destroy objects, they will eventually fill up the heap’s available space and the application will not be able to continue. Java doesn’t provide the developer with the ability to remove them from memory. Instead, Java handles this task by providing a garbage collector, which is code that runs in the background and occasionally checks for unreferenced objects.

You can think of an array as a special kind of object, although it’s not an object in the same sense that a class instance is an object. This pseudo-object has a solitary and read-only length field that contains the array’s size (the number of elements).

As well as using the syntactic sugar first presented in Chapter 2 for creating an array, you can also create an array using the new operator, with or without the syntactic sugar.

In Chapter 4 I continue to explore the Java language by examining its support for inheritance, polymorphism, and interfaces.

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

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