Return to index

Java Basics 6
Creating and Using Objects

 

Topics covered include :

  • Object Life Cycle
  • Declaring a Variable to Refer to an Object
  • Instantiation
  • Initialisation
  • Variable Access
  • Calling an Object's methods
  • The Garbage Collector and Finalisation

 

The Life Cycle of an Object

Here, you will learn how to create and use objects of any type. This section also discusses how the system cleans up an object when it is no longer needed.

A typical Java program creates many objects, which interact with one another by sending each other messages. Through these object interactions, a Java program can implement a GUI, run an animation, or send and receive information over a network. Once an object has completed the work for which it was created, it is garbage-collected and its resources are recycled for use by other objects.

Here's a small program, called CreateObjectDemo, that creates three objects: one Point object and two Rectangle objects. You will need all three source files to compile this program. They are available as a TJI project on our web site within the 'Resources' page.

public class CreateObjectDemo {
    public static void main(String[] args) {
        // create a point object and two rectangle objects
        Point origin_one = new Point(23, 94);
        Rectangle rect_one = new Rectangle(origin_one, 100, 200);
        Rectangle rect_two = new Rectangle(50, 100);

 

        // display rect_one's width, height and area
        System.out.println("Width of rect_one: " + rect_one.width);
        System.out.println("Height of rect_one: " + rect_one.height);
        System.out.println("Area of rect_one: " + rect_one.area());

 

        // set rect_two's position
        rect_two.origin = origin_one;
        // display rect_two's position
        System.out.println("X Position of rect_two: " + rect_two.origin.x);
        System.out.println("Y Position of rect_two: " + rect_two.origin.y);

 

        // move rect_two and display its new position
        rect_two.move(40, 72);
        System.out.println("X Position of rect_two: " + rect_two.origin.x);
        System.out.println("Y Position of rect_two: " + rect_two.origin.y);
    }
} 

After creating the objects, the program manipulates the objects and displays some information about them. Here's the output from the program:

Width of rect_one: 100
Height of rect_one: 200
Area of rect_one: 20000
X Position of rect_two: 23
Y Position of rect_two: 94
X Position of rect_two: 40
Y Position of rect_two: 72 

This section uses this example to describe the life cycle of an object within a program. From this, you can learn how to write code that creates and uses an object and how the system cleans it up.

 

Creating Objects

As you know, a class provides the blueprint for objects; you create an object (instance) from a class. Each of the following statements taken from the CreateObjectDemo program creates an object:

Point origin_one = new Point(23, 94);
Rectangle rect_one = new Rectangle(origin_one, 100, 200);
Rectangle rect_two = new Rectangle(50, 100); 

The first line creates an object of the Point class and the second and third lines each create an object of the Rectangle class.

Each of these statements has three parts:

  1. Declaration: The code set in purple in the previous listing are all variable declarations that associate a name with a type. When you create an object, you do not have to declare a variable to refer to it. However, a variable declaration often appears on the same line as the code to create an object.
  2. Instantiation: new is a Java operator that creates the new object (allocates memory space for it).
  3. Initialization: The new operator is followed by a call to a constructor. For example, Point(23, 94) is a call to Point's only constructor. The constructor initializes the new object.

The next three subsections discuss each of these actions in detail.

 

Declaring a Variable to Refer to an Object

From the previous section entitled 'Variables and Operators', you learned that to declare a variable, you write:

type name 

This notifies the compiler that you will use 'name' to refer to data whose type is 'type'.

In addition to the primitive types, such as int and boolean, provided directly by the Java platform, classes and interfaces are also types. So to declare a variable to refer to an object, you can use the name of a class or interface as a variable's type. The sample program uses both the Point and the Rectangle classes as types to declare variables.

Point origin_one = new Point(23, 94);
Rectangle rect_one = new Rectangle(origin_one, 100, 200);
Rectangle rect_two = new Rectangle(50, 100); 

Remember that declarations do not create new objects. The code

Point origin_one;

does not create a new Point object; it just declares a variable, named origin_one, that will be used to refer to a Point object. The reference is empty until assigned, as illustrated in the next figure. An empty reference is known as a 'null reference'.

To create an object you must instantiate it with the new operator. Note, however, that some classes require you to call a 'factory' method which returns a new object of that class.

 

Instantiating an Object

The new operator instantiates a class by allocating memory for a new object. The new operator requires a single, postfix argument: a call to a constructor. The name of the constructor provides the name of the class to instantiate. The constructor initializes the new object.

The new operator returns a reference to the object it created. Most often, this reference is assigned to a variable of the appropriate type. If the reference is not assigned to a variable, the object is unreachable after the statement in which the new operator appears finishes executing.

It is not always necessary to keep a direct object reference. For example, we may not need to refer to it again once it has been put to use, or it can be retrieved from the object which it is passed to. For example:

Vector v=new Vector();

v.addElement(new Integer(99));

We have kept no direct variable reference to the new Integer object, but because it is stored in Vector v (a reference to the Integer object is stored in Vector v), we can retrieve it from there:

Integer myInteger = (Integer)v.elementAt(0);

We can now assign the variable myInteger as an object reference to the Integer object retrieved from Vector v. We need to perform a 'class cast' to type Integer (we know that the object at index 0 is an Integer type) because method elementAt(int index) in class Vector returns an Object type so that any subclass of class Object (i.e. any class) can be stored in a Vector.

 

Initializing an Object

Here's the source code for the Point class:

public class Point {
    public int x = 0;
    public int y = 0;

 

    // Constructor :
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
} 

This class contains a single constructor. You can recognize a constructor because it has the same name as the class and has no return type. The constructor in the Point class takes two integer arguments, as declared in the source code (int x, int y). The following statement provides 23 and 94 as values for those arguments:

Point origin_one = new Point(23, 94); 

The effect of the previous line of code is illustrated in the next figure:

Here's the code for the Rectangle class, which contains four constructors:

public class Rectangle {
      public int width = 0;
      public int height = 0;
      public Point origin;

 

      // Four constructors
      public Rectangle() {
            origin = new Point(0, 0);
      }

 

      public Rectangle(Point p) {
            origin = p;
      }

 

      public Rectangle(int w, int h) {
            this(new Point(0, 0), w, h);
      }

 

      public Rectangle(Point p, int w, int h) {
            origin = p;
            width = w;
            height = h;
      }

 

      // A method for moving the rectangle
      public void move(int x, int y) {
            origin.x = x;
            origin.y = y;
      }

 

      // A method for computing the area of the rectangle
      public int area() {
            return width * height;
      }
} 

Each constructor lets you provide initial values for different aspects of the rectangle: the origin; the width, and the height; all three; or none. If a class has multiple constructors, they all have the same name but a different number of arguments or different typed arguments. The Java platform differentiates the constructors, based on the number and the type of the arguments. When the Java platform encounters the following code, it knows to call the constructor in the Rectangle class that requires a Point argument followed by two integer (int) arguments:

  Rectangle rect_one = new Rectangle(origin_one, 100, 200); 

This call initializes the Rectangle object's origin field to the Point object referred to by variable origin_one. The code also sets field width to 100 and field height to 200. Now there are two references to the same Point object. An object can have multiple references to it, as shown in the next figure:

Multiple references can refer to the same object. The following line of code calls the constructor that requires two integer arguments, which provide the initial values for width and height. If you inspect the code within the constructor, you will see that it also creates a new Point object whose x and y values are initialized to 0:

Rectangle rect_two = new Rectangle(50, 100); 

The Rectangle constructor used in the following statement doesn't take any arguments, so it's called a 'no-argument constructor':

Rectangle rect = new Rectangle(); 

If a class does not explicitly declare any constructors, the Java platform automatically provides a no-argument constructor, called the 'default constructor', that does nothing. Thus, all classes have at least one constructor.

Class Rectangle's 7 Constructors

Rectangle()
Constructs a new Rectangle whose top-left corner is at (0, 0) in the coordinate space, and whose width and height are both zero.

Rectangle(Dimension d)
Constructs a new Rectangle whose top left corner is (0, 0) and whose width and height are specified by the Dimension argument.

Rectangle(int width, int height)
Constructs a new Rectangle whose top-left corner is at (0, 0) in the coordinate space, and whose width and height are specified by the arguments of the same name.

Rectangle(int x, int y, int width, int height)
Constructs a new Rectangle whose top-left corner is specified as (x, y) and whose width and height are specified by the arguments of the same name.

Rectangle(Point p)
Constructs a new Rectangle whose top-left corner is the specified Point, and whose width and height are both zero.

Rectangle(Point p, Dimension d)
Constructs a new Rectangle whose top-left corner is specified by the Point argument, and whose width and height are specified by the Dimension argument.

Rectangle(Rectangle r)
Constructs a new Rectangle, initialized to match the values of the specificed Rectangle.

 

Using Objects

 

Once you've created an object, you probably want to use it for something. You may need information from it, want to change its state, or have it perform some action.

Objects give you two ways to do these things:

  1. Manipulate or inspect its variables.
  2. Call its methods.

 

Referencing an Object's Variables

The following is the general form of a 'qualified name', which is also known as a 'long name':

objectReference.variableName 

You may use a simple name for an instance variable when the instance variable is in scope - that is, within the code for the object's class. Code that is outside the object's class must use a qualified name.

For example, the code in the CreateObjectDemo class is outside the code for the Rectangle class. So to refer to the origin, width, and height fields within the Rectangle object named rect_one, the CreateObjectDemo class must use the names rect_one.origin, rect_one.width, and rect_one.height, respectively. The program uses two of these names to display the width and the height of rect_one:

System.out.println("Width of rect_one: " + rect_one.width);
System.out.println("Height of rect_one: " + rect_one.height); 

Attempting to use the simple names width and height within the CreateObjectDemo class doesn't make sense - those variables exist only within another class - and would result in a compiler error.

Later, the program uses similar code to display information about rect_two.

Objects of the same type have their own copy of the same instance variables. Thus, each Rectangle object has instance variables named origin, width, and height. When you access an instance variable through an object reference, you reference that particular object's variable. The two objects rect_one and rect_two in the CreateObjectDemo program have different origin, width, and height variables.

The first part of the variable's qualified name, the objectReference, must be a reference to an object. You can use the name of a reference variable here, as in the previous examples, or you can use any expression that returns an object reference. Recall that the new operator returns a reference to an object. So you could use the value returned from new to access a new object's variables:

int height = new Rectangle().height;
// or, more clearly :
int height = (new Rectangle()).height; 

This statement creates a new Rectangle object and immediately gets its height. In essence, the statement returns the default height of a Rectangle. Note that after this statement has been executed, the program no longer has a reference to the created Rectangle, because the program never stored the reference in a variable. The object is unreferenced, and its resources can be recycled by the Java VM.

 

Variable Access

The direct manipulation of an object's variables by other objects is discouraged because it is possible to set the variables to values that don't make sense. For example, consider the Rectangle class from the previous section. Using that class, you can create a rectangle whose width and height are negative, which, for some applications, doesn't make sense.

Ideally, instead of allowing direct manipulation of variables, a class would provide methods through which other objects can inspect or change variables. These methods can ensure that the values of the variables make sense for objects of that type. Thus, the Rectangle class would provide methods called setWidth(int w), setHeight(int h), int getWidth(), and int getHeight() for setting and getting the width and the height. The methods for setting the variables could report an error if the caller tried to set the width or the height to a negative number.

Another advantage of using access methods instead of direct variable access is that the class can change the type and the names of the variables it uses for storing the width and the height without affecting its clients.

However, in practical situations, it sometimes makes sense to allow direct access to an object's variables. For example, both the Point class and the Rectangle class allow free access to their member variables by declaring them public. This keeps these classes small and simple. Also, it keeps them generally useful. Some applications might allow rectangles with negative widths and heights.

The Java programming language provides an access control mechanism whereby classes can determine what other classes can have direct access to its variables. A class should protect variables against direct manipulation by other objects if those manipulations could result in values that don't make sense for objects of that type. Changes to these variables should be controlled by method calls.

If a class grants access to its variables, you can generally assume that you can inspect and change those variables without adverse effects. Also, by making the variables accessible, they become part of the class's API, which means that the writer of the class should not change their names or their types after the API is published.

 

Calling an Object's Methods

You can also use qualified names to call an object's methods. To form the qualified name of a method, you append the method name to an object reference, with an intervening period (.). Also, you provide, within enclosing parentheses, any arguments to the method. If the method does not require any arguments, use empty parentheses.

objectReference.methodName(argumentList);
   // or, if no arguments are required :
objectReference.methodName(); 

The Rectangle class has two methods: area() to compute the rectangle's area and move(int x, int y) to change the rectangle's origin. Here is the CreateObjectDemo code that calls these two methods:

System.out.println("Area of rect_one: " + rect_one.area());
...
rect_two.move(40, 72); 

The first statement calls rect_one's area() method and displays the result. The second line moves rect_two because the move(int x, int y) method assigns new values to the object's origin.x and origin.y.

As with instance variables, the objectReference must be a reference to an object. You can use a variable name, but you also can use any expression that returns an object reference. The new operator returns an object reference, so you can use the value returned from new to call the new object's methods:

(new Rectangle(100, 50)).area() 

The expression new Rectangle(100, 50) returns an object reference that refers to a Rectangle object. As shown, you can use the dot notation to call the new rectangle's area() method to compute the area of the new rectangle.

Some methods, such as area(), return a value. For methods that return a value, you can use the method call in expressions. You can assign the return value to a variable, use it to make decisions, or control a loop. The following code assigns the value returned by method area() to a variable:

int areaOfRectangle = (new Rectangle(100, 50)).area(); 

A method return can be used just like a variable of the same type in conditions. For example:

Rectangle r=new Rectangle();

r.height=10;

while (r.area()<200) {

r.width = r.width + 1;

}

Remember - invoking a method on a particular object is the same as sending a message to that object. In this case, the object that method area() is invoked on is the Rectangle object returned by the constructor.

 

A Note about Method Access

The methods in our Point and Rectangle classes are all declared public, so they are accessible to any other class. Sometimes, a class needs to restrict access to its methods. For example, a class might have a method that only subclasses are allowed to call. A class can use the same mechanism to control access to its methods as it uses to control access to its variables.

The access modifier 'protected' restricts access to classes in the same package. The access modifier 'private' restricts access to within the class only - no other class has access.

 

Cleaning Up Unused Objects

Some object-oriented languages require that you keep track of all the objects you create and that you explicitly destroy them when they are no longer needed. Managing memory explicitly is tedious and error prone. The Java platform allows you to create as many objects as you want (limited, of course, by what your system can handle), and you don't have to worry about destroying them. The Java runtime environment deletes objects when it determines that they are no longer being used. This process is called 'garbage collection'.

An object is eligible for garbage collection when there are no more references to that object. References that are held by a variable are usually dropped when the variable goes out of scope. Remember that there may be multiple references to the same object; all references to an object must be dropped before the object is eligible for garbage collection.

You can explicitly drop an object reference by setting the variable to the special value null.

 

The Garbage Collector

The Java runtime environment has a garbage collector that periodically frees the memory used by objects that are no longer referenced. The garbage collector does its job automatically, although, in some situations, you may want to run the garbage collection explicitly by calling the gc() method in the System class. For instance, you might want to run the garbage collector after a section of code that creates a large amount of garbage or before a section of code that needs a lot of memory.

 

Finalization

Before an object gets garbage-collected, the garbage collector gives the object an opportunity to clean up after itself through a call to the object's finalize() method. This process is known as 'finalization'.

Most programmers don't have to worry about implementing the finalize() method. In rare cases, however, a programmer might have to implement a finalize() method to release resources, such as native peers, that aren't under the control of the garbage collector.

The finalize() method is a member of the Object class, which is at the top of the Java platform's class hierarchy and the ultimate superclass of all other classes. A class can override the finalize() method to perform any finalization necessary for objects of that type. If you do override finalize(), your implementation of the method should call super.finalize() as the last thing it does.

 

Download and run ...

You can download the source code for the example program CreateObjectDemo as a TJI project from the 'Resources' page of our website.

 

NOTE : This document was based on parts of the excellent book 'The Java Tutorial' by Mary Campione, Kathy Walrath and Alison Huml; also on-line at Sun's Java site.


Return to index