In the previous laboratory session, we used a number of classes including a
Point
class that represented points on the Euclidean plane.
We also developed our own class, PointPrinter
, which provided
a simple method for printing information about points.
You may have noted that Point
provided many capabilities
that we have not yet seen how to add to PointPrinter
. In
particular, you may have noted that Point
s have a
state: each point has x and y coordinates and maintains those
coordinates throughout the execution of the program. In addition,
Point
s have parameterized constructors: when you
create a new point, you can specify the initial position of the point.
Most classes provide a number of methods, some of which have
return values: when you call a method such as
getX()
, the method not only gets the x coordinate, but also
returns it to the calling method. We will consider each of these issues
in this laboratory session.
Before we look into the state of objects, let us think a little bit about the state of a program. For example, suppose you were asked to describe everything that should be known about the following function at the point the ``What do we know?'' comment appears. What would you say?
public void doStuff(SimpleOutput out) { int a = 1; int b = 3; int d = a + b * 2; a = d + 2; // What do we know? b = a + a; out.println("b is " + b); } // doStuff()
Hopefully, you would have described the values of the variables and
parameters. For example, you might say something like ``d
has the value 7, a
has the value 9, b
has the
value 3, and out
has an unknown value, corresponding to
the SimpleOutput
object passed as a parameter to the
function''.
In Experiment J3.1 you will investigate program state.
It turns out that the state of an object is also represented by things that are very similar to the variables that we use in methods. These ``things'' are often called attributes or fields. Fields look very much like variables except that they are associated with classes (and objects), rather than methods. To declare a field in a class, write
Are there any fields that we might use with
PointPrinter
? Yes. We might want to keep track of the
number of points that we have printed out, so that we print out not only
the point, but also a number of each point. For our
PointPrinter
class, we would define this field with
public class PointPrinter { /** * The number of points we've printed. */ protected int numprinted = 0; ... } // PointPrinter
What can we do with this field now that we've added it? We need to
update it and perhaps even use it. When referring to the fields of
an object, you use the keyword this
, a
dot, and the name of the field. For example, we'll update the
numprinted
field in the print
method of
PointPrinter
by adding the line
this.numprinted = this.numprinted + 1;
The this
indicates that the
field belongs to the current object. While not strictly necessary,
usage improves clarity. You should always use
this
in your code.
How might we use the new numprinted
field? We can
print it out whenever we print a point. For example, we might
use
out.print(this.numprinted + ": "); ...
In Experiment J3.2 and Experiment J3.3 you will investigate fields.
In our example above, we initialized the
numprinted
field to give it an initial value. You may have
noted that it is often the case when we create an integer variable, we
initialize it. Often, you will initialize your fields to a default
value.
However, this does lead to a question. When we create a new object, how do
we specify what we want the value of that object to be? That is, if we can
create an integer with initial value 1, shouldn't we also be able to create
a Point
with initial value (2,3) or a
PointPrinter
that starts numbering points at 10?
You may recall that we used constructors to set the initial values for the
Point
s we created in a previous session.
For example, to create a new
Point
with the initial value (2,3), we might use
Point p = new Point (2,3);
Similarly, if there was an appropriate constructor defined for
PointPrinter
(we have not yet defined one, but we will), one might write
PointPrinter start_with_10 = new PointPrinter(10);
We will now consider how one defines constructors. In effect, a constructor is simply a method that uses its parameters to set the values of an object's fields. If you've done experiment 3.4, you've already created a method that does that. So, how do constructors differ from regular methods? They differ in their name and their type. When you create a constructor for a class,
For example, we might declare a constructor for PointPrinter
as follows
public class PointPrinter { ... /** * Create a new PointPrinter that counts printed points * starting with value start_val. */ public PointPrinter(int start_val) { this.numprinted = start_val; } // PointPrinter(start_val) ... } // class PointPrinter
Can we declare constructors with no parameters? Certainly. For
PointPrinter
, we might build a constructor that
initializes numprinted
to 1 with
public class PointPrinter { ... /** * Create a new PointPrinter that numbers points starting at 1. */ public PointPrinter() { this.numprinted = 1; } // PointPrinter() ... } // class PointPrinter
Note that there are two ways to initialize fields to default values: we can use assignment statements (using equals signs), or we can use parameterless constructors (constructors without parameters). Using constructors is the preferred mechanism.
We have defined two constructors for PointPrinter
, one which
has a single integer parameter and one which has no parameters. Both
seem to have the same name. Is this legal? Yes. Java permits multiple
constructors provided it is possible to tell them apart by the
parameters. If you could think of a reason, it would be legal to create
additional constructors with two integer parameters or with a string
parameter. However, it would not be legal to create another constructor
with a single integer parameter, because Java would not be able to tell
which one to use.
What happens if we do not define constructors for one of our classes?
It turns out that Java creates a default constructor with no parameters
that, in effect, sets all fields to "reasonable" defaults. This is why
we did not need to create a constructor for PointPrinter
or
MyPoint
. These default constructors are nice, but they
also have some drawbacks. In particular, if you define your own
constructors, then the default constructor is no longer available. This
point is important enough point that we'll repeat it: if you define any
constructor for a class (even a parameterized constructor), then there
is no longer a default, parameterless constructor for the class. We
will explore this issue further in the experiments.
In Experiment J3.4 you will add constructors
to the PointPrinter
class and investigate some of the issues
surrounding the no-argument constructors. In
Experiment J3.5, you will consider other issues
relating to constructors.
You've seen that it's possible to have multiple constructors for the same class. Since the name of a constructor is the same as the name of the class, it seems like we have two ``methods'' (constructor methods) in the same class with the same name.
Similarly, we've seen that different classes can provide
methods with the same name. For example, there is a print
method
in the SimpleOutput
class and a print
method in the
PointPrinter
class.
Can we have two methods with the same name in the same class (other than constructors)? Yes! This is a common programming technique called overloading.
Would we ever want to have different methods in the same class with the same
name? Yes. Consider the SimpleOutput
class. This class provides
a different print
method for String
s,
int
s, long
s, double
s, and all the
other things you might need to print. Imagine what would happen if you needed
to use a different method name for each type. Not only would you often need to
look up the appropriate name, depending on what you wanted to print, you
would also find it difficult to modify your code to work in slightly different
situations.
How do you overload a method? You simply define the method multiple times,
using different types of arguments each time. For example, we might want to
add a read
method to PointReader
that reads the
X and Y coordinates without prompting. Here is such a method.
/** * Read a point without prompting. */ public static Point read(SimpleInput in) { float x; // The x coordinate. float y; // The y coordinate. x = in.readFloat(); y = in.readFloat(); return new Point(x,y); } // read(SimpleInput)
How does Java decide which version of an overloaded method to use? First,
it looks at the class of the object whose method it calls. For example,
if you ask for printer.print
and printer
is
a PointPrinter
, then it will look in PointPrinter
.
If, however, printer
is a SimpleOutput
, then
it will look in SimpleOutput
. After determining which class
to use, Java looks at the signature, the name of the method plus the
types of the arguments. For example, if we call the print
method of a SimpleOutput
object with an int
as
a parameter, then Java looks for a method with signature print(int)
.
If Java can't find an exact match, it looks for an approximate match. For
example, if there is no print(int)
method, but there is a
print(double)
, Java will use that instead because Java knows
how to convert int
s to double
s.
In Experiment J3.6, you will add the overloaded
print
method to PointReader
.
In Experiment J3.7, you will investigate some of
the limitations of overloading.
This section is optional and is intended for classes emphasizing applets or pursuing a simultaneous discussion of applications and applets.
How can objects and classes help us as we build applets? We've already
seen that it's helpful to have the predefined Font
and
Color
classes. It is also useful to create our own
classes. For example, we can use classes to make it easier to draw
repeatedly a greater variety of figures. For example, we might create
classes that represent stick figures, more-interesting basic shapes,
and similar components for our drawings. We'll begin with a simple
shape: a filled square with a different-color border.
When designing a class, we need to consider fields, constructors, and methods.
What fields will a BorderedSquare
class need? It will
certainly need the color of the square and the color of the border. It
is also helpful to have the left edge, the top edge, and the length of
each side. (You may want to consider how we'd do without those values.)
What constructors will this class need? We'll start with one. It will take five parameters, corresponding to the five fields.
public BorderedSquare( int left, int top, int side, Color mainColor, Color border) { ... } // BorderedSquare(int,int,int,Color,Color)
What methods will this class need? We'll start with one. We'll need
a way to paint the square. We'll call this method paint
.
What parameters does it need? It needs the graphics paintbrush for
doing the actual painting.
public void paint(Graphics paintBrush) { ... } // paint(Graphics)
How do we paint the square? We'll simply use fillRect
filling in the left edge, top edge, and side length.
paintBrush.fillRect(left, top, sideLength, sideLength);
How might we draw a border around the square? You might assume that
we can use similar dimensions for a call to drawRect
that you were using for fillRect
, decreasing the
left and top edges by 1 and increasing the width and height by 2.
For example, suppose we wanted to explicitly draw a 40x40 black square
with a red border. We might write
public void paint(Graphics paintBrush) { paintBrush.setColor(Color.black); paintBrush.fillRect(5,5,40,40); paintBrush.setColor(Color.red); paintBrush.drawRect(4,4,42,42); } // paint(Graphics)
However, this is not precisely correct.
As you may recall, Java's coordinate system is somewhat odd, with the upper-left-corner being (0,0) and coordinates increasing to the right and downward. This should not affect our border. However, this is also not the only way in which Java's drawing conventions defy many beginning programmers' initial assumptions.
Surprisingly, the coordinates on the grid are not where the ink appears. Rather, the ink appears between these points. For example, the top-left pixel is not at (0,0). Rather, it appears between the four points (0,0), (0,1), (1,0), and (1,1). In fact, the ``graphics pen'' is placed down and to the right of the path it traverses.
Why does this make a
difference? Because fillRect
and fillOval
color only the pixels within the area described by the parameters, whereas
drawRect
and drawOval
color the pixels to
the left and down from the object described. For example, the lower-right
pixel drawn by fillRect(0,0,3,2)
is bounded
by (2,1), (2,2), (3,1), and (3,2). However, the lower-right pixel drawn
by drawRect(0,0,3,2)
falls to the right of and below the
lower-right corner (3,2) and therefore is bounded by (3,2), (3,3),
(4,2), and (4,3). What is the moral? Filled shapes are typically
one pixel smaller horizontally and vertically then drawn shapes.
In Experiment J3.8, you will consider this drawing difficulty. In Experiment J3.9, you will create and use a bordered square class.
[Front Door] [Introduction] [Code]
Copyright (c) 1998 Samuel A. Rebelsky. All rights reserved.
Source text last modified Mon Oct 25 15:18:20 1999.
This page generated on Tue Oct 26 15:38:22 1999 by Siteweaver.
Contact our webmaster at rebelsky@math.grin.edu