TOC PREV NEXT INDEX

Put your logo here!

The Essence of Object Oriented Programming with Java and UML


Chapter 3

Objects in Java

 

In Chapter 2, we covered the essence of object-oriented programming. The terms we used there really apply to any object-oriented programming language: C++, Smalltalk, and, of course, Java. In this chapter, we will focus on Java, and how Java supports object-oriented programming.

This chapter is not an introduction to Java. We assume you've already had some introduction to programming in Java. You have probably even used some basic object-oriented properties of Java, such as defining objects and using inheritance. But, the approach of this chapter will be to take a concept from object-oriented programming, such as a class, and describe how Java supports that concept. It will not be the usual "Here's the next language feature of Java" approach.

Since you are probably already somewhat familiar with Java, some of the material covered in this chapter may seem very basic. Even though you may already know some of these Java features, you may still find it useful to review it from the object-oriented perspective.

To keep the focus on objects, most of the examples will be first shown as a UML object diagram, and then mapped to the equivalent Java code. As we said earlier, just because a program is written in Java does not mean it is object-oriented. The goal is to keep your thinking object-oriented, not Java-oriented. Starting with UML objects will help keep the focus on objects1.

Defining Classes in Java

The class is the fundamental unit of Java programs. Any Java program will be made up of a collection of class definitions. Each Java class is defined in a single file with the exact name of the class, but with a .java file extension2.

For many of the examples in this chapter, we will assume we are working with a program that will draw and manipulate various geometric shapes. In some cases, for clarity we will give names to our classes that conflict with names of classes that are part of the standard Java class library (such as Point and Color). In any event, these examples are designed to illustrate how to use Java to implement classes and class relationships, and would likely be somewhat different in a real world application.

Our model of shapes will assume that each shape has an x,y origin, expressed as a floating point value. The shape would eventually be drawn on the screen at some physical location that represents the shape's real origin. Because each shape will need to specify at least one x,y coordinate, we will start by defining a Point class. A general Point class will likely be useful for other aspects of drawing shapes.

The following UML class diagram shows a possible implementation of a Point class:

Figure 3-1.
UML Diagram of Point class

The following is the equivalent Java code for the Point class, and would be saved in a file called Point.java.

Listing 3-1. Point.java
Point Class
 /**
  * Point - a double x,y coordinate
  */
 public class Point         
 {
     // Attributes
  
     private double myX;
     private double myY;
  
     // Constructors
         
     public Point(double x, double y)
     {
        myX = x; myY = y;
     }
  
     public Point()
     {
        myX = 0.; myY = 0.;
     }
  
     // Methods
     
     public double getX()
     {
         return myX;
     }
  
     public double getY()
     {
         return myY;
     }
  
     public void setPoint(double x, double y)
     {
         myX = x; myY = y;
     }
 }

Notice the exact mapping from the UML class diagram to the equivalent Java code. With good UML design tools such as those discussed in a later chapter, it is possible to have much of the code associated with your program generated automatically. Initially, a design tool would generate the code that represents all the attributes and methods provided by your class. You would fill in the lines of code that actually implements the actions supported by the class methods. As you work with and modify your class, a good UML design tool will automatically incorporate the changes you make into the UML class diagrams.

In this example, the Point class is defined as public class Point. The first part of the class definition includes the private attributes of the class. The public methods associated with the class (getX, getY, setPoint, etc.) are defined next. While Point is a simple class, it still hides the real internal implementation of x and y from the outside world, and provides public setters and getters. This class is so simple that you might be tempted to rename myX and myY to x and y and make them publicly available. In fact, you will find many classes in the standard Java library that do give direct access to the attributes. However, the standard Java libraries represent a compromise between practical and efficiency considerations, and truly good object-oriented design. Thus, the standard Java libraries are unfortunately not always great examples of object-oriented software design and should not necessarily be taken as examples of good OO programming.

In general, it is hardly ever a good idea to allow direct access to the attributes of a class. Even providing getters and setters can expose implementation details that might better be left hidden. In practice, however, especially for simple objects, it is sometimes acceptable to expose the underlying model. In this case, we've chosen to expose that we are keeping the x and y of a point as doubles. Using setters and getters helps protect the integrity of the model. For example, we could easily add validity checks for a range of valid points if necessary, and the programs using the Point class would not need to change.

If we wanted to change our representation of a Point from double to int values, for example, we would have some difficulties. We would need to change any places where doubles are used to ints. Depending on the stage of the development, even small changes like that can still affect many lines of code. Fortunately, the strong type checking of the Java compiler would flag places where doubles need to be ints.

Here is a slightly more complicated example. It is a Circle class that uses the Point class we just defined.

Figure 3-2.
UML diagram of Circle class
Listing 3-2. Circle.java
First version of Circle
 /**
  * Circle.java - a simple Circle class
  */
 public class Circle   
 {
     private int red;      // color of circle
     private int green;
     private int blue;
     private Point origin; // origin of circle
     private double radius; // radius
  
     public Circle(final Point org, final double rad)
     {
         red = 0; green = 0; blue = 0;  // black
         origin = new Point(org.getX(), org.getY());
         radius = rad;
     }
  
     public int getB()
     {
         return blue;
     }
  
     public int getG()
     {
         return green;
     }
  
     public int getR()
     {
         return red;
     }
  
     public void setRGB(int r, int g, int b)
     {
        // Simplification: doesn't check for valid values
        red = r; green = g; blue = b;
     }
  
     public double getRadius()
     {
         return radius;
     }
  
     public void setRadius(double r)
     {
         radius = r;
     }
  
     public Point getOrigin()
     {
         return origin;
     }
     
     public void setOrigin(Point org)
     {
         origin.setPoint(org.getX(), org.getY());
     }
 }

Even though this is a simple definition of a Circle class, it has some problems. Because we already had a Point class, we used a Point to define the Circle's origin. However, we represented the color of the Circle directly. Our design should undoubtedly define a Color class, too. And if we were designing a program to represent different shapes, it would make sense to define a Shape class for items that are common to all shapes (such as origin and color), and derive specific shapes like Circle or Rectangle from that Shape class. We will do that later in this chapter.

Note that a Point is merely a simple attribute of a Circle. A Point is really similar to a fundamental type such as a double or an int. There is no higher level association relationship between a Circle and Point. We will examine higher level associations later.

Visibility

When viewed in the overall context of object-oriented programming and encapsulation, it is easier to understand all the visibility features of Java. For a class to be useful, it must provide some services to be used by objects of other classes, while keeping the internal details hidden.

Java uses the public specifier to define which attributes and methods are available to the entire outside world. Any variable or method in the top level of a class definition with a public specifier will be available for use by any other class. Note the various public methods provided by the definition of the Point example: getX, getY, setPoint, and the Point constructor. It is best to give public access only to class methods, and not class attributes.

The remainder, non-public parts of a class must be hidden from the outside world. This includes both attributes (remember that attribute is the object-oriented term for a data structure, and other variables used in a class definition), and any methods needed to support the internal class implementation. Java provides three ways to control the non-public visibility of attributes and methods.

Java naming conventions

Choosing names for Java classes, variables, and methods is not a random process. Since its inception, there has been a set of standards for choosing Java names. These standards were originally proposed by Sun, and have been widely adopted by the Java programming community. You should follow the generally accepted guidelines for naming Java items in your own programs. The conventions presented here are adapted directly from Sun's own standards, posted on Sun's Java web pages.

Packages

The prefix of a unique package name is always written in all-lowercase ASCII letters and should be one of the top-level domain names, such as .com, .edu, or one of other official top-level names.

Subsequent components of the package name vary according to an organization's own internal naming conventions. Use dots to separate the parts.

Examples: com.sun.eng, com.objectcentral.javatools.

Classes

Class (and interface) names should be nouns descriptive of the purpose of the class. Names are in mixed case, beginning with a capital and with the first letter of each internal word capitalized. Use complete words, and avoid abbreviations.

Examples: Point, Shape, MovieEditor, ClientList.

Methods

Methods should be verbs descriptive of the purpose of the method. Method names are in mixed case, with the first letter lowercase, and the first letter of each internal word capitalized. There are prefix conventions for general types of methods, such as using get and set for getters and setters.

Examples: getOrigin, findSmallest, drawGraph, saveModel.

Variables

Except when used as constants, all variables are named using mixed case with a lowercase first letter, and internal words starting with capital letters. Variable names should be meaningful enough to convey their use to someone reading the code. Avoid abbreviations. Use one letter variable names for only for temporary variables. Using meaningful variable names is one of the most important things you can do to make your code easy to read and maintain.

Examples: myMovie, editedMovie, backgroundColor, lastItem.

Constants

The names of variables used as constants should be all uppercase with words separated by underscores ("_")

Examples: MAX_SIZE, R_PG13, TERM_LIMIT.

For a simple class attributes and operations that are used only by the class itself, and won't be used by any derived subclasses, the private specifier means that a variable or method is visible only within the given class. Neither the outside world nor classes derived from the class can see or use private attributes or methods. In the Point example, the attributes myX and myY are private.

When a class is designed to serve as a parent class, and will have derived subclasses, the protected specifier allows the given class and its subclasses to see those attributes or methods. Classes not related to that class by inheritance cannot see protected attributes or methods.

Finally, Java provides what is called package visibility for attributes and methods. Any attribute or method defined without using any visibility specifier (public, private, protected) has package visibility. A package is a collection of related classes that are tied together by the Java package statement, which must be the first statement in each file that is a part of the package. Package visibility is much like public visibility except that it is limited to other classes included in the same Java package.

Package visibility is usually not a good thing. Using package visibility breaks class encapsulation. It will couple the definitions of the classes in the package, and likely lead to problems later as the package is maintained over time. When classes work closely together, as they often do in a library, using package visibility can avoid making some attributes or methods public, so it can be useful.

There are a couple of other complicating factors with package visibility. First, all non-private attributes and methods are visible to other classes in the same package. This means public, protected, and unlabeled attributes and methods. Second, if you don't put a class into a package, it automatically is placed into the default package, which usually consists of all classes in the current file directory. The small example classes shown in Listing 3-3 demonstrate this. The example consists of two files, PackageVisibility.java and Foo.java, contained in the same file directory. Because neither file has been explicitly placed in a package, the default package visibility makes Foo.protectedInt visible to the PackageVisibility class. Beware.

Listing 3-3. PackageVisibility.java, Foo.java
Example showing default package visibility.
 /* This example demonstrates "default package visibility".
  * Foo is defined in the same directory as PackageVisibility.
  * Thus, only the Foo private attribute privateInt is really
  * private. PackageVisibility sees all three other variables.
  */
  
 public class PackageVisibility
 {
     public static void main(String args[])
     {
         Foo foo = new Foo();
         int fooPublic = foo.publicInt;       // public
         int fooPackage = foo.packageInt;     // package
         int fooProtected = foo.protectedInt; // default package
         // Following is not visible, would cause compiler error
 //      int fooPrivate = foo.privateInt;
     }
 }

 public class Foo
 {
     // Simple class to demonstrate visibility
     public int publicInt;
     protected int protectedInt;
     int packageInt;
     private int privateInt;
 }

As a rule, you should always use a public, private, or protected specifier in the definition of each attribute and method in a class definition. The only exception is if you require package visibility for a library or other Java package, and then the class definition must use a Java package specification.

The UML provides a notation for specifying visibility of attributes and methods. Note in the UML class diagram of Point that the private myX and myY attributes have a "-" at the beginning, which means private visibility, while the methods getY, getX, and setPoint have a "+" to indicate public visibility. The UML uses a "#" to indicate protected (and Java package) visibility.

Inheritance

Inheritance is a fundamental feature of object-oriented design and programming. While the use of class association, aggregation, and composition are just as important as inheritance when developing real programs, inheritance differs in that implementation support for it must be provided by the object-oriented programming language. It is in the language support for inheritance that C++ most differs from C, or Java differs from earlier languages such as Pascal.

In the earlier Point and Circle example, we noted that having a Color class might be useful, and that it would make sense to generalize the concept of a shape. To demonstrate inheritance in Java, we will develop a simple example that uses a Point class, a Color class, and a Shape class that will be used to define Circle and Rectangle classes. (We could define other shapes, but two are enough to illustrate how to use the UML and Java to implement inheritance.)

First, let's design a general Shape class. What is common to a Circle and a Rectangle? Both will have an origin, and a color. Furthermore, all shapes have some other common geometric attributes such as area and perimeter. Thus, we will design a parent Shape class that accounts for these common factors by including objects of the Point and Color classes as attributes of Shape. Area and perimeter will be handled by defining methods. The calculation of area and perimeter will be defined by the subclasses of Shape.

In our example, we can reuse the Point class from earlier, but will need to add a new Color class. We will also need a base Shape class that will be used to derive other specific shapes. For our example, we will design new Circle and Rectangle classes that are derived from a Shape class.

First, here are UML class diagrams for the new Color class and again for Point:

Figure 3-3.
Point and Color UML

The corresponding code for Color (see Listing 3-1 for Point):

Listing 3-4. Color.java
RGB Color class
 /**
  * Color - a simple RGB color representation
  */
 public class Color        
 {
     // Attributes
     private int blue;
     private int green;
     private int red;
  
     // Constructors
     public Color(int r, int g, int b)
     {
         red = r; green = g; blue = b;
     }
  
     // Methods
     public int getB()
     {
         return blue;
     }
  
     public int getG()
     {
         return green;
     }
  
     public int getR()
     {
         return red;
     }
  
     public void setRGB(int r, int g, int b)
     {
         red = r; green = g; blue = b;
     }
 }

Now we will show the UML diagrams for our new shapes. Recall that to show inheritance (or generalization/specialization), the UML uses a line from the class diagram of the specialized class connected to the diagram of the more general class with an open headed arrow pointing to the parent class.

In this design, it would never make sense to have a Shape object - there would just be instances of Circles or Rectangles. Thus, the Shape class should be an abstract class. This is shown in the UML by using Italic type for the class name. In addition, we specify that all specific Shapes must provide methods for area and perimeter. Because each specific shape will use a different method to calculate its area and perimeter, the area and perimeter methods of the Shape class are declared abstract, and the specific implementations are defined in the derived subclasses.

Here is the UML diagram for Circle and Rectangle classes derived from the abstract Shape class.

Figure 3-4.
Circle and Rectangle are derived from Shape

Here is the corresponding code:

Listing 3-5. Shape.java
Abstract base class for shapes
 /*
  * Shape - an abstract base class for other shapes.
  * Defines color and origin of shape, and getter/setters for those
  * Defines abstract methods area and perimeter for actual shapes  */
 public abstract class Shape           
 {
     // Attributes
     private Color color;
     private Point origin;
  
     // Constructors
     protected Shape(Color col, Point org)
     {
         origin = new Point(org.getX(), org.getY());
         color = new Color(col.getR(),col.getG(), col.getB());
     }
  
     protected Shape(Point org)
     {
         origin = new Point(org.getX(), org.getY());
         color = new Color(0,0,0);    // black by default
     }
  
     protected Shape()
     {
         origin = new Point(0.0, 0.0);  // 0.,0. origin
         color = new Color(0,0,0);      // black by default
     }
  
     // Methods
     public abstract double area();     // real shape defines
  
     public abstract double perimeter(); // real shape defines
  
     public Color getColor()
     {
         return color;
     }
  
     public void setColor(Color col)
     {
         color.setRGB(col.getR(), col.getG(), col.getB());
     }
  
     public Point getOrigin()
     {
         return origin;
     }
  
     public void setOrigin(Point org)
     {
         origin.setPoint(org.getX(), org.getY());
     }
 }

Listing 3-6. Circle.java
Circle derived from Shape
 /*
  * Circle - a Shape representing a circle
  */
 import java.lang.Math;        // for PI
  
 public class Circle extends Shape
 {
     // Attributes
     private double radius;
  
     // Constructors
     public Circle()
     {
         super();
         radius = 0.0;
     }
     
     public Circle(final Point org, final double rad)
     {
         super(org);
         radius = rad;
     }
  
     // Methods
     public double area()
     {
         return Math.PI * radius * radius;  // Pi r Squared
     }
  
     public double getRadius()
     {
         return radius;
     }
  
     public double perimeter()
     {
         return 2 * Math.PI * radius;    // 2 PI r
     }
  
     public void setRadius(double r)
     {
         radius = r;
     }
 }

Listing 3-7. Rectangle.java
Rectangle derived from Shape
 /*
  * Rectangle - defines height and width to specify rectangle
  */
 public class Rectangle extends Shape
 {
     // Attributes
     private double height;
     private double width;
  
     // Constructors
     public Rectangle()
     {
         super();
         height = 0.0; width = 0.0;
     }
  
     public Rectangle(Point org, double h, double w)
     {
         super(org);
         height = h; width = w;
     }
  
     // Methods
     public double area()
     {
         return height * width;
     }
  
     public double perimeter()
     {
         return 2 * (height + width);
     }
  
     public double getH()
     {
         return height;
     }
  
     public double getW()
     {
         return width;
     }
  
     public void setHW(double h, double w)
     {
         height = h; width = w;
     }
 }

The Java extends specifier is used by the definitions of Circle and Rectangle to specify that they inherit from the Shape class. The Java abstract specifier is used by the definition of Shape to indicate that it is an abstract class, and that area and perimeter are abstract and will not have implementations in Shape.

The base class can provide implementations of the attributes and methods common to all subclasses. Thus, Shape provides the declarations for the color and origin attributes, and the implementations of the getColor, getOrigin, setColor, and setOrigin methods. It is up to the definitions the subclasses Circle and Rectangle to provide appropriate implementations for area and perimeter.

This simple example illustrates how a simple UML diagram can be mapped to Java code. In these examples, we've used all three of the public, private, and protected Java specifiers. All of these are examples of specific features of Java used to support inheritance.

Notice that Java uses the dot (.) operator to provide access to individual attributes or methods of a class. For example, the line

     color = new Color(col.getR(),col.getG(), col.getB());

from the Shape class constructor used col.getR() to access the red value of the color passed into the constructor. In Circle.java, we find the line

     return 2 * Math.PI * radius;

which references the constant value for PI from the Java core Math package.

Most of the variables used in the rest of the code don't use the dot operator. These are references to attributes of the given instance of the class. Each of these references has an implicit "this." associated with it, and sometimes it adds clarity to the meaning of a program to explicitly specify this.

Association, Aggregation, and Composition

While the UML has special symbols to represent association, aggregation, and composition, Java (like other OO languages) does not provide special programming language constructs to support them. Fortunately, implementing these designs in code is not very difficult. Usually, association, aggregation, and composition are all implemented in the same way - using links. When there is only one object involved (a multiplicity of 1), then a simple object reference variable will do. When more than one object can be involved in the relationship, then a list or Java Vector can be used.

The links will be attributes of the classes involved in the relationship. There can be either a one-way link, or a link in both directions, depending on the requirements of the design. This directional connection is called navigability. If the navigability is one way, that direction is shown in UML by and arrow on the association connection line. No arrows implies two-way navigability.

There are conventions that can be used to choose the name of the link variable. Associations are usually named, and often have specific names for each end of the association. If the ends are named, then that name should be used for the link variable. If there is no name on the end, then simply use a lower case version of the class name. Thus, for aggregation and composition, which usually don't have an association name, use the lower case name of the class at the other end. This will usually be the name of the part class in the whole/part relationship.

Just to demonstrate how these links can be implemented, we will revisit the simple Library hierarchy we discussed in Chapter 2. Figure 3-5 shows the hierarchy again. Note that in the code that implements the UML hierarchy, you can't really tell the difference between any of these associations just from the link declarations in the code. The different behaviors required for association, aggregation, or composition are properties of how the code works.

Figure 3-5.

Relationships between a Library and its customers

The following Java code shows just how the links between the different objects can be implemented in Java. Note that this code just shows the links. It does not show constructors, or any other methods what would be required to actually use these objects.

Listing 3-8. Library.java
Links to parts
 /*
  * Library.java - Outline only. Code not complete.
  */
 public class Library 
 {
     private Vector libraryBook;   // List of all LibraryBooks
     private Vector borrowing;     // List of Borrowings
     private Vector reader;        // List of Readers
  
     // ...
 }

Listing 3-9. Book.java
Vector page implements composition links
 /*
  * Book.java - Outline only. Code not complete.
  */
 public class Book 
 {
     private Vector page;    // list of Pages
 }

Listing 3-10. Page.java
No links
 /*
  * Page.java - Outline only. Code not complete.
  */
 public class Page 
 {
     // ...
 }

Listing 3-11. LibraryBook.java
Borrowing borrowed implements association
 /*
  * LibraryBook.java - Outline only. Code not complete.
  */
 public class LibraryBook extends Book
 {
     private Borrowing borrowed;    // link to Borrowing object
     
     // ...        
 }

Listing 3-12. Person.java
Root class
 /*
  * Person.java - Outline only. Code not complete.
  */
 public class Person
 {
     // ...
 }

Listing 3-13. Reader.java
Extends Person, Vector borrowing implements association
 /*
  * Reader.java - Outline only. Code not complete.
  */
 public class Reader extends Person
 {
     private Vector borrowing; // Borrowings of LibraryBooks
     
     // ...
 }

Listing 3-14. Borrowing.java
Reader reader and LibraryBook libraryBook implement association
 /*
  * Borrowing.java - Outline only. Code not complete.
  * This code would implement a Borrowing - it will link a
  * specific LibraryBook to the corresponding Reader.
  */
 public class Borrowing 
 {
     private Reader reader;            // Link to the Reader
     private LibraryBook libraryBook;  // Link to the book 
  
     // ...
 }

Java Interfaces

Java does not support multiple inheritance. In practice, multiple inheritance is infrequently used. Java does provide a language feature called interfaces. Java interfaces support what multiple inheritance is most often used for: mix-in classes. A mix-in allows a class to implement a specific behavior defined by the mix-in class. When multiple inheritance is available, the mix-in can be a true class, with methods and behavior of its own. A Java interface just allows a specification of methods without providing any code implementation of those methods. It is up to the class using a Java interface to provide the actual implementation of the methods described by the interface specification.3

An examination of a real Java utility class that uses an interface for its functionality will help demonstrate just how useful a mix-in interface can be. The core Java utility package provides a class called Observable that implements the Observer design pattern.4 It is a common programming problem to have one object that is of special interest to several other objects that need to know when that one object changes its state. Consider a single sensor that is monitoring the status of a door, for example. When that sensor triggers because someone opens the door, several other objects may be affected by that event. There could be an object that controls lighting, and needs to turn on lights automatically if the door opens. Another object might notify a guard station or alarm system, and another simply count how many times the door has been opened. So whenever the door sensor detects that the door has opened, it would need to notify all objects interested in that event. The Observer design is ideal for this.

The sensor is the Observable object, and would be designed by inheriting from the Java Observable utility class. Any other object (light controller, guard station) interested in being notified whenever the Observable object (the door sensor) changes state would register itself with the Observable object. Then, when that object (door sensor) changes state, it would send a message to all registered Observables using the notifyObservers method provided by the Java Observable class. The notifyObservers would then call the update method implemented by each Observer object that registered. The UML for this is shown in the "Interfaces in UML" section.

Interfaces in UML

Interfaces are specified slightly differently in the UML than classes. The main diagram is like a class box, but includes <<interface>> or a circle to indicate it is an interface and not a class. And because interfaces don't have attributes, the attribute section is usually not included. Classes that implement an interface use a dashed realizes connection rather than the solid generalization connection.

The following UML illustrates the Observer interface discussed in this section. Note the association between Observable and Observer with the triangle direction indicator.

In this case, the Observable class provides some real functionality. It has a method (addObserver) that builds a list of all Observers. When its notifyObservers method is called, it uses that list to call the update method of each Observer. On the other hand, the only function of the Observer definition is to specify that there will be an update method provided for the Observable to call. Since the behavior of each Observer is likely to be quite different, there is no need for any functionality in the Observer specification. Thus, Observer is defined as a Java interface that specifies a single method, update. Furthermore, it is likely that some of the classes that implement Observer objects will be derived for other classes. Thus, the fact that Observer is an interface means it will serve as a simple mix-in to the actual classes that define Observers, and without the need for multiple inheritance.

While multiple inheritance can be useful for some programming problems, it does in fact lead to some significant problems for the language compiler and run time implementation (These issues are beyond the scope of this book, but are real, although solvable at some cost). The Java interface provides a similar functionality to multiple inheritance, but avoids the big complications that multiple inheritance brings to a language implementation. Thus, it gives most of the benefits without the added cost of true multiple inheritance.

Object lifetime in Java

As we discussed in Chapter 2, all objects have a lifetime. They are created, carry out their responsibilities, and usually go away - either when the program is finished using an object, or when the program terminates. (It is possible to have persistent objects that "live" from one invocation of the program to another, but such persistent objects are a different matter.)

No Java object exists until it has been created with the Java new operator. Because a program has to start by creating new objects, a Java program requires a static component, e.g., public static main(String args[]). The main method exists statically, and creates new objects to get the program running.

Constructors

When a Java object is created with the new operator, it begins its lifetime by having its constructor automatically called by the runtime system. The constructor is really a special purpose method. The constructor has the same name as the class, with no other type attribute. The constructor can be declared to be public, private, or protected.

The goal of a constructor is to initialize the class. It should set all class attributes to a known state. It can create other subobjects that are a part of the class. When the constructor is done, the newly created object should be ready to use. A constructor does not return a value, but it can throw an exception. The new operator returns a reference to the newly created object.

Java allows you to define multiple constructors for a class. Each constructor has a different signature determined by the order and type of the constructor's arguments. The default constructor has no arguments. It is often useful to define several constructors to initialize objects to different values.

If a class is derived from another class, the constructor of the parent should be called. This is done with the Java super() statement. Java will automatically call the default constructor of the parent class if you don't explicitly use a super() statement. If you want to use a parent constructor other than the default one, you can use super() with an appropriate set of arguments.

Almost every Java example shown in this book will have constructors that you can study to see the different ways constructors can be used.

Garbage collection

After an object has carried out its purpose, it is often not needed by the program any longer. All objects consume at least some system resources, and it is important to free these resources when an object is no longer needed. Whenever an object no longer has any references to it, the Java runtime system will consider the object unused, and therefore available for garbage collection.

Automatic garbage collection is a great simplification for the programmer. Unlike in other object-oriented languages such as C++, the Java programmer is not responsible for explicitly freeing resources used by an object. The garbage collector handles all this (usually - see the next section, "Memory leaks").

However, having garbage collection is not totally a free ride. First, you never can predict when the garbage collector will be invoked, and thus won't know just when an object has its resources freed. Java provides a special method for each class called finalize(). The finalizer is called when the garbage collector frees the resources of the object. In theory, this should allow the object to free other resources it might have used, such as file handles. In practice, this is not something to rely on, and finalize() is in fact relatively worthless for all but a few specialized cases that most programmers will never see. If you have an object that uses system resources like file handles or network connections or whatever, you should be sure that the object frees them when you know you are done with the object, and before you release the last reference to it, and not count on finalize.

There is another problem with garbage collection. When the garbage collector does run, it can more or less freeze a program for a few moments while garbage collection takes place. As processors get faster, this issue becomes less important, but it is still possible to see a Java program momentarily pause for no apparent reason. This is the garbage collector running.

Memory leaks

Releasing the memory used by an object is easy in Java because Java uses garbage collection. When an object no longer has any references to it, it becomes available for garbage collection. All you need to do is let go of all references to the object. You will never have to worry about corrupting memory or causing a runtime error by freeing an object more than once as you would in other languages such as C++.

You do, however, have to worry about getting rid of all references to an object when you are finished with it. If you leave a dangling reference to an object, it will never be garbage collected, and you can still end up with a memory leak, just like in C++.

It isn't always easy to generate unused references to an object, but it does happen. One way is to add objects to a list, and then use only one item in the list and then never empty the rest of the list. You can create dangling references using some core Java library routines that use listeners. Failing to unregister listeners is a common error that can create memory leaks. On the other hand, if you eliminate all references to an object that in turn references other objects, the garbage collector will also reclaim the memory from those indirectly freed objects.

Most of the time, just reassigning a Java object variable to a different object will let go of the reference to the original object. It is good programming practice to explicitly assign null to an object reference variable when you are done with the object if you aren't reassigning it to a different object. This practice will help be sure that objects don't have unused references to them, and allow them to be garbage collected.

Class vs. Instance methods and attributes

Recall that class methods and attributes go with an entire class. They exist even if no instances of a class have been created. They are available for use by any instance of the class, and are sometime used by objects outside the class. There is only one copy of any class attribute, and its value is independent of how many instances of the class are created.

Instance methods and attributes exist and are useful only after an instance of the class has been created. Each instance of the class will get its own copy of instance attributes, and these attributes can have different values, depending on the state of the specific object. Instance methods can use both instance attributes and methods and class attributes and methods.

Java uses the keyword static to indicate class variables and methods. Class or static methods and attributes can be accessed without having any instances of the class. To access static methods and attributes, you can simply use the name of the class, rather than a class reference variable. A class method must not access instance variables or methods of the class.

Before instance methods and attributes can be used, you must first create an instance of the class with the new operator. You must then use the object reference variable to access the class methods or attributes for a specific instance of the class.

Class attributes can be declared as public static final and given an initial value. This is the typical Java idiom for defining constants for use within a class and by other classes. For example, the Circle class discussed earlier used the Java Math package's constant Math.PI, which is the value of Pi. Using constants like this is one case where it is acceptable to use a class attribute directly rather than through a getter. The Card example used in the next section demonstrates using class attributes to represent playing cards.

Copies of Objects

When programming with an object-oriented programming language like Java, it is important to understand what is going on when you assign the value of one variable to another. With variables of standard primitive types such as int, it is easy. Take, for example:

  int a, b;
  a = 1;
  b = a;

It is clear that the value of b will be 1, just like a. When the variable is an object reference, however, the situation is not so clear. For example:

  Circle c1, c2;
  c1 = new Circle(0.5);
  c2 = c1;

In this case, c1 is a reference to a Circle object with a radius of 0.5. The important thing to note is that c1 is a reference to a Circle, and not a Circle object. Thus, the assignment c2 = c1 makes c2 a reference to the same Circle object that c1 refers to. Thus, c2 is a reference copy of c1. While you are programming, it is easy to think of c1 as a particular Circle object, but as soon as you start assigning one object variable to another, you must remember that you are really using references to objects.

Consider the following code fragment:

  Circle c1, c2;
  c1 = new Circle(0.5);
  c2 = c1;
  c1.setRadius(1.0); // Change the radius of c1
  int r1 = c1.getRadius();
  int r2 = c2.getRadius();

What are the values of r1 and r2? Both are 1.0 because both c1 and c2 are references to the same object. If the goal of having the c2 variable was to keep a copy of the original value of the circle, then this code does not accomplish that task. There are plenty of times you will want and need a copy of an object reference, but there are times when you want and need a copy of an entire object, not just its reference.

Java provides a mechanism for creating copies of objects called cloning. However, just to confuse matters even more, there are two different ways to make a copy of an object called shallow copy and deep copy. Depending on just how you are using a copy of an object, sometimes shallow copy will be all you need, and sometimes you will have to use deep copy.

reference copy A reference copy of an object is simply another reference variable that is a duplicate reference to the same object.

shallow copy A bit-wise copy of an object. A new object is created that has an exact copy of the values in the original object. If any of the fields of the object are references to other objects, just the references are copied.

deep copy A complete duplicate copy of an object. If an object has references to other objects, complete new copies of those objects are also made.

A shallow copy makes a bit by bit copy of your object, including references to other objects. Thus, if the object you are copying contains references to yet other objects, a shallow copy refers to the same subobjects. Sometimes this is OK, especially if the values of the subobjects won't change.

A deep copy generates a copy not only of the primitive values of the original object, but copies of all subobjects as well, all the way to the bottom. If you need a true, complete copy of the original object, then you will need to implement a full deep copy for the object.

Java supports shallow and deep copy with the Cloneable5 interface to create copies of objects. To make a clone of a Java object, you declare that an object implements Cloneable, and then provide an override of the clone method of the standard Java Object base class. When making a clone of an object, the assignment statement looks like:

     MyObject copy = (MyObject)original.clone();

(Note: clone always returns an Object, thus the need for the cast.)

Implementing Cloneable simply tells the Java compiler that your object is Cloneable. The cloning is actually done by the clone method. The default behavior of the clone method provided by the standard Java Object class is to make a shallow copy of the object. To build a deep copy, you override clone with a version that calls the standard clone method to first create a shallow copy, and then explicitly create the copies of the subobjects used by the class. If all the subobjects are Cloneable, then you can simply clone each subobject. It is good programming practice to make all subobjects of a class Cloneable if you need to implement deep copy. The examples presented in Listing 3-15 should help clarify the whole issue.

Whether you need a reference copy, a shallow copy, or a deep copy of an object will usually be clear from the context of your program. Many Java programs never need to use anything other than a reference copy of an object. But it is important to understand what it means to be using a reference to an object rather than either a shallow or deep copy (clone) of the object.

The following examples should make the difference between the three types of object copies clear. We will use a small console based Java program to demonstrate this.

These are the two classes in this example program. It requires two classes to demonstrate the difference between shallow and deep copy.

Listing 3-15. CardHand.java
Demonstrates reference, shallow and deep copy
 /* CardHand.java - an example to show the differences among
  * reference copy, shallow copy, and deep copy.
  * Copyright (c) 2001, Bruce E. Wampler
  */
 public class CardHand implements Cloneable
 {
     private Card c1;            // A CardHand has 2 cards
     private Card c2;
     
     public CardHand(Card cd1, Card cd2)
     {
         c1 = cd1; c2 = cd2;
     }
     
     public String toString()
     {
         return "c1:" + c1 + ","+" c2:" + c2;
     }
  
 // **** UNCOMMENT the clone method for SHALLOW and DEEP COPY
 //      // override Object.clone()
 //      public Object clone() throws CloneNotSupportedException
 //      {
 //          // To clone, first shallow clone whole object.
 //          CardHand c = (CardHand) super.clone();
 // // **** UNCOMMENT next THREE line for DEEP COPY ONLY
 // //         // now clone the deep parts
 // //         c.c1 = (Card) c1.clone();
 // //         c.c2 = (Card) c2.clone();
 //          return c;
 //      }
     
     static void main(String args[])
                 throws CloneNotSupportedException
     {
         Card newCard = new Card(Card.HEART,7);
         CardHand origHand =
            new CardHand(new Card(Card.SPADE, 1),
                         new Card(Card.SPADE, 13));
  
 // **** UNCOMMENT next 3 lines for copy by reference ****
         CardHand saveHand = origHand;
         System.out.println("\n**** REFERENCE COPY ****\n\n"
                 + "BEFORE: saveHand = origHand; :\n"
  
 // **** UNCOMMENT next 3 lines for SHALLOW COPY ****
 //        CardHand saveHand = (CardHand) origHand.clone();
 //        System.out.println("\n**** SHALLOW COPY ****\n\n"
 //                + "BEFORE: saveHand = origHand.clone(); :\n"
  
 // **** UNCOMMENT next 3 lines for DEEP COPY version ****
 //        CardHand saveHand = (CardHand) origHand.clone();
 //        System.out.println("\n**** DEEP COPY ****\n\n"
 //                + "BEFORE: saveHand = origHand.clone(); :\n"
  
                 + "\n           origHand is " + origHand
                 + "\n           saveHand is " + saveHand
                 + "\n           newCard  is " + newCard + "\n");
                 
         origHand.c1 = newCard;
         origHand.c2.setCard(Card.DIAMOND,4);
         newCard.setCard(Card.CLUB, 2);
  
         System.out.println(
                   "AFTER: origHand.c1 = newCard;\n"
                 + "       origHand.c2.setCard(DIAMOND,4);\n"
                 + "       newCard.setCard(CLUB,2); :\n"
                 + "\n           origHand is " + origHand
                 + "\n           saveHand is " + saveHand
                 + "\n           newCard  is " + newCard + "\n");
     }
 }

This example, CardHand.java, simply defines a hand containing two Cards as defined in the class shown in Listing 3-16. All three cases, reference copy, shallow copy, and deep copy can be demonstrated by adding and removing appropriate comments. We've chosen to use comments this way to help you to see how similar the program is for any of the three ways all in one place. The program as shown in Listing 3-15 is commented to build the reference copy version. The program creates an original card hand, makes a copy of the original, and then changes the values of the original. Depending on the kind of copy used, the results are completely different.

Listing 3-16 is the code for the Card object. Card has been made Cloneable so that it can easily be used by other classes that implement deep copy. Just because it is Cloneable does not mean that objects that use a Card will need to make clones. And because a Card has only ints for attributes, a shallow copy and a deep copy of a Card would produce identical copy objects.

Listing 3-16. Card.java
Needed to distinguish shallow and deep copy
 /* Card.java - a simple class for cards - part of clone demo
  * Copyright (c) 2001, Bruce E. Wampler
  */
 public class Card implements Cloneable
 {
     public static final int CLUB = 1;   // constants for suites
     public static final int DIAMOND = 2;
     public static final int HEART = 3;
     public static final int SPADE = 4;
  
     private int suite;
     private int value;
     
     public Card(int s, int v)
     {
         suite = s; value = v;
     }
  
     public Object clone() throws CloneNotSupportedException
     {
         return super.clone();    // all ints, so just use super
     }
  
     public void setCard(int s, int v)
     {
         suite = s; value = v;
     }
  
     public int getSuite() { return suite; }
     public int getValue() { return value; }
     
     public String toString()
     {
         String str;
  
         if (value == 1)
             str = "A/";
         else if (value == 11)
             str = "J/";
         else if (value == 12)
             str = "Q/";
         else if (value == 13)
             str = "K/";
         else
             str = Integer.toString(value) + "/";
         switch (suite)
         {
             case CLUB:
                 str += "Clubs";
                 break;
             case DIAMOND:
                 str += "Diamonds";
                 break;
             case HEART:
                 str += "Hearts";
                 break;
             default:
                 str += "Spades";
                 break;
         }
         return str;
     }
 }    
  

Reference copy is the easiest. The statement

     CardHand saveHand = origHand;

is where the reference copy is made. Note that even though the Card object is Cloneable, this statement does not make any clones since clone() is not used. Figure 3-6 shows the output for the reference copy version.

Because saveHand is simply a reference to origHand, any changes to origHand simply show up when we reference the same object using saveHand instead. This is also true when we assign newCard to origHand.c1. We are making a reference copy, so any changes to newCard will show up when origHand.c1 is used - it is a reference to the same object.

 **** REFERENCE COPY ****
 
 BEFORE: saveHand = origHand; :
 
            origHand is c1:A/Spades, c2:K/Spades
            saveHand is c1:A/Spades, c2:K/Spades
            newCard  is 7/Hearts
 
 AFTER: origHand.c1 = newCard;
        origHand.c2.setCard(DIAMOND,4);
        newCard.setCard(CLUB,2); :
 
            origHand is c1:2/Clubs, c2:4/Diamonds
            saveHand is c1:2/Clubs, c2:4/Diamonds
Figure 3-6.
Output showing results of reference copy
 

To implement shallow copy, we simply change the statement where the copy is made to use clone instead:

     CardHand saveHand = (CardHand) origHand.clone();

This makes a shallow copy. Since we haven't yet uncommented the clone method override, the code will use the default protected clone method of the Object class. (Note that we had to explicitly override clone in the Card class to change clone to public.) Thus, the only difference between the code for the reference copy version and the shallow copy version is the use of origHand.clone().

After the clone, saveHand is no longer a reference to the same object as origHand. Instead, it now contains copies of the references to the two Cards, c1 and c2. It does not have new copies of the Cards, however. Thus, when origHand.c1 is set to refer to newCard, saveHand.c1 is not changed. However, when the Card that origHand.c2 refers to is changed by the setCard call, it is the same Card object that both origHand.c2 and saveHand.c2 that is changed.

 **** SHALLOW COPY ****
 
 BEFORE: saveHand = origHand.clone(); :
 
            origHand is c1:A/Spades, c2:K/Spades
            saveHand is c1:A/Spades, c2:K/Spades
            newCard  is 7/Hearts
 
 AFTER: origHand.c1 = newCard;
        origHand.c2.setCard(DIAMOND,4);
        newCard.setCard(CLUB,2); :
 
            origHand is c1:2/Clubs, c2:4/Diamonds
            saveHand is c1:A/Spades, c2:4/Diamonds
            newCard  is 2/Clubs
 
Figure 3-7.
Output showing results of shallow copy

Finally, we show the results of a deep copy. To do this, we uncomment the clone override in CardHand. Now the statement

     CardHand saveHand = (CardHand) origHand.clone();

creates not only copies of the references to the Cards as in a shallow copy, but copies of those Card objects as well. This now means that the setCard call changes the origHand.c2 Card, but not the saveHand.c2 Card copy. And origHand.c1 and newCard still refer to the same Card object, while saveHand.c1 refers to the original "A/Spades" Card object.

 **** DEEP COPY ****
 
 BEFORE: saveHand = origHand.clone(); :
 
            origHand is c1:A/Spades, c2:K/Spades
            saveHand is c1:A/Spades, c2:K/Spades
            newCard  is 7/Hearts
 
 AFTER: origHand.c1 = newCard;
        origHand.c2.setCard(DIAMOND,4);
        newCard.setCard(CLUB,2); :
 
            origHand is c1:2/Clubs, c2:4/Diamonds
            saveHand is c1:A/Spades, c2:K/Spades
            newCard  is 2/Clubs
Figure 3-8.
Output showing results of deep copy

Messages

When thinking in object-oriented terms, one says that an object passes a message to another. The idea of thinking about passing messages is very useful. In fact, most messages in a Java program are really calls to some method of a class, which can then respond to the message by returning a value. While you can say that you are calling or invoking a class method, it is better to say Class A passes a message to Class B.

Asynchronous messages, such as those caused by events, are somewhat different. In these cases, the idea of really sending a message is close to what is going on. This kind of message can be handled by a Java listener and callback. We looked at callbacks earlier. The Java interface construct is useful for building this kind of message handling. You define a Java interface that describes the name of the callback method, and then implement the callback in the class that needs to receive a message. Then, some other class can send a message by calling the callback method described in the interface.

Chapter Summary

Resources

Java

The Java Tutorial, from Sun:
java.sun.com/docs/books/tutorial/

Code Conventions for Java, from Sun:
java.sun.com/docs/codeconv/

Java Documents, from Sun:
java.sun.com/docs/index.html

Object-Oriented Software Development Using Java, Xiaoping Jia, Addison-Wesley, 2000, ISBN 0-201-35084-X.

Thinking in Java, Bruce Eckel, Prentice Hall, 1998, ISBN 0-13-659723-8.

Java: An Introduction to Computer Science & Programming, Walter Savitch, Prentice Hall, 1999, ISBN 0-13-287426-1.

Java in a Nutshell, David Flanagan, O'Reilly, 1999, ISBN 1-56592-487-8.

Practical Java Programming Language Guide, Peter Haggar, Addison-Wesley, 2000, ISBN 0-201-61646-7.

1
Of course, even using UML doesn't guarantee object-oriented programs.

2
Actually, you can build Java source files that define more than one class, but there can be only one public class in each file. Any other classes within a file serve as utility classes for the main public class in that file.

3
While the Java keywords interface and implements describe the language features quite well, the terms are also commonly used to describe a user interface and code that implements a program feature. Usually, context will tell the true meaning.

4
Design patterns are design solutions for a large number of problems commonly found in object-oriented programs. They are discussed in more detail in Chapter 7.

5
Cloneable is Java's (mis)spelling.


TOC PREV NEXT INDEX

The Essence of Object-Oriented Programming with Java and UML (DRAFT)

Copyright © 2001 by Addison-Wesley

All rights reserved. No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form, or by any means, electronic, mechanical, photocopying, recording, or otherwise, without the prior consent of the publisher. You may view this document on a web browser, but you must not mirror or make local copies.