Wednesday 8 March 2017

Using Objects as Parameters, A Closer Look at Argument Passing & Returning Objects - Java Tutorials

Using Objects as Parameters


So far we have only been using simple types as parameters to methods. However, it is both correct and common to pass objects to methods. For example, consider the following short program:

  // Objects may be passed to methods.
  class Test {
    int a, b;

    Test(int i, int j) {
      a = i;
      b = j;
    }

    // return true if o is equal to the invoking object
    boolean equals(Test o) {
      if(o.a == a && o.b == b) return true;
      else return false;
    }
  }

  class PassOb {
    public static void main(String args[]) {
      Test ob1 = new Test(100, 22);
      Test ob2 = new Test(100, 22);
      Test ob3 = new Test(-1, -1);

      System.out.println("ob1 == ob2: " + ob1.equals(ob2));

      System.out.println("ob1 == ob3: " + ob1.equals(ob3));
    }
  }

This program generates the following output:

  ob1 == ob2: true
  ob1 == ob3: false

As you can see, the equals( ) method inside Test compares two objects for equality and returns the result. That is, it compares the invoking object with the one that it is passed. If they contain the same values, then the method returns true. Otherwise, it returns false. Notice that the parameter o in equals( ) specifies Test as its type. Although Test is a class type created by the program, it is used in just the same way as Java’s built-in types.

One of the most common uses of object parameters involves constructors. Frequently you will want to construct a new object so that it is initially the same as some existing object. To do this, you must define a constructor that takes an object of its class as a parameter. For example, the following version of Box allows one object to initialize another:

  // Here, Box allows one object to initialize another.

  class Box {
    double width;
    double height;
    double depth;

    // construct clone of an object
    Box(Box ob) { // pass object to constructor
      width = ob.width;
      height = ob.height;
      depth = ob.depth;
    }

    // constructor used when all dimensions specified
    Box(double w, double h, double d) {
      width = w;
      height = h;
      depth = d;
    }

    // constructor used when no dimensions specified
    Box() {
      width = -1; // use -1 to indicate
      height = -1; // an uninitialized
      depth = -1; // box
    }

    // constructor used when cube is created
    Box(double len) {
      width = height = depth = len;
    }

    // compute and return volume
    double volume() {
      return width * height * depth;
    }
  }

  class OverloadCons2 {
    public static void main(String args[]) {
      // create boxes using the various constructors
      Box mybox1 = new Box(10, 20, 15);
      Box mybox2 = new Box();
      Box mycube = new Box(7);

      Box myclone = new Box(mybox1);

      double vol;

      // get volume of first box
      vol = mybox1.volume();
      System.out.println("Volume of mybox1 is " + vol);

      // get volume of second box
      vol = mybox2.volume();
      System.out.println("Volume of mybox2 is " + vol);

      // get volume of cube
      vol = mycube.volume();
      System.out.println("Volume of cube is " + vol);

      // get volume of clone
      vol = myclone.volume();
      System.out.println("Volume of clone is " + vol);
    }
  }

As you will see when you begin to create your own classes, providing many forms of constructor methods is usually required to allow objects to be constructed in a convenient and efficient manner.





A Closer Look at Argument Passing

In general, there are two ways that a computer language can pass an argument to a subroutine. The first way is call-by-value. This method copies the value of an argument into the formal parameter of the subroutine. Therefore, changes made to the parameter of the subroutine have no effect on the argument. The second way an argument can be passed is call-by-reference. In this method, a reference to an argument (not the value of the argument) is passed to the parameter. Inside the subroutine, this reference is used to access the actual argument specified in the call. This means that changes made to the parameter will affect the argument used to call the subroutine. As you will see, Java uses both approaches, depending upon what is passed.

In Java, when you pass a simple type to a method, it is passed by value. Thus, what occurs to the parameter that receives the argument has no effect outside the method. For example, consider the following program:

  // Simple types are passed by value.
  class Test {
    void meth(int i, int j) {
      i *= 2;
      j /= 2;
    }
  }

  class CallByValue {
    public static void main(String args[]) {
      Test ob = new Test();

      int a = 15, b = 20;

      System.out.println("a and b before call: " + a + " " + b);

      ob.meth(a, b);

      System.out.println("a and b after call: " + a + " " + b);
    }
  }

The output from this program is shown here:

  a and b before call: 15 20
  a and b after call: 15 20

As you can see, the operations that occur inside meth( ) have no effect on the values of a and b used in the call; their values here did not change to 30 and 10.

When you pass an object to a method, the situation changes dramatically, because objects are passed by reference. Keep in mind that when you create a variable of a class type, you are only creating a reference to an object. Thus, when you pass this reference to a method, the parameter that receives it will refer to the same object as that referred to by the argument. This effectively means that objects are passed to methods by use of call-by-reference. Changes to the object inside the method do affect the object used as an argument. For example, consider the following program:

  // Objects are passed by reference.
  class Test {
    int a, b;

    Test(int i, int j) {
      a = i;
      b = j;
    }

    // pass an object
    void meth(Test o) {
      o.a *= 2;

      o.b /= 2;
    }
  }

  class CallByRef {
    public static void main(String args[]) {
      Test ob = new Test(15, 20);

      System.out.println("ob.a and ob.b before call: " +
                                       ob.a + " " + ob.b);

      ob.meth(ob);

      System.out.println("ob.a and ob.b after call: " +
                                      ob.a + " " + ob.b);
    }
  }

This program generates the following output:

  ob.a and ob.b before call: 15 20
  ob.a and ob.b after call: 30 10

As you can see, in this case, the actions inside meth( ) have affected the object used as an argument.

As a point of interest, when an object reference is passed to a method, the reference itself is passed by use of call-by-value. However, since the value being passed refers to an object, the copy of that value will still refer to the same object that its corresponding argument does.

When a simple type is passed to a method, it is done by use of call-by-value. Objects are passed by use of call-by-reference.




Returning Objects

A method can return any type of data, including class types that you create. For example, in the following program, the incrByTen( ) method returns an object in which the value of a is ten greater than it is in the invoking object.

  // Returning an object.
  class Test {
    int a;

    Test(int i) {
      a = i;
    }

    Test incrByTen() {
      Test temp = new Test(a+10);
      return temp;
    }
  }

  class RetOb {
    public static void main(String args[]) {
      Test ob1 = new Test(2);
      Test ob2;

      ob2 = ob1.incrByTen();
      System.out.println("ob1.a: " + ob1.a);
      System.out.println("ob2.a: " + ob2.a);

      ob2 = ob2.incrByTen();
      System.out.println("ob2.a after second increase: " + ob2.a);
    }
  }

The output generated by this program is shown here:

  ob1.a: 2
  ob2.a: 12
  ob2.a after second increase: 22

As you can see, each time incrByTen( ) is invoked, a new object is created, and a reference to it is returned to the calling routine.

The preceding program makes another important point: Since all objects are dynamically allocated using new, you don’t need to worry about an object going out-of-scope because the method in which it was created terminates. The object will continue to exist as long as there is a reference to it somewhere in your program. When there are no references to it, the object will be reclaimed the next time garbage collection takes place.

Overloading Methods - Java Tutorials

In Java it is possible to define two or more methods within the same class that share the same name, as long as their parameter declarations are different. When this is the case, the methods are said to be overloaded, and the process is referred to as method overloading. Method overloading is one of the ways that Java implements polymorphism. If you have never used a language that allows the overloading of methods, then the concept may seem strange at first. But as you will see, method overloading is one of Java’s most exciting and useful features.

When an overloaded method is invoked, Java uses the type and/or number of arguments as its guide to determine which version of the overloaded method to actually call. Thus, overloaded methods must differ in the type and/or number of their parameters. While overloaded methods may have different return types, the return type alone is insufficient to distinguish two versions of a method. When Java encounters a call to an overloaded method, it simply executes the version of the method whose parameters match the arguments used in the call. Here is a simple example that illustrates method overloading:

  // Demonstrate method overloading.
  class OverloadDemo {
    void test() {
      System.out.println("No parameters");
    }

    // Overload test for one integer parameter.
    void test(int a) {
      System.out.println("a: " + a);
    }

    // Overload test for two integer parameters.
    void test(int a, int b) {
      System.out.println("a and b: " + a + " " + b);
    }

    // overload test for a double parameter
    double test(double a) {
      System.out.println("double a: " + a);
      return a*a;
    }
  }

  class Overload {
    public static void main(String args[]) {
      OverloadDemo ob = new OverloadDemo();
      double result;

      // call all versions of test()
      ob.test();
      ob.test(10);
      ob.test(10, 20);
      result = ob.test(123.25);
      System.out.println("Result of ob.test(123.25): " + result);
    }
  }

This program generates the following output:

  No parameters
  a: 10
  a and b: 10 20
  double a: 123.25
  Result of ob.test(123.25): 15190.5625

As you can see, test( ) is overloaded four times. The first version takes no parameters, the second takes one integer parameter, the third takes two integer parameters, and the fourth takes one double parameter. The fact that the fourth version of test( ) also returns a value is of no consequence relative to overloading, since return types do not play a role in overload resolution.

When an overloaded method is called, Java looks for a match between the arguments used to call the method and the method’s parameters. However, this match need not always be exact. In some cases Java’s automatic type conversions can play a role in overload resolution. For example, consider the following program:

  // Automatic type conversions apply to overloading.
  class OverloadDemo {
    void test() {
      System.out.println("No parameters");
    }

    // Overload test for two integer parameters.
    void test(int a, int b) {
      System.out.println("a and b: " + a + " " + b);
    }

    // overload test for a double parameter
    void test(double a) {
      System.out.println("Inside test(double) a: " + a);
    }
  }

  class Overload {
    public static void main(String args[]) {
      OverloadDemo ob = new OverloadDemo();
      int i = 88;

      ob.test();
      ob.test(10, 20);

      ob.test(i); // this will invoke test(double)
      ob.test(123.2); // this will invoke test(double)
    }
  }

This program generates the following output:

  No parameters
  a and b: 10 20
  Inside test(double) a: 88
  Inside test(double) a: 123.2

As you can see, this version of OverloadDemo does not define test(int). Therefore, when test( ) is called with an integer argument inside Overload, no matching method is found. However, Java can automatically convert an integer into a double, and this conversion can be used to resolve the call. Therefore, after test(int) is not found, Java elevates i to double and then calls test(double). Of course, if test(int) had been defined, it would have been called instead. Java will employ its automatic type conversions only if no exact match is found.

Method overloading supports polymorphism because it is one way that Java implements the “one interface, multiple methods” paradigm. To understand how, consider the following. In languages that do not support method overloading, each method must be given a unique name. However, frequently you will want to implement essentially the same method for different types of data. Consider the absolute value function. In languages that do not support overloading, there are usually three or more versions of this function, each with a slightly different name. For instance, in C, the function abs( ) returns the absolute value of an integer, labs( ) returns the absolute value of a long integer, and fabs( ) returns the absolute value of a floating-point value. Since C does not support overloading, each function has to have its own name, even though all three functions do essentially the same thing. This
makes the situation more complex, conceptually, than it actually is. Although the underlying concept of each function is the same, you still have three names to remember. This situation does not occur in Java, because each absolute value method can use the same name. Indeed, Java’s standard class library includes an absolute value method, called abs( ). This method is overloaded by Java’s Math class to handle all numeric types. Java determines which version of abs( ) to call based upon the type of argument.

The value of overloading is that it allows related methods to be accessed by use of a common name. Thus, the name abs represents the general action which is being performed. It is left to the compiler to choose the right specific version for a particular circumstance. You, the programmer, need only remember the general operation being performed. Through the application of polymorphism, several names have been reduced to one. Although this example is fairly simple, if you expand the concept, you can see how overloading can help you manage greater complexity.

When you overload a method, each version of that method can perform any activity you desire. There is no rule stating that overloaded methods must relate to one another. However, from a stylistic point of view, method overloading implies a relationship. Thus, while you can use the same name to overload unrelated methods, you should not. For example, you could use the name sqr to create methods that return the square of an integer and the square root of a floating-point value. But these two operations are fundamentally different. Applying method overloading in this manner defeats its original purpose. In practice, you should only overload closely related operations.

Overloading Constructors

In addition to overloading normal methods, you can also overload constructor methods. In fact, for most real-world classes that you create, overloaded constructors will be the norm, not the exception. To understand why, let’s return to the Box class developed in the preceding chapter. Following is the latest version of Box:

  class Box {
    double width;
    double height;
    double depth;

    // This is the constructor for Box.
    Box(double w, double h, double d) {
      width = w;
      height = h;
      depth = d;
    }

    // compute and return volume
    double volume() {
      return width * height * depth;
    }
  }

As you can see, the Box( ) constructor requires three parameters. This means that all declarations of Box objects must pass three arguments to the Box( ) constructor. For example, the following statement is currently invalid:

  Box ob = new Box();

Since Box( ) requires three arguments, it’s an error to call it without them. This raises some important questions. What if you simply wanted a box and did not care (or know) what its initial dimensions were? Or, what if you want to be able to initialize a cube by specifying only one value that would be used for all three dimensions? As the Box class is currently written, these other options are not available to you.

Fortunately, the solution to these problems is quite easy: simply overload the Box constructor so that it handles the situations just described. Here is a program that contains an improved version of Box that does just that:

  /* Here, Box defines three constructors to initialize
     the dimensions of a box various ways.
  */
  class Box {
    double width;
    double height;
    double depth;

    // constructor used when all dimensions specified
    Box(double w, double h, double d) {
      width = w;
      height = h;
      depth = d;
    }

    // constructor used when no dimensions specified
    Box() {
      width = -1; // use -1 to indicate
      height = -1; // an uninitialized
      depth = -1; // box
    }

    // constructor used when cube is created
    Box(double len) {
      width = height = depth = len;
    }

    // compute and return volume
    double volume() {
      return width * height * depth;
    }
  }

  class OverloadCons {
    public static void main(String args[]) {
      // create boxes using the various constructors
      Box mybox1 = new Box(10, 20, 15);
      Box mybox2 = new Box();
      Box mycube = new Box(7);

      double vol;

      // get volume of first box
      vol = mybox1.volume();
      System.out.println("Volume of mybox1 is " + vol);

      // get volume of second box
      vol = mybox2.volume();
      System.out.println("Volume of mybox2 is " + vol);

      // get volume of cube
      vol = mycube.volume();
      System.out.println("Volume of mycube is " + vol);
    }
  }

The output produced by this program is shown here:

  Volume of mybox1 is 3000.0
  Volume of mybox2 is -1.0
  Volume of mycube is 343.0

As you can see, the proper overloaded constructor is called based upon the parameters specified when new is executed.

The finalize( ) Method, A Stack Class - Java Tutorials

The finalize( ) Method

Sometimes an object will need to perform some action when it is destroyed. For example, if an object is holding some non-Java resource such as a file handle or window character font, then you might want to make sure these resources are freed before an object is destroyed. To handle such situations, Java provides a mechanism called finalization. By using finalization, you can define specific actions that will occur when an object is just about to be reclaimed by the garbage collector.

To add a finalizer to a class, you simply define the finalize( ) method. The Java run time calls that method whenever it is about to recycle an object of that class. Inside the finalize( ) method you will specify those actions that must be performed before an object is destroyed. The garbage collector runs periodically, checking for objects that are no longer referenced by any running state or indirectly through other referenced objects. Right before an asset is freed, the Java run time calls the finalize( ) method on the object.

The finalize( ) method has this general form:

  protected void finalize( )
  {
  // finalization code here
  }

Here, the keyword protected is a specifier that prevents access to finalize( ) by code defined outside its class. This and the other access specifiers are explained in Chapter 7.

It is important to understand that finalize( ) is only called just prior to garbage collection. It is not called when an object goes out-of-scope, for example. This means that you cannot know when—or even if—finalize( ) will be executed. Therefore, your program should provide other means of releasing system resources, etc., used by the object. It must not rely on finalize( ) for normal program operation.

If you are familiar with C++, then you know that C++ allows you to define a destructor for a class, which is called when an object goes out-of-scope. Java does not support this idea or provide for destructors. The finalize( ) method only approximates the function of a destructor. As you get more experienced with Java, you will see that the need for destructor functions is minimal because of Java’s garbage collection subsystem.




A Stack Class

While the Box class is useful to illustrate the essential elements of a class, it is of little practical value. To show the real power of classes, this chapter will conclude with a more sophisticated example. As you recall from the discussion of object-oriented programming (OOP) presented in Chapter 2, one of OOP’s most important benefits is the encapsulation of data and the code that manipulates that data. As you have seen, the class is the mechanism by which encapsulation is achieved in Java. By creating a class, you are creating a new data type that defines both the nature of the data being manipulated and the routines used to manipulate it. Further, the methods define a consistent and controlled interface to the class’ data. Thus, you can use the class through its methods without having to worry about the details of its implementation or how the data is actually managed within the class. In a sense, a class is like a “data engine.” No knowledge of what goes on inside the engine is required to use the engine through its controls. In fact, since the details are hidden, its inner workings can be changed as needed. As long as your code uses the class through its methods, internal details can change without causing side effects outside the class.

To see a practical application of the preceding discussion, let’s develop one of the archetypal examples of encapsulation: the stack. A stack stores data using first-in, last-out ordering. That is, a stack is like a stack of plates on a table—the first plate put down on the table is the last plate to be used. Stacks are controlled through two operations traditionally called push and pop. To put an item on top of the stack, you will use push. To take an item off the stack, you will use pop. As you will see, it is easy to encapsulate the entire stack mechanism.

Here is a class called Stack that implements a stack for integers:

  // This class defines an integer stack that can hold 10 values.
  class Stack {
    int stck[] = new int[10];
    int tos;

    // Initialize top-of-stack
    Stack() {
      tos = -1;
    }

    // Push an item onto the stack
    void push(int item) {
      if(tos==9)
        System.out.println("Stack is full.");
      else
        stck[++tos] = item;
    }

    // Pop an item from the stack
    int pop() {
      if(tos < 0) {
        System.out.println("Stack underflow.");
        return 0;
      }
      else

        return stck[tos--];
    }
  }

As you can see, the Stack class defines two data items and three methods. The stack of integers is held by the array stck. This array is indexed by the variable tos, which always contains the index of the top of the stack. The Stack( ) constructor initializes tos to –1, which indicates an empty stack. The method push( ) puts an item on the stack. To retrieve an item, call pop( ). Since access to the stack is through push( ) and pop( ), the fact that the stack is held in an array is actually not relevant to using the stack. For example, the stack could be held in a more complicated data structure, such as a linked list, yet the interface defined by push( ) and pop( ) would remain the same.

The class TestStack, shown here, demonstrates the Stack class. It creates two integer stacks, pushes some values onto each, and then pops them off.

  class TestStack {
    public static void main(String args[]) {
      Stack mystack1 = new Stack();
      Stack mystack2 = new Stack();

      // push some numbers onto the stack
      for(int i=0; i<10; i++) mystack1.push(i);
      for(int i=10; i<20; i++) mystack2.push(i);

      // pop those numbers off the stack
      System.out.println("Stack in mystack1:");
      for(int i=0; i<10; i++)
        System.out.println(mystack1.pop());

      System.out.println("Stack in mystack2:");
      for(int i=0; i<10; i++)
        System.out.println(mystack2.pop());
    }
  }

This program generates the following output:

  Stack in mystack1:
  9
  8
  7
  6
  5
  4
  3
  2
  1
  0
  Stack in mystack2:
  19
  18
  17
  16
  15
  14
  13
  12
  11
  10

As you can see, the contents of each stack are separate.

One last point about the Stack class. As it is currently implemented, it is possible for the array that holds the stack, stck, to be altered by code outside of the Stack class. This leaves Stack open to misuse or mischief. In the next chapter, you will see how to remedy this situation.

Constructors, The this Keyword & Garbage Collection - Java Tutorials

Constructors


It can be tedious to initialize all of the variables in a class each time an instance is created. Even when you add convenience functions like setDim( ), it would be simpler and more concise to have all of the setup done at the time the object is first created. Because the requirement for initialization is so common, Java allows objects to initialize themselves when they are created. This automatic initialization is performed through the use of a constructor.

A constructor initializes an object immediately upon creation. It has the same name as the class in which it resides and is syntactically similar to a method. Once defined, the constructor is automatically called immediately after the object is created, before the new operator completes. Constructors look a little strange because they have no return type, not even void. This is because the implicit return type of a class’ constructor is the class type itself. It is the constructor’s job to initialize the internal state of an object so that the code creating an instance will have a fully initialized, usable object immediately.

You can rework the Box example so that the dimensions of a box are automatically initialized when an object is constructed. To do so, replace setDim( ) with a constructor. Let’s begin by defining a simple constructor that simply sets the dimensions of each box to the same values. This version is shown here:

  /* Here, Box uses a constructor to initialize the
     dimensions of a box.
  */
  class Box {
    double width;
    double height;
    double depth;

    // This is the constructor for Box.
    Box() {
      System.out.println("Constructing Box");
      width = 10;
      height = 10;
      depth = 10;
    }

    // compute and return volume
    double volume() {
      return width * height * depth;
    }
  }

  class BoxDemo6 {
    public static void main(String args[]) {
      // declare, allocate, and initialize Box objects
      Box mybox1 = new Box();
      Box mybox2 = new Box();

      double vol;

      // get volume of first box
      vol = mybox1.volume();
      System.out.println("Volume is " + vol);

      // get volume of second box
      vol = mybox2.volume();
      System.out.println("Volume is " + vol);
    }
  }

When this program is run, it generates the following results:

  Constructing Box
  Constructing Box
  Volume is 1000.0
  Volume is 1000.0

As you can see, both mybox1 and mybox2 were initialized by the Box( ) constructor when they were created. Since the constructor gives all boxes the same dimensions, 10 by 10 by 10, both mybox1 and mybox2 will have the same volume. The println( ) statement inside Box( ) is for the sake of illustration only. Most constructors will not display anything. They will simply initialize an object.

Before moving on, let’s reexamine the new operator. As you know, when you allocate an object, you use the following general form:

  class-var = new classname( );

Now you can understand why the parentheses are needed after the class name. What is actually happening is that the constructor for the class is being called. Thus, in the line

  Box mybox1 = new Box();

new Box( ) is calling the Box( ) constructor. When you do not explicitly define a constructor for a class, then Java creates a default constructor for the class. This is why the preceding line of code worked in earlier versions of Box that did not define a constructor. The default constructor automatically initializes all instance variables to zero. The default constructor is often sufficient for simple classes, but it usually won’t do for more sophisticated ones. Once you define your own constructor, the default constructor is no longer used.

Parameterized Constructors

While the Box( ) constructor in the preceding example does initialize a Box object, it is not very useful—all boxes have the same dimensions. What is needed is a way to construct Box objects of various dimensions. The easy solution is to add parameters to the constructor. As you can probably guess, this makes them much more useful. For example, the following version of Box defines a parameterized constructor which sets the dimensions of a box as specified by those parameters. Pay special attention to how Box objects are created.

  /* Here, Box uses a parameterized constructor to
     initialize the dimensions of a box.
  */
  class Box {
    double width;
    double height;
    double depth;

    // This is the constructor for Box.
    Box(double w, double h, double d) {
      width = w;
      height = h;
      depth = d;
    }

    // compute and return volume
    double volume() {
      return width * height * depth;
    }
  }

  class BoxDemo7 {
    public static void main(String args[]) {
      // declare, allocate, and initialize Box objects
      Box mybox1 = new Box(10, 20, 15);
      Box mybox2 = new Box(3, 6, 9);
      
      double vol;

      // get volume of first box
      vol = mybox1.volume();
      System.out.println("Volume is " + vol);

      // get volume of second box
      vol = mybox2.volume();
      System.out.println("Volume is " + vol);
    }
  }

The output from this program is shown here:

  Volume is 3000.0
  Volume is 162.0

As you can see, each object is initialized as specified in the parameters to its constructor. For example, in the following line,

  Box mybox1 = new Box(10, 20, 15);

the values 10, 20, and 15 are passed to the Box( ) constructor when new creates the object. Thus, mybox1’s copy of width, height, and depth will contain the values 10, 20, and 15, respectively.



The this Keyword

Sometimes a method will need to refer to the object that invoked it. To allow this, Java defines the this keyword. this can be used inside any method to refer to the current object. That is, this is always a reference to the object on which the method was invoked. You can use this anywhere a reference to an object of the current class’ type is permitted.

To better understand what this refers to, consider the following version of Box( ):

  // A redundant use of this.
  Box(double w, double h, double d) {
    this.width = w;
    this.height = h;
    this.depth = d;
  }

This version of Box( ) operates exactly like the earlier version. The use of this is redundant, but perfectly correct. Inside Box( ), this will always refer to the invoking object. While it is redundant in this case, this is useful in other contexts, one of which is explained in the next section.

Instance Variable Hiding

As you know, it is illegal in Java to declare two local variables with the same name inside the same or enclosing scopes. Interestingly, you can have local variables, including formal parameters to methods, which overlap with the names of the class’ instance variables. However, when a local variable has the same name as an instance variable, the local variable hides the instance variable. This is why width, height, and depth were not used as the names of the parameters to the Box( ) constructor inside the Box class. If they had been, then width would have referred to the formal parameter, hiding the instance variable width. While it is usually easier to simply use different names, there is another way around this situation. Because this lets you refer directly to the object, you can use it to resolve any name space collisions that might occur between instance variables and local variables. For example, here is another version of Box( ), which uses width, height, and depth for parameter names and then uses this to access the instance variables by the same name:

  // Use this to resolve name-space collisions.
  Box(double width, double height, double depth) {
    this.width = width;
    this.height = height;
    this.depth = depth;
  }

A word of caution: The use of this in such a context can sometimes be confusing, and some programmers are careful not to use local variables and formal parameter names that hide instance variables. Of course, other programmers believe the contrary—that it is a good convention to use the same names for clarity, and use this to overcome the instance variable hiding. It is a matter of taste which approach you adopt.

Although this is of no significant value in the examples just shown, it is very useful in certain situations.



Garbage Collection

Since objects are dynamically allocated by using the new operator, you might be wondering how such objects are destroyed and their memory released for later reallocation. In some languages, such as C++, dynamically allocated objects must be manually released by use of a delete operator. Java takes a different approach; it handles deallocation for you automatically. The technique that accomplishes this is called garbage collection. It works like this: when no references to an object exist, that object is assumed to be no longer needed, and the memory occupied by the object can be reclaimed. There is no explicit need to destroy objects as in C++. Garbage collection only occurs sporadically (if at all) during the execution of your program. It will not occur simply because one or more objects exist that are no longer used. Furthermore, different Java run-time implementations will take varying approaches to garbage collection, but for the most part, you should not have to think about it while writing your programs.