My Object All Sublime
I Will Achieve in Time
Gilbert and Sullivan--The Mikado
To stay abreast of modern software development practices, Java is object oriented from the ground up. The point of designing an object-oriented language is not simply to jump on the latest programming fad. The object-oriented paradigm meshes well with the needs of client-server and distributed software. Benefits of object technology are rapidly becoming realized as more organizations move their applications to the distributed client-server model.
Unfortunately, "object oriented" remains misunderstood, over-marketed as the silver bullet that will solve all our software ills, or takes on the trappings of a religion. The cynic's view of object-oriented programming is that it's just a new way to organize your source code. While there may be some merit to this view, it doesn't tell the whole story, because you can achieve results with object-oriented programming techniques that you can't with procedural techniques.
An important characteristic that distinguishes objects from ordinary procedures or functions is that an object can have a lifetime greater than that of the object that created it. This aspect of objects is subtle and mostly overlooked.In the distributed client-server world, this creates the potential for objects to be created in one place, passed around networks, and stored elsewhere, possibly in databases, to be retrieved for future work.
As an object-oriented language, Java draws on the best concepts and features of previous object-oriented languages, primarily Eiffel, SmallTalk, Objective C, and C++. Java goes beyond C++ in both extending the object model and removing the major complexities of C++. With the exception of its primitive data types, everything in Java is an object, and even the primitive types can be encapsulated within objects if the need arises.
What are objects? They're software programming models. In your everyday life, you're surrounded by objects: cars, coffee machines, ducks, trees, and so on. Software applications contain objects: buttons on user interfaces, spreadsheets and spreadsheet cells, property lists, menus, and so on. These objects have state and behavior. You can represent all these things with software constructs called objects, which can also be defined by their state and their behavior.
In your everyday transportation needs, a car can be modelled by an object. A car has state (how fast it's going, in which direction, its fuel consumption, and so on) and behavior (starts, stops, turns, slides, and runs into trees).
You drive your car to your office, where you track your stock portfolio. In your daily interactions with the stock markets, a stock can be modelled by an object. A stock has state (daily high, daily low, open price, close price, earnings per share, relative strength), and behavior (changes value, performs splits, has dividends).
After watching your stock decline in price, you repair to the cafe to console yourself with a cup of good hot coffee. The espresso machine can be modelled as an object. It has state (water temperature, amount of coffee in the hopper) and it has behavior (emits steam, makes noise, and brews a perfect cup of java).
An object's behavior is defined by its methods. Methods manipulate the instance variables to create new state; an object's methods can also create new objects.
The small picture to the left is a commonly used graphical representation of an object. The diagram illustrates the conceptual structure of a software object--it's kind of like a cell, with an outer membrane that's its interface to the world, and an inner nucleus that's protected by the outer membrane.
An object's instance variables (data) are packaged, or encapsulated, within the object. The instance variables are surrounded by the object's methods. With certain well-defined exceptions, the object's methods are the only means by which other objects can access or alter its instance variables. In Java, classes can declare their instance variables to be public
, in which cases the instance variables are globally accessible to other objects. Declarations of accessibility are covered in later in Access Specifiers.
class Point extends Object { public double x; /* instance variable */ public double y; /* instance variable */ }As mentioned, this declaration merely defines a template from which real objects can be instantiated, as described next.
Point
object--an instance of the Point
class--with a fragment of code like this:
Point myPoint; // declares a variable to refer to a Point object myPoint = new Point(); // allocates an instance of a Point objectNow, you can access the variables of this Point object by referring to the names of the variables, qualified with the name of the object:
myPoint.x = 10.0; myPoint.y = 25.7;This referencing scheme, similar to a C structure reference, works because the instance variables of
Point
were declared public
in the class declaration. Had the instance variables not been declared public
, objects outside of the package within which Point
was declared could not access its instance variables in this direct manner. The Point
class declaration would then need to provide accessor methods to set and get its variables. This topic is discussed in a little more detail after the discussion on constructors.
Point
class from before:
class Point extends Object { public double x; /* instance variable */ public double y; /* instance variable */ Point() { /* constructor to initialize to default zero value */ x = 0.0; y = 0.0; } /* constructor to initialize to specific value */ Point(double x, double y) { this.x = x; /* set instance variables to passed parameters */ this.y = y; } }Methods with the same name as the class as in the code fragment are called constructors. When you create (instantiate) an object of the
Point
class, the constructor method is invoked to perform any initialization that's needed--in this case, to set the instance variables to an initial state.
This example is a variation on the Point
class from before. Now, when you wish to create and initialize Point
objects, you can get them initialized to their default values, or you can initialize them to specific values:
Point lowerLeft; Point upperRight; lowerLeft = new Point(); /* initialize to default zero value */ upperRight = new Point(100.0, 200.0); /* initialize to non- zero */The specific constructor that's used when creating a new
Point
object is determined from the type and number of parameters in the new
invocation.The this Variable
What's the this
variable in the examples above? this
refers to the object you're "in" right now. In other words, this
refers to the receiving object. You use this
to clarify which variable you're referring to. In the two-parameter Point
method, this.x
means the x
instance variable of this object, rather than the x
parameter to the Point
method.
In the example above, the constructors are simply conveniences for the Point
class. There are situations, however, where constructors are necessary, especially in cases where the object being instantiated must itself instantiate other objects. Let's illustrate this by declaring a Rectangle
class that uses two Point
objects to define its bounds:
class Rectangle extends Object { private Point lowerLeft; private Point upperRight; Rectangle() { lowerLeft = new Point(); upperRight = new Point(); } . . . instance methods appear in here . . . }In this example, the
Rectangle()
constructor is vitally necessary to ensure that the two Point
objects are instantiated at the time a Rectangle
object is instantiated, otherwise, the Rectangle
object would subsequently try to reference points that have not yet been allocated, and would fail.
Using the message passing paradigms of object-oriented programming, you can build entire networks and webs of objects that pass messages between them to change state. This programming technique is one of the best ways to create models and simulations of complex real-world systems. Let's redefine the declaration of the Point
class from above such that its instance variables are private
, and supply it with accessor methods to access those variables.
class Point extends Object { private double x; /* instance variable */ private double y; /* instance variable */ Point() { /* constructor to initialize to zero */ x = 0.0; y = 0.0; } /* constructor to initialize to specific value */ Point(double x, double y) { this.x = x; this.y = y; } public void setX(double x) { /* accessor method */ this.x = x; } public void setY(double y) { /* accessor method */ this.y = y; } public double getX() { /* accessor method */ return x; } public double getY() { /* accessor method */ return y; } }These method declarations provides the flavor of how the
Point
class provides access to its variables from the outside world. Another object that wants to manipulate the instance variables of Point
objects must now do so via the accessor methods:
Point myPoint; // declares a variable to refer to a Point object myPoint = new Point(); // allocates an instance of a Point object myPoint.setX(10.0); // sets the x variable via the accessor method myPoint.setY(25.7);Making instance variables public or private is a design tradeoff the designer makes when declaring the classes. By making instance variables public, you are exposing some of the details of the implementation of the class, thereby providing higher efficiency and conciseness of expression at the possible expense of hindering future maintenance efforts. By hiding details of the internal implementation of a class, you have the potential to change the implementation of the class in the future without breaking any code that uses that class.
finalize
method in a class.
/** * Close the stream when garbage is collected. */ protected void finalize() { try { file.close(); } catch (Exception e) { } }This
finalize
method will be invoked when the object is about to be garbage collected, which means that the object must shut itself down in an orderly fashion. In the particular code fragment above, the finalize
method merely closes an I/O file stream that was used by the object, to ensure that the file descriptor for the stream is closed.
class Zebra extends Horse { Your new instance variables and new methods go here }The definition of
Horse
, wherever it is, would define all the methods to describe the behavior of a horse: eat, neigh, trot, gallop, buck, and so on. The only method you need to override is the method for drawing the hide. You gain the benefit of already written code that does all the work--you don't have to re-invent the wheel, or in this case, the hoof. The extends
keyword tells the Java compiler that Zebra is a subclass of Horse. Zebra is said to be a derived class--it's derived from Horse, which is called a base class.
Here's an example of subclassing a variant of our Point
class from previous examples to create a new three-dimensional point called ThreePoint
:
class Point extends Object { protected double x; /* instance variable */ protected double y; /* instance variable */ Point() { /* constructor to initialize to zero */ x = 0.0; y = 0.0; } } class ThreePoint extends Point { protected double z; /* the z coordinate of the point */ ThreePoint() { /* default constructor */ x = 0.0; /* initialize the coordinates */ y = 0.0; z = 0.0; } ThreePoint(double x, double y, double z) {/* specific constructor */ this.x = x; /* initialize the coordinates */ this.y = y; this.z = z; } }Notice that
ThreePoint
adds a new instance variable for the z coordinate of the point. The x and y instance variables are inherited from the original Point
class, so there's no need to declare them in ThreePoint
. However, notice we had to make Point
's instance variables protected
instead of private
as in the previous examples. Had we left Point
's instance variables private
, even its subclasses would be unable to access them, and the compilation would fail.Subclassing enables you to use existing code that's already been developed and, much more important, tested, for a more generic case. You override the parts of the class you need for your specific behavior. Thus, subclassing gains you reuse of existing code--you save on design, development, and testing. The Java run-time system provides several libraries of utility functions that are tested and are also thread safe.
public
, protected
, and private
.The fourth level doesn't have a name--it's often called "friendly" and is the access level you obtain if you don't specify otherwise. The "friendly" access level indicates that your instance variables and methods are accessible to all objects within the same package, but inaccessible to objects outside the package.
The friendly access level comes in handy if you're creating packages of classes that are related to each other and can access each other's instance variables directly. A geometry package consisting of Point
and Rectangle
classes, for instance, might well be easier and cleaner to implement, as well as more efficient, if the Point
's instance variables were directly available to the Rectangle
class. Outside of the geometry package, however, the details of implementations are hidden from the rest of the world, giving you the freedom to changed implementation details without worrying you'll break code that uses those classes. Packages are a Java language construct that gather collections of related classes into a single container. For example, all Java I/O system code is collected into a single package. The primary benefit of packages is organizing many class definitions into a single unit. The secondary benefit from the programmer's viewpoint is that the "friendly" instance variables and methods are available to all classes within the same package, but not to classes defined outside the package.
public
methods and instance variables are available to any other class anywhere.
protected
means that instance variables and methods so designated are accessible only to subclasses of that class, and nowhere else.
private
methods and instance variables are accessible only from within the class in which they're declared--they're not available even to their subclasses.
To declare class variables and class methods, you declare them as static
. This short code fragment illustrates the declaration of class variables:
class Rectangle extends Object { static final int version = 2; static final int revision = 0; }The
Rectangle
class declares two static
variables to define the version and revision level of this class. Now, every instance of Rectangle that you create from this class will share these same variables. Notice they're also defined as final
because you want them to be constants.
Class methods are methods that are common to an entire class. When would you use class methods? Usually, when you have behavior that's common to every object of a class. For example, suppose you have a Window
class. A useful item of information you can ask the class is the width of the border around the window. There's no point in having an instance method to obtain this information that's shared by every instance of Window
--it makes more sense to have just one class method to return the border width.
Class methods can operate only on class variables. Class methods can't access instance variables, nor can they invoke instance methods. Like class variables, you declare class methods by defining them as static
.
This all sounds wonderfully, well, abstract, so why would you need an abstract superclass? Let's look at a concrete example, no pun intended. Let's suppose you're going to a restaurant for dinner, and you decide that tonight you want to eat fish. Well, fish is somewhat abstract--you generally wouldn't just order fish; the waiter is highly likely to ask you what specific kind of fish you want. When you actually get to the restaurant, you will find out what kind of fish they have, and order a specific fish, say, sturgeon, or salmon, or opakapaka.
In the world of objects, an abstract class is like generic fish--the abstract class defines generic state and generic behavior, but you'll never see a real live implementation of an abstract class. What you will see is a concrete subclass of the abstract class, just as opakapaka is a specific (concrete) kind of fish.
Suppose you are creating a drawing application. The initial cut of your application can draw rectangles, lines, circles, polygons, and so on. Furthermore, you have a series of operations you can perform on the shapes--move, reshape, rotate, fill color, and so on. You could make each of these graphic shapes a separate class--you'd have a Rectangle class, a Line class, and so on. Each class needs instance variables to define its position, size, color, rotation and so on, which in turn dictates methods to set and get at those variables.
At this point, you realize you can collect all the instance variables into a single abstract superclass called Graphical
, and implement most of the methods to manipulate the variables in that abstract superclass. The skeleton of your abstract superclass might look something like this:
abstract class Graphic extends Object { protected Point lowerLeft; // lower left of bounding box protected Point upperRight; // upper right of bounding box . . . more instance variables . . . public void setPosition(Point ll, Point ur) { lowerLeft = ll; upperRight = ur; } abstract void drawMyself(); // abstract method } }Now, you can't instantiate the
Graphical
class, because it's declared abstract
. You can only instantiate a subclass of it. When you implement the Rectangle
class or the Circle
class, you'd extend (subclass) Graphical
. Within Rectangle
, you'd provide a concrete implementation of the drawMySelf()
method that draws a rectangle, because the definition of drawMySelf()
must by necessity be unique to each shape inherited from the Graphical
class. Let's see a small fragment of the Rectangle
class declaration, where its drawMySelf()
method operates in a somewhat PostScript'y fashion:
abstract class Rectangle extends Graphical { void drawMySelf() { // really does the drawing moveTo(lowerLeft.x, lowerLeft.y); lineTo(upperRight.x, lowerLeft.y); lineTo(upperRight.x, upperRight.y) lineTo(lowerLeft.x, upperRight.y); . . . and so on and so on . . . } }Notice, however, that in the declaration of the
Graphical
class, the setPosition()
method was declared as a regular (public void
) method. All methods that can be implemented by the abstract superclass can be declared there and their implementations defined at that time. Then, every class that inherits from the abstract superclass will also inherit those methods.
You can continue in this way adding new shapes that are subclasses of Graphical
, and most of the time, all you ever need to implement is the methods that are unique to the specific shape. You gain the benefit of re-using all the code that was defined inside the abstract superclass.
The Java(tm) Language Environment: A White Paper