Espresso: A Concentrated Introduction to Java
Summary: We describe how and why to create your own classes that serve as templates for the creation of new objects.
Prerequisites: Basics of Java.
Contents:
You have probably heard that Java is an object-oriented language. What does that mean? It means that the focus of programming in Java is supposed to be on the design of interacting objects. (To some, it means that Java supports objects, encapsulation, inheritance, and polymorphism.)
You may find that emphasis surprising, given that most of the programs you've written have focused primarily on using objects, rather than on designing objects. It turns out that Java supports two models of problem solving, imperative and object-oriented. Computer science educators have mixed opinions on which model to teach first. Espresso originally took an objects-early approach. However, as I've used and expanded Espresso, I've found that students seem to do better by mastering the basic imperative issues first.
You have now encountered most of the imperative aspects of Java. It is therefore time to move on to the object-oriented paradigm. We begin by considering how to build and use template classes in Java.
Different object-oriented languages provide a variety of mechanisms by which programmers describe objects. In Java, as in many languages, programmers typically describe objects through template classes. A template class is a class that provides a template for building individual objects. Template classes typically contain three parts:
We will consider each in turn.
In Java, a template class typically has the following structure.
package username.grouping; import package.name.RelatedClass; /** * A helpful introductory comment that describes the class. */ public class ClassName { // +--------+-------------------------------------------------- // | Fields | // +--------+ Field declarations // +--------------+-------------------------------------------- // | Constructors | // +--------------+ Constructors // +---------+------------------------------------------------- // | Methods | // +---------+ Methods } // class ClassName
You can also include portions of a utility class or a main class, such as static fields, static methods, constants, and a main methods.
As suggested above, object fields provide the data associated with an object. They may be information the object contains (e.g., a list might contain a particular value as its first element), or they may describe the object (e.g., a book has a title).
Each field declaration has three parts:
Field declarations have the form:
protection Type name;
For example, to say that we have a field named title
with type String
and the standard protection, we
would write
String title;
The custom in Java is to have field names begin with a lowercase
letter. When you reference a field within a class, you typically
preface the name of the field with the keyword this
.
For example, we would refer to the title
field
as this.title
.
You may note that field declarations look a lot like variable declarations. However, they differ in a number of ways. First, variable declarations are nested within method definitions; field declarations appear directly within the class definition. Next, variables are available only within the declaring method, while fields are available to any method in the class. Finally, fields have protection levels, variables do not.
As an example, let's consider a simple class that we see in many sciences, a vector in two space rooted at the origin. If you have no idea what a vector in two space is, think of it as a line from the origin, (0,0), to a point, (x,y).
What fields might we have for a vector in two space? One possibility is to have the location of the head, that is, the x and y coordinates. Another is to use the angle of the vector from the positive x axis and the radius. Which do we choose? It depends on our application. (In fact, you will find that choosing the appropriate fields is one of the difficult aspects of object design.) For now, let's just use the radius and the angle.
/** The length of the vector. */ double radius; /** The angle of the vector from the positive X axis in radians. */ double theta;
Note that we might also have chosen to represent the radius
and angle as BigDecimal
values.
How do these fields get initialized? Traditionally, they
get initialized when you build (or construct
) a new
object in the class. To initialize the fields, you write
something called a constructor that fills in those
fields (and, perhaps, does other initialization).
The form of a constructor is fairly simple,
protection NameOfClass(parameters) OptionalThrowsClause { body; } // NameOfClass(ParameterTypes)
The body of the constructor contains a sequence of imperative operations to fill in the fields and do other initialization. The parameters provide the information necessary to fill in those fields.
As you've seen in the past, you typically call a constructor
using the new
keyword, as in
PrintWriter pen = new PrintWriter(System.out, true);
or
Vec2D alpha = new Vec2D(Math.PI/2, 1.0);
For the case of vectors in two-space, we might want an angle
and a radius to build a vector. Here's the definition of
such a constructor, assuming that we've called the class
Vec2D
,
public Vec2D(double _theta, double _radius) { this.theta = _theta; this.radius = _radius; } // Vec2D(double, double)
You'll note that I've given the parameters similar names to the fields (the main difference being that they begin with an underscore). You may choose whatever name you want for the parameters, but I find that when the parameters match the fields, it's convenient to choose similar names.
We might call this constructor as follows:
Vec2D alpha = new Vec2D(Math.PI/2, 1.0);
We can create more than one constructor. In this case, we might also want to permit clients to construct a unit vector (that is, with a radius of 1.0) at any angle.
public Vec2D(double _theta) { this.theta = _theta; this.radius = 1.0; } // Vec2D(double)
We can call this constructor with
Vec2D northeast = new Vec2D(Math.PI/4);
We might even want to permit clients to construct vectors given the x and y position of the head of the vector. It would be tempting to write such a constructor as
public Vec2D(double x, double y) { this.theta = Math.atan(y/x); this.radius = Math.sqrt(x*x + y*y); } // Vec2D(double, double)
Unfortunately, Java will not permit you to write two constructors that take identical types as parameters. After all, if the constructors have the same names and the same types, how will it tell which one you mean? What is the alternative? We can create a special method that builds the vector (which we describe below) or we can choose different parameter types.
public Vec2D(int x, int y) { this.theta = Math.atan(((double) y)/((double) x)); this.radius = Math.sqrt(x*x + y*y); } // Vec2D(int, int)
Finally, we're ready to consider the methods of the class.
Object methods look much like static methods, except that
(a) they do not require the keyword static
and (b) they can refer to the fields of the class.
The general form of an object method is
/** * Helpful introductory comment. */ protection Type methodName(parameters) optionalThrowsClause { body; } // methodName(parameterTypes)
Calling a method is fairly simple: You write the name of the object (that you've already constructed), followed by the name of the method, followed by parameters. For example,
pen.println("Hi!");
or
Vec2D newvec = alpha.add(northeast);
The simplest methods that we can call are the methods that extract simple information from an object. Such methods are typically called observers or, when the extracted information matches a field, getters. Here's one that gets the radius.
/** * Determine the radius of this vector. */ public double getRadius() { return this.radius; } // getRadius()
Similarly, here's one that gets the angle from the positive x axis.
/** * Determine the angle of this vector from the positive * x axis. */ public double getTheta() { return this.theta; } // getTheta()
Here's a silly program fragment that uses those two methods.
int x = ...; int y = ...; Vec2D sample = new Vec2D(x,y); pen.println("Considering the point "(" + x + "," + y + ")"); pen.println(" That point is " + sample.getRadius() + " units from the origin."); pen.println(" The angle between the X-axis and the line to that point is " + sample.getTheta());
We can certainly write methods that compute new values, too.
/** * Compute the x position of the head of this vector. */ public double getX() { return this.radius * Math.cos(this.theta); } // getX()
We can even write methods that build new objects.
/** * Add another vector to this vector. */ public Vec2D add(Vec2D addend) { return new Vec2D(this.getX() + addend.getX(), this.getY() + addend.getY()); } // add(Vec2D)
Given that we can build new objects, we might even write methods that act very much like constructers.
/** * Build a vector to the point (x,y) */ public Vec2D vectorTo(double x, double y) { return new Vec2D(Math.atan(y/x), Math.sqrt(x*x + y*y)); } // vectorTo(double,double)
Monday, 7 February 2005 [Samuel A. Rebelsky]
Tuesday, 21 February 2006 [Samuel A. Rebelsky]
Monday, 27 February 2006 [Samuel A. Rebelsky]
Math.atan
rather than
Math.arctan
. (Thanks to Samuel Tape for the correction.)
This page was generated by
Siteweaver on Thu Mar 30 15:24:22 2006.
The source to the page was last modified on Mon Feb 27 08:53:02 2006.
This page may be found at http://www.cs.grinnell.edu/~rebelsky/Espresso/Readings/classes.html
.
You may wish to
validate this page's HTML
;
;
Check with Bobby