Richard G Baldwin (512) 223-4758, baldwin@austin.cc.tx.us, http://www2.austin.cc.tx.us/baldwin/

Objects

Java Programming, Lecture Notes # 36, Revised 10/02/99.

Preface
Introduction
The Stages of an Object's Life
Creating Objects
Using Objects
Cleaning Up Leftover Objects
Review

Preface

Students in Prof. Baldwin's Introductory Java Programming classes at ACC are responsible for knowing and understanding all of the material in this lesson (except that they are not responsible for detailed information that is specific to C++).

The detailed material on C++ is provided as supplementary material for the benefit of those persons who are already familiar with C++ and who are making the transition into Java.

Introduction

Start with a little jargon

As computer programmers, we are beset by the jargon of the profession.

An object is an instance of a class, so we instantiate objects.

An object has state and behavior.

An object's state is contained in its member variables (data members) and its behavior is implemented through its methods (member functions).

Its member variables can be either instance variables or class variables.

We activate the behavior of an object by invoking one of its methods (sending it a message).

When an object receives a message (has one of its methods invoked), it either performs an action, or modifies its state, or both.

The garbage collector

When an object is no longer needed in Java, we simply forget it. Eventually, the garbage collector may (or may not) come by and pick it up for recycling.

The destructor function in C++

When an object is no longer needed in C++, it is destroyed and the memory that it occupies is returned to the operating system for recycling (provided that you have written a correct destructor function).

Object-oriented programming

Perhaps nowhere does jargon run so rampant as in object-oriented programming. Hopefully, we can make it through the jargon and gain an understanding of the underlying concepts.

Jargon from an earlier age

For simplicity, this lesson will put the cart before the horse (jargon from an earlier age). In particular, we will initially assume that class or type definitions exist, and discuss how to use the methods (member functions) and variables (data members) of those class definitions. Later, we will discuss the detailed ramifications of defining classes.

Differences between Java and C++

Much of what we discuss will have close ties to C++. However, a few of the things that we will discuss will not have any tie to C++. For example, the interface is one of the Java concepts that does not exist in C++. Even for many of the concepts that do exist in C++, there will be subtle differences between the two languages.

The stages of an object's life

This lesson discusses the stages of an Object's life consisting of:

Defining classes

A subsequent lesson will discuss the development of classes from which objects can be instantiated.

The Java interface

In a subsequent lesson, we will also discuss the interface, which is a collection of method definitions and constant values.

Packages

Once we have developed a few classes, we will need a way to organize them, and this will lead us into a discussion of the package. Later on, we will discuss how to create and use packages, and also how to use some of the standard packages that come with Java.

The Stages of an Object's Life

What are the stages?

The stages of an object's life consist of

Creating Objects

Again, we are going to assume that we either have access to existing classes, or we know how to create our own (which we will learn how to do later).

What is an object?

An object is an instance of a class.

What steps are involved in creation of an object?

In both Java and C++, the creation of an object involves three steps (which are often combined):

Objects vs. primitive types

When discussing Java, it is important to recognize the difference between objects and variables of primitive types.

The difference between objects and primitive types is not so pronounced in C++ because they are treated more similarly.

Primitive types

In both Java and C++, there are a large number of primitive types. In both languages, the declaration and instantiation of a variable of a primitive type uses a statement such as the following to cause memory to be set aside for the variable and a name to be associated with the variable.

int myVar;

Combining all three steps for primitive types

Also, in both languages, you can initialize the value of the variable when it is declared, thereby accomplishing declaration, instantiation, and initialization all in one statement such as the following.

int myVar = 6;

What does the compiler know?

Perhaps even more important, everything that happens in the above statements regarding primitive variables is known to the compiler at compile time.

.

C++ also allows you to declare and instantiate variables of primitive types or objects from classes at runtime in such a way that the compiler doesn't know at compile time where the variable or object is stored. This is illustrated in the following simple C++ program. This is referred to as instantiating a variable or an object in dynamic memory.

In addition to instantiating and initializing variables and objects in dynamic memory, this program also illustrates static instantiation and initialization of objects and variables, as well as the instantiation of arrays of objects and variables in dynamic memory. Note that when C++ is used to instantiate an array in dynamic memory, initialization is not allowed in conjunction with the instantiation.

/*File dynamic1.cpp  Copyright 1997, R.G.Baldwin
Illustrates static and dynamic instantiation of both
primitive variables and objects.  Also illustrates
instantiation of dynamic arrays of both primitive
variables and objects.

The output from this program is as follows:

myIntVar contains 6
myIntPtr points to 7
myIntArrayPtr points to 8 9 10
myStaticObj contains 11
myObjPtr points to 12
myObjArrayPtr points to 13 14 15

********************************************************/

#include<iostream.h

class dynamic1 {
  int objData; //single instance variable of the class

public:
  dynamic1(){}//default constructor

  //parameterized constructor
  dynamic1(int inData){objData = inData;}

  int getData(){return objData;} //get method

  void putData(int inData){objData = inData;}//put method

  static void classMain(){ //class method named classMain
    //Instantiate and initialize static primitive
    // variable
    int myIntVar = 6;
    cout << "myIntVar contains " << myIntVar << endl;

    //Instantiate and initialize dynamic primitive
    // variable
    int *myIntPtr = new int(7);
    cout << "myIntPtr points to " << *myIntPtr << endl;

    //Instantiate dynamic array of primitive variables.
    // Initialization is not supported for dynamic
    // arrays.
    int *myIntArrayPtr = new int[3];

    //Put some data in the dynamic array.
    for(int i = 0; i < 3; i++) *(myIntPtr+i) = i+8;

    //Display the data in the dynamic array.
    cout << "myIntArrayPtr points to ";
    for(i = 0; i < 3; i++) cout << *(myIntPtr+i)
                                                 << " ";
    cout << endl;

///Now work with objects instead of primitive variables.

    //Instantiate and initialize static object
    dynamic1 myStaticObj(11);
    cout << "myStaticObj contains "
                       << myStaticObj.getData() << endl;

    //Instantiate and initialize dynamic object
    dynamic1 *myObjPtr = new dynamic1(12);
    cout << "myObjPtr points to "
                       << myObjPtr - getData() << endl;

    //Instantiate dynamic array of objects.
    // Initialization of dynamic arrays is not supported.
    dynamic1 *myObjArrayPtr = new dynamic1[3];

    //Put some data in the dynamic array of objects
    for(i = 0; i < 3; i++)
      (myObjArrayPtr+i)-putData(i+13);

    //Display the data in the dynamic array of objects
    cout << "myObjArrayPtr points to ";
    for(i = 0; i < 3; i++)
      cout << (myObjArrayPtr+i)-getData() << " ";

  }//end classMain
};//End dynamic1 class definition.
//=====================================================//

void main()
{
  //call the class method named classMain
  dynamic1::classMain();
}//end main

Static vs. dynamic memory in Java

In Java, all primitive variables must be allocated to static memory at compile time. Java does not allow primitive variables to be instantiated into dynamic memory at runtime. However, there are wrapper classes for primitive types that can be used to turn them into objects for this purpose.

In Java, all objects must be instantiated into dynamic memory at runtime. Java does not allow objects to be instantiated in static memory at compile time.

An array of objects

When C++ instantiates an array of objects in dynamic memory, it actually instantiates an array of objects and returns a pointer to the first object.

.

When Java instantiates "an array of objects" (which is always in dynamic memory), it actually instantiates an array of references to objects.

An additional step is required to create the objects pointed to by the references.

A sample Java application

The next sample program is a Java application similar to the above C++ program but with the restrictions mentioned above.

Displaying the values of the objects and the references to the objects

This program not only displays the data in the objects referred to by the reference variables in the array, it also displays the values contained in the reference variables themselves. These reference values may be used either directly or indirectly (depending on the JVM) to access the objects referred to by the reference variables.

A quick review

To review, in C++, an array of objects instantiated in dynamic memory consists of an actual array of objects and a single pointer is returned which points to the first object in the array. Pointer arithmetic can then be used to access the other objects.

.

An array of objects in Java is instantiated as an array of reference variables where each reference variable can then be used to instantiate an object and have it referred to by the reference variable.

Multiple use of the new operator is required in Java

In Java, multiple usage of the new operator is required to instantiate an "array of objects" because the array doesn't actually contain the objects; it contains references to the objects.

C++ requires only one use of the new operator

Only one usage of the new operator will instantiate a (single dimensional) array of objects in C++.

Instantiation of an array of objects in Java is very similar to the instantiation of an array of pointers in C++, which is often used to point to a group of strings. However, from a syntax viewpoint, the array of references in Java is much easier to use than an array of pointers in C++.

Default display format for a Java reference

Note in the output shown at the beginning of the following program that the display format for the reference is to identify the class and the value contained in the reference as a hexadecimal value. This is the default display format when you include an object reference of a type without an overridden toString() method as a parameter to the println() method.

main() instantiates objects of its own class

You should also note that when the main method in this application instantiates objects dynamically, those objects are of a new type defined by the class of which the main method is a member. In other words, the main() method instantiates objects of the same class of which it is a member.

/*File Dynamic1.java Copyright 1997, R.G.Baldwin
Illustrates allocation of primitive variables to static
memory at compile time.

Also illustrates instantiation of dynamic objects and 
dynamic arrays of objects at runtime.

The output from this program is as follows:

myPrimitiveVariable contains 6
myObjRef contains Dynamic1@1cc748
Object referred to by myObjRef contains 12
myArrayOfRefs contains 
Dynamic1@1cc766 
Dynamic1@1cc767 
Dynamic1@1cc768 

Objects referred to by myArrayOfRefs contain 
13 
14 
15 
********************************************************/

class Dynamic1 {
  //single instance variable of the class of type int
  private int objData; 

  //parameterized constructor
  public Dynamic1(int inData){
    objData = inData;
  }//end constructor

  //get method
  public int getData(){
    return objData;
  }//end getData()

  public static void main(String[] args){//main method
    //Declare and initialize primitive variable of type 
    // int in static memory at compile time. Display it.
    int myPrimitiveVariable = 6;
    System.out.println("myPrimitiveVariable contains " 
                                  + myPrimitiveVariable);

    //Instantiate and initialize object in dynamic memory
    // at runtime.  Display the contents of the reference
    // variable and the contents of the object it refers
    // to.
    Dynamic1 myObjRef = new Dynamic1(12);
    System.out.println("myObjRef contains " + myObjRef);
    System.out.println(
              "Object referred to by myObjRef contains " 
                                 + myObjRef.getData() );

    //Instantiate array of references to objects in 
    // dynamic memory at runtime.
    Dynamic1[] myArrayOfRefs = new Dynamic1[3];

    //Instantiate some objects in dynamic memory and
    // store references to those objects in the array
    // of references.
    for(int i = 0; i < 3; i++)
      myArrayOfRefs[i] = new Dynamic1(i+13);

    //Display the values contained in the array of 
    // references to objects
    System.out.println("myArrayOfRefs contains ");
    for(int i = 0; i < 3; i++)
      System.out.println(myArrayOfRefs[i]);
    System.out.println();

    //Display the values in the objects referenced by
    // the array of references
    System.out.println(
        "Objects referred to by myArrayOfRefs contain ");
    for(int i = 0; i < 3; i++)
      System.out.println(myArrayOfRefs[i].getData());

  }//end Main
}//End Dynamic1 class definition.

Assignment to superclass type reference variable

The next program is a modified version of the previous one that illustrates a very important object-oriented programming concept in Java.

A reference to an object in Java can be assigned to any reference variable whose type is the type of the object, or is a superclass of the type of the object.

Using Object class

In the next program, the array of reference variables is declared to be of type Object (rather than the actual type of the objects to which they will refer).

Recall that Object is the superclass of all classes in Java. Therefore, a reference variable of type Object can refer to an object of any type. Three objects of the Dynamic2 class are instantiated, and references to those objects are stored in the three elements of the array of reference variables.

Displaying the values of the reference variables

Then the values contained in the reference variables are displayed. By observing the values that are displayed (in the comments near the beginning of the program) you can see that the actual type of the objects, Dynamic2, is embedded in the reference values. The actual type of the objects is displayed (as opposed to the type Object, which is the type of the reference variables.)

Downcasting is required

After this, the reference variables are used to display the contents of the objects to which they refer. However, this is where things get a little more complex. The reference variables of type Object can be used in their raw form to access only those members of the object that are defined in the Object class. In order to access members defined in a subclass of Object, you must downcast the reference to that subclass.

In this case, we downcast the reference to type Dynamic2 so that we can access and display the instance variable defined in the class Dynamic2.

Downcast syntax can be complex

The syntax for the downcasting can be a little complex, involving lots of parentheses, so pay particular attention to the syntax as you review this program.

/*File Dynamic2.java Copyright 1997, R.G.Baldwin
Illustrates instantiation of array of references of type
Object and the use of that array to refer to objects
of an inherited class.

The output from this program is as follows:

myArrayOfRefs contains 
Dynamic2@1cc738 
Dynamic2@1cc739 
Dynamic2@1cc73a 

Objects referred to by myArrayOfRefs contain 
13
14
15
********************************************************/

class Dynamic2 {
  //single instance variable of the class of type int
  private int objData; 

  //parameterized constructor
  public Dynamic2(int inData){
    objData = inData;
  }//end constructor

  //get method
  public int getData(){
    return objData;
  }//end getData()

  public static void main(String[] args){//main method
    //Instantiate array of references to objects of type
    // Object in dynamic memory at runtime.
    Object[] myArrayOfRefs = new Object[3];

    //Instantiate some objects of type Dynamic2 in 
    // dynamic memory and store references to those 
    // objects in the array of references of type Object.
    for(int i = 0; i < 3; i++)
      myArrayOfRefs[i] = new Dynamic2(i+13);

    //Display the values contained in the array of 
    // references to objects. Note that the values
    // identify the actual type of object referred to.
    System.out.println("myArrayOfRefs contains ");
    for(int i = 0; i < 3; i++)
      System.out.println(myArrayOfRefs[i]);
    System.out.println();

    //Display the values in the objects referenced by
    // the array of references.  Note the requirement
    // to downcast the references to the actual type
    // of object referred to by the references in
    // order to access the instance variables of the
    // objects.
    System.out.println(
        "Objects referred to by myArrayOfRefs contain ");
    for(int i = 0; i < 3; i++)
      System.out.println(
        ((Dynamic2)(myArrayOfRefs[i])).getData());
  }//end Main
}//End Dynamic2 class definition.

Anonymous objects

In Java, it is not always necessary to declare an object (to give it a name). Objects instantiated without a declaration are called anonymous objects.

When might you use an anonymous object?

Consider, for example a case where a new object is instantiated to be used in an expression and there is no requirement to be able to access that object outside of the expression. In that case, there is no need to declare a reference variable and assign the object to the reference variable.

Instantiating an anonymous object in a method call

This is illustrated in the following program. In this program, a Date object is instantiated and passed to the I/O system for display. The object is not assigned to a reference variable. At least it is not assigned to a reference variable within the code that we wrote.

/*File date1.java Copyright 1997, R.G.Baldwin
Illustrates instantiation of an object without the 
declaration of a name.  A Date object is instantiated 
and passed to the I/O system for display.  The Date 
class is contained in java.util, thus the requirement 
for the import statement.

The output for one particular run was:

Thu Oct 24 16:44:35  1996

********************************************************/
import java.util.*;
class date1 { //define the controlling class
  public static void main(String[] args){ //main method
    System.out.println( new Date() );
  }//end main
}//End date1 class.  

Is the object really anonymous?

One might argue that the object in the above program is actually assigned to a reference variable (which is a method parameter) inside the println() method, and they would probably be correct.

Instantiating an anonymous object within an expression

The following program instantiates an anonymous object and uses it within a local expression without assigning it to a reference variable. In this case, there is no method call involved, so there is no method parameter to assign the anonymous object to.

This program instantiates an anonymous object, initializing it with a value of 10, then immediately retrieves the value of the instance variable and adds that value to 3. The overall expression evaluates to 13, which is displayed on the screen.

/*File Anonymous1.java Copyright 1997, R.G.Baldwin
Illustrates instantiation and use of an anonymous
object in a local expression.

The output from this program is as follows:
13

********************************************************/

class Anonymous1 {
  private int objData; //instance variable

  public Anonymous1(int inData){//constructor
    objData = inData;
  }//end constructor

  public int getData(){//get method
    return objData;
  }//end getData()

public static void main(String[] args){//main method
    System.out.println("" 
                    + (new Anonymous1(10).getData()+3));
  }//end Main
}//End Anonymous1 class definition.

Useful for illustration only

Granted, the previous program isn't too useful, but it does illustrate the concept of anonymous objects.

A more useful anonymous object

The following program uses anonymous objects in a more useful way. In particular, this program uses an anonymous object to gain access to a method that converts integers to hexadecimal representation. In so doing, it displays the integer value of 128 in hexadecimal format.

In order to do this, the program instantiates an anonymous object of type Integer passing a dummy value of zero to the constructor. The objective is to be able to access the toHexString() method of the integer class and to use that method to convert 128 to hex.

/*File Anonymous2.java Copyright 1997, R.G.Baldwin
Illustrates instantiation and use of an anonymous
object to display the integer value 128 in hex.

The output from this program is as follows:
80
80

********************************************************/

class Anonymous2 {

public static void main(String[] args){//main method
    //Note that the value of zero being passed to the
    // Integer class constructor is a dummy value 
    // provided to make it possible to instantiate
    // the object of type Integer.  The object of 
    // type Integer is needed so that its instance
    // method named toHexString() can be invoked.
    System.out.println("" 
                    + new Integer(0).toHexString(128));

    //Access the toHexString() method as a class method
    // without the requirement for an object of the 
    // Integer type.
    System.out.println(Integer.toHexString(128));      
  }//end Main
}//End Anonymous2 class definition.

The truth shall prevail

To be truthful, I will point out that the toHexString() method is a class method that can be accessed without the requirement to instantiate an object, so the anonymous object wasn't really required in this case. But hopefully, you get the picture insofar an anonymous objects are concerned.

A similar approach in C++

The following program illustrates a similar concept in C++ where an object is instantiated and passed as a parameter to a function. In truth, there is a hidden declaration of the object in this program, and it is contained in the argument list of the function. The parameter that is instantiated and passed is then used to initialize the local automatic object in the function.

/*File date1.cpp  Copyright 1997 R.G.Baldwin
Illustrates instantiating an object without an "obvious"
declaration of the object.  An object is instantiated and
passed to a function where it is used to initialize one
of the parameters in the function.

The output from the program is:

The date is 10/24/96

*****************************************************/

#include<iostream.h
#include<string.h

class dateContainer{ //class to instantiate a date object
public:
  char dataArray[20];
  dateContainer(char* inString){
    strcpy(dataArray,inString);
  }
};//end class

class date1 { //simulates Java controlling class
public:
  //Function to receive an object containing a date
  // and display it.
  void displayDate(dateContainer objIn){
    cout << "The date is "<< objIn.dataArray << endl;
  }//end displayDate()

  static void classMain(){
    //object is required to call displayDate()
    date1 thisObject;

    //Put a call to the constructor as a parameter in
    // the function call.
    thisObject.displayDate(dateContainer("10/24/96") );
  }//end classMain
};//End date1 class definition.
//=====================================================//

void main()
{
  //call the class method named classMain
  date1::classMain();
}//end main

Overloaded constructors

We have been using a constructor for the class to initialize a new object in the above Java and C++ programs. Because constructors can be overloaded, new objects can often be initialized in a variety of ways. We will discuss this further when we discuss the various methods of a class.

Using Objects

Member variables and methods may be hidden

In both Java and C++, once we have an object that contains instance variables and methods, we can access those variables and methods to do useful work. There is essentially no difference in the syntax used to access a variable or a method. However, sometimes variables or methods may be hidden so as to make it impossible to access them.

Hide the variables and expose the access methods

It is very common to hide the variables and to expose the methods that can be accessed to serve as a pathway to the variables. We will discuss more about how to do this later.

Joining the member name to the reference variable

In the meantime, assuming that they aren't hidden, variables and methods may be accessed in either Java or C++ by specifying the name of the object (reference) and the name of the variable or method and using a period to join the two.

An Aside: Objects don't have a name in Java

It is important to remember that objects don't have names in Java. Only the reference variables that refer to the objects have names.

For brevity, we often speak of "an object named theObject." What we really mean is a reference variable named theObject that currently refers to a particular object.

Reference variables can refer to different objects

It is also very important to remember that the same reference variable can refer to different objects at different points in time during execution of a Java program.

Now back to the main thought

Use of the joining operator is illustrated in the following code fragments from the above programs. Note that these code fragments are not related in any way. They are simply isolated statements from above.

    System.out.println(
                ((Dynamic2)(myArrayOfRefs[i])).getData());
    System.out.println("" 
                    + (new Anonymous1(10).getData()+3));

The pointer operator in C++

In addition, in C++, if a pointer contains the address of an object, the members of that object can be accessed by joining the name of the pointer to the name of the member using the pointer operator -.

Sending a message to an object

The process that is often referred to as "sending a message to an object", is really nothing more than making a call to a method associated with an object. As in any call to a method, the method may return a value.

Using the new operator in Java

In Java, the new operator, used in conjunction with a constructor, returns a reference to a new object.

Using the new operator in C++

The new operator also returns something in C++. It returns a pointer. In Java, it returns a reference (which is similar to a pointer) but the reference that it returns is very restricted relative to pointers in C++. (In particular, you cannot do pointer arithmetic with Java references and you cannot arbitrarily change the address stored in a Java reference.)

In C++, the new operator can be used in an expression which expects a pointer of a given type.

In Java, the new operator can be used in an expression which expects a reference of a given type.

Cleaning Up Leftover Objects

The C++ Approach to Cleanup
The Java Approach to Cleanup

The process of cleaning up is completely different between Java and C++.

The C++ approach to cleanup

In C++, the programmer is responsible for all cleanup. Of particular importance is the need to return dynamically allocated memory to the operating system. However, in addition to returning memory to the operating system, there may be other important tasks that need to be taken care of such as shutting down communication channels, closing files, etc.

C++ supports the concept of the destructor method for this purpose. The destructor is a special method or member function of a class which always executes automatically whenever an object goes out of scope. You can place any code that is needed for cleanup in the body of the destructor and be confident that it will be executed.

The following C++ program illustrates the automatic action of the destructor. In this program, an object is instantiated in dynamic memory within a code block of limited scope. When control passes beyond the scope of the block, the destructor is executed, returning the memory to the operating system and displaying a message to announce that it is has been invoked.

/*File template.cpp Copyright 1997, R.G.Baldwin
Illustrates use of destructor for cleanup in C++.  Also
illustrates use of a block for scope reduction.

The output from the program is:

In constructor
In showData method. Data value is 5
Leaving scope-reduction block.  Object will be destroyed.
In destructor
No longer in scope-reduction block.
Terminating program

******************************************************/

#include<iostream.h
#include<new.h
#include<stdlib.h

class newClass{ //prog instantiates an obj of this type
  int* ptrToNewData; //use as pointer to dynamic memory
public:
  newClass(int inData){ //constructor
    cout << "In constructor\n";
    //Instantiate and initiate object in dynamic memory
    ptrToNewData = new int(inData);
    if(!ptrToNewData) exit(1);
  }//end constructor

  ~newClass(){//destructor
    cout << "In destructor\n";
    delete ptrToNewData;//return memory to op sytem
  }//end destructor

  void showData(){cout <<
               "In showData method. Data value is "
    << *ptrToNewData << endl;}

};//end newClass

class cleanup1{ //simulates controlling class in Java
public:
  static void classMain(){//simulates main method in Java
    //prepare new operator for null return on failure
    set_new_handler(0);

    { //begin scope-reduction block
    //instantiate and initialize object
    newClass newObject(5);
    newObject.showData();  //display contents of object
    cout << "Leaving scope-reduction block. "
                         "Object will be destroyed.\n";
    } //end scope-reduction block

    cout << "No longer in scope-reduction block."
                             "\nTerminating program\n";
  }//end classMain
};//End cleanup1 class definition.

//===================================================//

void main()
{
  //call the class method named classMain
  cleanup1::classMain();
}//end main

To reiterate, although it is the responsibility of the programmer in C++ to provide the code which returns memory to the operating system, the programmer can be assured that the destructor will always be executed whenever the object goes out of scope, and can include other required cleanup code in the destructor.

.

The Java Approach to Cleanup

Garbage Collection
The finalize Method

The good news

The good news (if things work as planned) is that the Java programmer never needs to worry about returning memory to the operating system. This is taken care of automatically by a feature of Java known as the garbage collector.

The bad news

The bad news is that Java does not support anything like a destructor that is guaranteed to be called whenever the object goes out of scope or is no longer needed. Therefore, other than returning allocated memory, it is the responsibility of the programmer to explicitly perform any other required cleanup at the appropriate point in time.

Other kinds of cleanup could involve closing files, disconnecting from open telephone lines, etc.

Garbage Collection

Purpose of garbage collection

The sole purpose of garbage collection is to reclaim memory occupied by objects that are no longer needed.

Eligibility for garbage collection

An object becomes eligible for garbage collection when there are no more references to that object. You can make an object eligible for garbage collection by setting all references to that object to null, or allowing them to go out of scope.

Eligible garbage may not be collected

However, just because an object is eligible for garbage collection doesn't mean that it will be reclaimed.

The garbage collector runs in a low-priority thread, and presumably is designed to create minimal interference with the other threads of the program. Therefore, the garbage collector may not run unless a memory shortage is detected. And when it does run, it runs asynchronously relative to the other threads in the program.

A formal definition of garbage collection

According to Campione and Walrath, http://java.sun.com/books/Series/Tutorial/java/javaOO/garbagecollection.html

"The Java garbage collector is a mark-sweep garbage collector that scans Java's dynamic memory areas for objects, marking those that are referenced. After all possible paths to objects are investigated, those objects that are not marked (that is, not referenced) are known to be garbage and are collected.

Although you can ask the garbage collector to run by calling System.gc(), Campione and Walrath go on to point out:

"Asking the garbage collection to run does not guarantee that your objects will be garbage collected."

The finalize Method

Before the garbage collector reclaims the memory occupied by an object, it calls the object's finalize() method.

Doing cleanup in the finalize() method

The finalize() method is a member of the Object class. Since all classes inherit from the Object class, your classes also contain the default finalize() method. This gives you an opportunity to execute your special cleanup code on each object before the memory is reclaimed.

Overriding the finalize() method

In order to make use of the finalize() method, you must override it, providing the code that you want to have executed before the memory is reclaimed. (We will discuss overriding methods in detail later.)

finalize() is not a destructor

The finalize() method does not take the place of the destructor from C++. Although you can be confident that the finalize method will be called before the garbage collector reclaims the memory occupied by a particular object, you cannot be certain when, or if that memory will be reclaimed.

To reiterate, the finalize method is not a destructor in the C++ sense. It does not run automatically when your object references go out of scope or are set to null.

You can never be certain when the finalize() method will be executed (except that in JDK 1.1 and later, you can force finalization methods to run on all non-finalized objects at program termination, but these methods were deprecated in JDK 1.2.)

Is the cleanup timing critical?

If you simply need to do cleanup work on an object sometime before the program terminates, (and you have specified finalization on exit) you can depend on your overridden finalize() method being executed sometime before the program terminates.

If you need cleanup work to be performed earlier (such as disconnecting an open long-distance telephone call), you must explicitly call methods to do cleanup at the appropriate point in time and not depend on finalization to get the job done.

Asking for garbage collection

As mentioned earlier, Campione and Walrath indicate that you can ask the garbage collector to run at any time by calling the method

System.gc();

They point out however, that making the request does not guarantee that your objects will be garbage collected.

They also indicate:

"You can force object finalization to occur by calling the System's runFinalization method as follows:

System.runFinalization();

They state: "This method calls the finalize methods on all objects that are waiting to be garbage collected."

Forcing finalization on exit

Fortunately, the following very important method was added to JDK 1.1.

runFinalizersOnExit(boolean)

With this method, you can guarantee that finalization will occur before the program exits. This enables you to guarantee that any necessary cleanup will be accomplished before program termination by overriding the finalize() method for your class to perform that cleanup.

To guarantee finalization on exit, call this method passing true as an argument somewhere early in your program.

runFinalizersOnExit() has been deprecated

Of the three methods involving finalization and garbage collection, the only one that I have found to be reliable is runFinalizersOnExit(). Unfortunately, it was deprecated in JDK 1.2 with the following explanation

"Deprecated. This method is inherently unsafe. It may result in finalizers being called on live objects while other threads are concurrently manipulating those objects, resulting in erratic behavior or deadlock."

System.runFinalization() and System.gc()

System.runFinalization() and System.gc() don't seem to behave as I would hope they would. However, the JavaSoft documentation points out that these two methods simply "suggest" that the JVM "expend its best effort" to do what you have requested (finalization or garbage collection).

An excellent discussion of this topic can be found in Bruce Eckel's online book, Thinking in Java, http://www.eckelobjects.com.

Review

Q - In Object-Oriented Programming, an object is often said to be an ____________ of a class. Provide the missing word.

A - An object is often said to be an instance of a class.

Q - In Object-Oriented Programming, an object is often said to have s_______ and b_______. Provide the missing words which begin with the letters shown.

A - In OOP, an object is often said to have state and behavior.

Q - An object's state is contained in its ________ and its behavior is implemented through its ________. Provide the missing words.

A - An object's state is contained in its member variables ( or data members) and its behavior is implemented through its methods ( or member functions).

Q - The member variables of an object can be either ____________ or _________ . Provide the missing words.

A - Its member variables can be either instance variables or class variables.

Q - What is generally meant by the terminology "sending a message to an object?"

A - We activate the behavior of an object by invoking one of its methods (sending it a message).

Q - What are the two things that can usually happen when an object receives a message?

A - When an object receives a message, it usually either performs an action, or modifies its state, or both.

Q - What happens to the memory occupied by an object in Java when the object is no longer needed, and what do we normally do to make that happen?

A - When an object is no longer needed in Java, we simply forget it. Eventually, the garbage collector may (or may not) come by and pick it up for recycling.

Q - What does Baldwin's Java tutorial identify as the stages of an object's life?

A - The stages of an Object's life are:

Q - According to Baldwin's Java tutorial, the creation of an object involves three steps (which are often combined). What are the three steps?

A - The three steps are:

Q - Java allows the instantiation of variables of primitive types in dynamic memory: True or False? If false, explain why and what you might be able to do to achieve almost the same result.

A - False. Java does not allow the instantiation of primitive variables in dynamic memory. (However, there are wrapper classes for primitive types which can be used to turn them into objects for this purpose.)

Q - An array of objects in Java is instantiated as an array of reference variables where each reference variable can then be used to instantiate an object pointed to by the reference variable: True or False. If false, explain why and either provide a code fragment that illustrates your answer, or refer to a sample program in Baldwin's Java tutorial that illustrates your answer.

A - True.

Q - In Java, it is always necessary to declare (give a name to) all new objects: True or False? If false, explain why and either provide a code fragment that illustrates your answer, or refer to a sample program in Baldwin's Java tutorial that illustrates your answer.

A - It is not always necessary in Java to declare an object (to give it a name). Consider, for example a case where a new object is instantiated to be used in an expression and there is no requirement to be able to access that object outside of the expression. This is illustrated in the program named date1.java in the lesson entitled Java036.htm.

Q - Instance variables and instance methods can be accessed using an object as the access mechanism. What is the difference in the syntax used to access an instance variable and an instance method.

A - None. There is essentially no difference in the syntax used to access a variable or a method.

Q - Once you have instantiated an object, it is always possible to access all of the instance variables and instance methods of that object by joining the name of the object to the name of the variable or method using a period: True or False? If false, explain why.

A - False. Sometimes variables or methods may be hidden so as to make it impossible to access them. It is very common to hide the variables and to provide methods which can be accessed to serve as a pathway to the variables.

Q - Given an object named obj that has a public instance method named myMethod(), provide a code fragment that shows the proper syntax for accessing the method.

A - The proper syntax for accessing an instance method named myMethod() follows:

obj.myMethod()

Q - The object-oriented approach normally recommends hiding instance variables behind access methods: True or False? If false, explain why.

A - True. The object-oriented approach normally recommends hiding instance variables behind access methods.

Q - The object-oriented approach normally recommends hiding instance variables behind access methods: True or False? If true, explain why.

A - True. The object-oriented approach normally recommends hiding instance variables behind access methods. There are a variety of reasons why. One important reason is that hiding the instance variables makes it possible to later modify the implementation of instance variables to improve the behavior of objects of the class, without a requirement for modifying code that uses the clsss, provided that the access methods are not modified.

Q - The returning of memory (to the operating system) occupied by objects that are no longer needed is automatically accomplished in Java by a feature commonly known as the ____________________. Fill in the missing word(s).

A - The returning of memory to the operating system is taken care of automatically by a feature of Java known as the garbage collector.

Q - All necessary cleanup in a Java program is performed automatically by the garbage collector: True or False? If false, explain why.

A - False. Java does not support anything like a destructor that is guaranteed to be called whenever the object is no longer needed. Therefore, other than returning allocated memory, it is the responsibility of the programmer to explicitly perform any other required cleanup at the appropriate point in time.

Q - When does an object become eligible for garbage collection?

A - An object becomes eligible for garbage collection when there are no more references to that object.

Q - What can your program do to purposely make an object eligible for garbage collection.

A - Your program can make an object eligible for garbage collection by assigning null to all references to the object.

Q - The purpose of garbage collection in Java is to perform all necessary cleanup and the memory occupied by objects that are no longer needed will always be reclaimed by the garbage collector: True or False? If false, explain why.

A - False. The sole purpose of garbage collection is to reclaim memory occupied by objects that are no longer needed, and it has no other purpose relative to necessary cleanup. An object becomes eligible for garbage collection when there are no more references to that object. However, just because an object is eligible for garbage collection doesn't mean that it will be reclaimed. The garbage collector runs in a low-priority thread, and may not run at all unless a memory shortage is detected.

Q - Before the garbage collector reclaims the memory occupied by an object, it always calls the object's _________ method. (Provide the name of the method.) Explain why this method is one of the methods in all new classes that you define.

A - Before the garbage collector reclaims the memory occupied by an object, it calls the object's finalizemethod. The finalize method is a member of the Object class. Since all classes inherit from the Object class, your classes also contain the default finalize method.

Q - What must you do to make effective use of the finalize method? Explain why you might want to do this.

A - In order to make use of the finalize method, you must override it, providing the code that you want to have executed before the memory is reclaimed.

Q - You can always be confident that the finalize method will be promptly executed to perform necessary cleanup in a Java program when an object becomes eligible for garbage collection: True or False? If false, explain why.

A - False. Although you can be confident that the finalize method will be called before the garbage collector reclaims the memory occupied by a particular object, you cannot be certain when, or if that memory will be reclaimed. There is no guarantee that the memory will be reclaimed by the garbage collector during the execution of your program.

Q - Provide a code fragment illustrating the method call that you can make to ask the garbage collector to run. This guarantees that garbage collection will take place: True or False? If false, explain why.

A - False. Campione and Walrath indicate that you can ask the garbage collector to run at any time by calling the method shown below. They further point out, however, that making the request does not guarantee that your objects will be reclaimed through garbage collection.

System.gc();

Q - Write a Java program that meets the following specifications.

/*File SampProg12.java from lesson 36
Copyright 1997, R.G.Baldwin
Without reviewing the solution that follows, write a Java
application that illustrates static instantiation of
primitive variables and also illustrates instantiation 
of dynamic objects and dynamic arrays of objects. In 
this case, the word static is not being used in the 
sense of the static keyword that produces class 
variables and class methods.  It is being used in the 
sense of "not dynamic", meaning that the memory is
allocated at compile time and not at runtime.

Display the value contained in each reference variable 
as well as the value stored in the object pointed to 
by the reference variable.

Provide a termination message with your name.
**********************************************************/

class SampProg12 {
  //single instance variable of the class
  private int objData;

  //constructor
  public SampProg12(int inData){objData = inData;}

  public int getData(){return objData;} //get method

  public static void main(String[] args){
    //Instantiate and initialize static primitive variable
    int myIntVar = 6;
    System.out.println("myIntVar contains " + myIntVar);

    //Instantiate and initialize dynamic object
    SampProg12 myObjPtr = new SampProg12(12);
    System.out.println("myObjPtr contains " + myObjPtr);
    System.out.println(
      "myObjPtr points to object containing " 
      + myObjPtr.getData() );

    //Instantiate dynamic array of objects.
    SampProg12[] myObjArrayPtr = new SampProg12[3];

    //Put some data in the dynamic array of objects
    for(int i = 0; i < 3; i++)
      myObjArrayPtr[i] = new SampProg12(i+13);

    //Display the addresses in the array of pointers 
    // to objects
    System.out.print("myObjArrayPtr contains ");
    for(int i = 0; i < 3; i++)
      System.out.print(myObjArrayPtr[i] + " ");
    System.out.println();

    //Display the data pointed-to by the array of pointers
    System.out.print(
      "myObjArrayPtr points objects containing ");
    for(int i = 0; i < 3; i++)
      System.out.print(myObjArrayPtr[i].getData() + " ");
    System.out.println();

    System.out.println("Terminating, Dick Baldwin");
  }//end Main
}//End SampProg12 class definition.

Q - Write a Java program that meets the following specifications.

/*File SampProg13.java from lesson 36
Copyright 1997, R.G.Baldwin
Without reviewing the following solution, write a Java
application that illustrates the instantiation and use
of an anonymous object.

Also provide a termination message with your name.
**********************************************************/

class SampProg13{
  public static void main(String[] args){
    System.out.println("" 
      + ((new Integer(5).intValue()) * 3));
    System.out.println("Terminating, Dick Baldwin");
  }//end main
  
}//end class SampProg13

-end-