An Introduction to Objects

Since Java is an object-oriented language, here's a quick introduction to OOP (object-oriented programming) before we dive into our first applet. If you're familiar with OOP, or you don't want to bother with it yet, feel free to skip this section and move on to PaintApp.

First of all, what's an object? Well, an object is a structure that contains data, but unlike a struct in C, an object also contains code that uses its own data.

The Old Way...

This concept is very different from traditional procedure-based programming. Before, structures were just programming conveniences: a single structure could hold a bunch of stuff. You'd put related data together into a structure. Then, when you wanted to use that data, you'd write a procedure that accepted your structure as an argument and did something with it. Note that the data—your structure—and the code that used that data—your procedure—were separate.

For example, let's say you're working on a drawing program, and you want to create structures that represent circles and rectangles. In C++ notation, you could define them like this:

enum { CIRCLE, RECT };
// ---------- Circle ----------

struct Circle {
	int x, y;   // center
	int radius;
};

void DrawCircle(Circle& circle) {
	drawCircle(circle.x, circle.y, circle.radius);
}

// ---------- Rect ----------

struct Rect {
	int x, y; // upper-left corner
	int width, height;
};

void DrawRect(Rect& rect) {
	drawRectangle(rect.x, rect.y, rect.width, rect.height);
}

// ---------- Shape ----------

struct Shape {
	int type;  // enum constant: CIRCLE or RECT

	union {
		Circle circle;
		Rect rect;
	};
};

Then, when you want to draw a shape, you could call this general-purpose procedure:

void DrawShape(Shape& shape) {
	switch(shape.type) {
		case CIRCLE:
			DrawCircle(shape.circle);
			break;
		case RECT:
			DrawRect(shape.rect);
			break;
	};
}

This is quite manageable, for two shapes. However, what if you want to add Line, Triangle, and Polygon shapes? OOP or not, you'll still need to add additional structures. However, you'd also need to define new constants and extend the switch statement in DrawShape() to call additional DrawXxxx() functions. In a larger project, this becomes unwieldy, possibly requiring the recompilation of modules.

...And the New

In OOP, we combine data and code, creating the Shape object. The Shape object has the ability to draw itself, no matter what type of shape it is! This is made possible by the mechanism of inheritance, extending an existing class—the definition of an object—by adding or changing the characteristics of it. Let's see how this is done in pseudo-code. We first define the generic Shape class:

class Shape {
	void Draw() {}
}

We then extend the Shape class to form two new classes, Circle and Rect:

class Circle extends Shape {
	int x, y; // center
	int radius;

	void Draw() {
		drawCircle(x, y, radius);
	}
}

class Rect extends Shape {
	int x, y; // upper-left corner
	int width, height;

	void Draw() {
		drawRectangle(x, y, width, height);
	}
}

Finally, here is our new DrawShape() procedure, for purposes of comparison with the "old way":

void DrawShape(Shape shape) {
	shape.Draw();
}

Some definitions first: An object's data variables are called its fields. Its procedures and functions are called its methods. In the example above, x, y and radius are fields of the Circle class, and Draw() is its sole method.

Explanation time: Because the Circle class (for example) is extended from Shape, it is a Shape, and subsequently inherits all the code of Shape, which in this case, is the Draw method. Note that the fields are no longer qualified with the dot operator (e.g. circle.) inside Draw(). All the fields of an object are directly available to its methods.

Also, because a Circle is a Shape, Circle can be used anywhere in place of Shape. Thus, you can write the following:

Circle c;
c.x = 10;
c.y = 20;
c.radius = 5;
DrawShape(c);

Next comes the DrawShape() method. As shown, you call an object's methods by using the dot operator. What might be puzzling though, is the fact that anything might be drawn at all! After all, the Draw() method in Shape does absolutely nothing. But OOP will in fact determine at run time the correct method to call, depending on whether shape is a Shape, a Circle, or a Rect!

Now, suppose you want to support lines in the drawing program. Just define the Line class as follows:

class Line extends Shape {
	int x1, y1, x2, y2;

	void Draw() {
		drawLine(x1, y1, x2, y2);
	}
}

That's all! The Draw() method of Line will be automatically called by DrawShape() if shape is a Line. No new constants, no switch statement, and no new functions—just add a new class and you're done!

Whew! What does all this have to do with Java, anyway? As you probably guessed, Java is strictly object-oriented. This means that all the code you write must be part of one class or another. If you've been programming in languages such as C or C++, Pascal, or BASIC, this is a big change. You can no longer write procedures that just "float" around in a module. Even the main function in stand-alone applications must be part of a class!


1