6 - Defining Classes - ECE Student Information
6 - Defining Classes - ECE Student Information
6 - Defining Classes - ECE Student Information
You also want an ePaper? Increase the reach of your titles
YUMPU automatically turns print PDFs into web optimized ePapers that Google loves.
<strong>Defining</strong> <strong>Classes</strong><br />
As well as using predefined library classes in<br />
Java programs, it is usual to define new classes<br />
as well. The library classes model commonly<br />
occurring situations, the new classes are used<br />
for situations unique to a given program.<br />
The basic syntax for defining a class in Java is:<br />
class <br />
[]<br />
[]<br />
[]<br />
where isthenameoftheclass,a<br />
legal Java identifier; is<br />
one or more class and/or instance variable declarations;<br />
is one or more<br />
Constructors, and is one or more<br />
class and/or instance method declarations.
Simple <strong>Classes</strong>:<br />
The simplest sort of Java class contains only<br />
instance variables. This sort of class is analogous<br />
to the struct in C:<br />
Complex.java<br />
class Complex {<br />
public<br />
double r, // real part<br />
public<br />
i; // imaginary part<br />
}<br />
This defines a new type called “Complex”with<br />
two publicly accessible data members, “r”and<br />
“i”.<br />
Note that the class definition occurs in a file<br />
called “Complex.java”. This is mandatory for<br />
public classes: each such class must be defined<br />
in a file which has the same name as the class<br />
(and filename extension “.java”.) Only one<br />
public class definition is allowed per file.
Once the class is defined, it may be used in<br />
other Java source files exactly like any other<br />
class:<br />
Declare and create a Complex value.<br />
//<br />
//<br />
Complex a = new Complex();<br />
Declare, create and initialise an array of 3<br />
//<br />
Complex values.<br />
//<br />
//<br />
b = { new Complex(),<br />
Complex[]<br />
Complex(), new Complex() };<br />
new<br />
The instance variables of each Complex value<br />
//<br />
are accessible using the dot operator. Note that all<br />
//<br />
these fields have been initialised to 0.0 (doubles).<br />
//<br />
//<br />
== " + a.r<br />
System.out.println("a.r<br />
+ " a.i == " + a.i);<br />
(int j = 0; j < b.length; j++) {<br />
for<br />
= 1.0 + j;<br />
b[j].r<br />
b[j].i = -1.0 - j;<br />
}<br />
= b[0].r*b[1].r - b[0].i*b[1].i;<br />
a.r<br />
= b[0].r*b[1].i + b[0].i*b[1].r;<br />
a.i
Methods:<br />
The essential way in which Java improves on C<br />
is by allowing Methods as well as data in<br />
classes (C only allows data in structs.)<br />
It would be convenient to allow the user to add<br />
two Complex values without having to write<br />
out the addition in component form. To do this<br />
we write an “add” method and include it in the<br />
class definition.<br />
Complex.java<br />
class Complex {<br />
public<br />
double r, i;<br />
public<br />
Complex add(Complex v) {<br />
public<br />
result = new Complex();<br />
Complex<br />
= r + v.r;<br />
result.r<br />
= i + v.i;<br />
result.i<br />
return result;<br />
}<br />
}
The Signature of the method<br />
(name + number & types of arguments.)<br />
The method is available<br />
to all parts of a Java<br />
program.<br />
Createanew<br />
object<br />
to hold the sum.<br />
x<br />
e<br />
l<br />
p<br />
m<br />
o<br />
C<br />
{<br />
v<br />
)<br />
x<br />
l<br />
e<br />
m<br />
p<br />
C<br />
o<br />
d<br />
(<br />
a<br />
d<br />
x<br />
l<br />
e<br />
m<br />
p<br />
C<br />
o<br />
i<br />
c<br />
b<br />
l<br />
p<br />
u<br />
)<br />
;<br />
x<br />
(<br />
l<br />
e<br />
m<br />
p<br />
C<br />
o<br />
w<br />
n<br />
e<br />
=<br />
l<br />
t<br />
s<br />
u<br />
r<br />
e<br />
x<br />
l<br />
e<br />
m<br />
p<br />
C<br />
o<br />
”<br />
“r<br />
This is the<br />
instance var<br />
of the object<br />
which owns<br />
this method<br />
(the “current<br />
object.”)<br />
r<br />
;<br />
v<br />
.<br />
+<br />
r<br />
=<br />
.<br />
r<br />
l<br />
t<br />
s<br />
u<br />
r<br />
e<br />
i<br />
;<br />
v<br />
.<br />
+<br />
i<br />
=<br />
.<br />
i<br />
l<br />
t<br />
s<br />
u<br />
r<br />
e<br />
;<br />
l<br />
t<br />
s<br />
u<br />
r<br />
e<br />
r<br />
n<br />
t<br />
u<br />
r<br />
e<br />
}<br />
The method returns a reference<br />
to a object.<br />
x<br />
l<br />
e<br />
m<br />
p<br />
C<br />
o
The method is now available for use with<br />
Complexobjects.ItmaybeusedinotherJava<br />
source files exactly like any other instance<br />
method:<br />
Declare Complex variables a & b.<br />
//<br />
//<br />
a = new Complex(),<br />
Complex<br />
= new Complex();<br />
b<br />
= 2.0; a.i = 2.0; // a=2+j2<br />
a.r<br />
= 4.0; b.i = -3.0; // b=4− j3<br />
b.r<br />
Complex c = a.add(b); // c=a+b=6− j1<br />
As many methods as desired may be added to<br />
the class definition. This, for example, is multiplication:<br />
Complex mul(Complex v) {<br />
public<br />
result = new Complex();<br />
Complex<br />
= r * v.r - i * v.i;<br />
result.r<br />
= i * v.r + r * v.i;<br />
result.i<br />
return result;<br />
}
Signatures & Name Overloading:<br />
A method in Java is identified by its Signature,<br />
which is more than just its name. A Signature<br />
comprises both the method name and the<br />
number and types of arguments that the method<br />
accepts.<br />
The name of a method does not have to be<br />
unique, several methods can share the same<br />
name, as long as their signatures are distinct.<br />
This is known as Name Overloading.<br />
So Complex may have several different add<br />
methods. E.g., we may wish to have an add<br />
method which accepts a double as its argument<br />
instead of a Complex object. The signature<br />
of this method is:<br />
“add(double)”.<br />
Notice that neither the return type of this<br />
method nor the names of its formal parameter(s)<br />
are part of its signature.
Complex add(Complex v) {<br />
public<br />
result = new Complex();<br />
Complex<br />
= r + v.r;<br />
result.r<br />
= i + v.i;<br />
result.i<br />
return result;<br />
}<br />
Complex add(double real) {<br />
public<br />
result = new Complex();<br />
Complex<br />
= r + real;<br />
result.r<br />
= i;<br />
result.i<br />
return result;<br />
}<br />
Sometimes we refer to the signature of a<br />
method as its entire “first line”.<br />
public Complex add(double real)<br />
This is useful when we want to specify what a<br />
method should “look like” to the outside world,<br />
but the names of the arguments (formal parameters)<br />
are not really relevant, and the return<br />
type of the method isn’t, strictly speaking, part<br />
of the signature.
If a class contains overloaded methods the Java<br />
compiler decides which one to call by looking<br />
at the types and number of parameters in a<br />
message.<br />
a = new Complex(),<br />
Complex<br />
= new Complex();<br />
b<br />
= 2.0; a.i = 2.0; // a=2+j2<br />
a.r<br />
= 4.0; b.i = -3.0; // b=4− j3<br />
b.r<br />
In this example call add(Complex) because the<br />
//<br />
add message has a single Complex parameter.<br />
//<br />
Complex c = a.add(b); // c=a+b=6− j1<br />
Whereas in this one call add(double) because<br />
//<br />
this add message has a single double parameter.<br />
//<br />
Complex d = b.add(2.0); // d=6− j3<br />
In this case we also call add(double) because<br />
//<br />
this add message has a single float parameter,<br />
//<br />
which may be promoted (via an implicit cast) to<br />
//<br />
type double but not to type Complex.<br />
//<br />
a = a.add(10.0F); // a=12+j2
Note that the complier attempts to get the<br />
“closest match” it can between signatures and<br />
message parameters. Hence, if we have a<br />
method with signature “add(float)”, as well<br />
as one with signature “add(double)”, Java<br />
will use the former as the method called by a<br />
message like “a.add(10.0F)”, rather than the<br />
latter.<br />
The “this” reference:<br />
Used within an instance method, the keyword<br />
“this” is a reference to the Current Object (the<br />
object the method belongs to). It is used in<br />
three ways in instance methods:<br />
1. In return statements where it is desired to<br />
return a reference to the current object.<br />
2. To disambiguate references to the current<br />
object’s instance variables where these<br />
have been “hidden” by a method’s local<br />
variables or formal parameters.<br />
3. From a Constructor, this is used to call a<br />
constructor with a different argument list.
Returning a reference to the current<br />
object:<br />
The “add” methods described so far create new<br />
Complex objects to hold the results of the addition.<br />
We may wish to write a version add of<br />
which modifies the current object instead<br />
(“addOn”). It would be natural in these circumstancestowanttoreturnareferencetothecurrent<br />
object. This is the purpose of “return the<br />
this” idiom.<br />
Complex addOn(Complex b) {<br />
public<br />
+= b.r; // Modify the<br />
r<br />
i += b.i;<br />
// Current Object.<br />
return this;<br />
}<br />
Two ways to achieve the same result. Add b to a,<br />
//<br />
modifying a in the process, and put a reference to<br />
//<br />
the result of the addition in c as well.<br />
//<br />
c = a.addOn(b); // Version 1,<br />
c = a = a.add(b); // Version 2, less efficient.
Disambiguating references to the current<br />
object’s instance variables:<br />
What happens if a local variable or a formal<br />
parameter in a method shares a name with an<br />
instance variable of the class<br />
class Complex {<br />
public<br />
double r, i;<br />
public<br />
Complex add(double r) {<br />
public<br />
result = new Complex();<br />
Complex<br />
= r + r;<br />
result.r<br />
= i;<br />
result.i<br />
return result;<br />
}<br />
}<br />
All uses of “r” within the method refer to the<br />
most recently declared “r”, i.e., the formal<br />
parameter. The instance variable “r”isHidden<br />
or Aliased by the formal parameter. The same<br />
thing happens if “r” is a local variable.
How to get around this problem and access the<br />
instance variable Use the “this” reference<br />
with the dot operator:<br />
class Complex {<br />
public<br />
double r, i;<br />
public<br />
Complex add(double r) {<br />
public<br />
result = new Complex();<br />
Complex<br />
= this.r + r;<br />
result.r<br />
= i;<br />
result.i<br />
}<br />
.<br />
return result;<br />
The use of “this” & the dot operator is<br />
always legal when accessing instance variables<br />
(e.g., “result.i = i” is actually shorthand for<br />
“result.i = this.i”), but is only necessary<br />
when an ambiguity would otherwise occur.
Constructors:<br />
An Object Constructor is a special method<br />
which Java will call automatically when a new<br />
instance of the class containing that constructor<br />
is created.<br />
Constructors are used to help to build new<br />
objects. Java will first allocate memory for the<br />
object’s variables and will then call the appropriate<br />
constructor to initialise these variables to<br />
desired values.<br />
A constructor looks like a method, but has a<br />
special name. The name of a constructor is<br />
always the same as the name of its class.<br />
A class can have as many constructors as its<br />
designer thinks fit. All the constructors of a<br />
class must have unique signatures. As all the<br />
constructors in a class share the same name,<br />
this means that they must have distinct argument<br />
lists.
class Complex {<br />
public<br />
double r, i;<br />
public<br />
Constructor without arguments (default<br />
//<br />
constructor). Set both the real and imaginary<br />
//<br />
components of the Complex value to 0.0.<br />
//<br />
Complex() {<br />
public<br />
= i = 0.0;<br />
r<br />
}<br />
Constructor taking 2 arguments, the real and<br />
//<br />
imaginary components of a Complex number.<br />
//<br />
Complex(double r, double i) {<br />
public<br />
= r; this.i = i;<br />
this.r<br />
}<br />
Constructor taking a single argument, the real<br />
//<br />
part of a Complex number. The imaginary<br />
//<br />
component is set to zero.<br />
//<br />
Complex(double real) {<br />
public<br />
= real; i = 0.0;<br />
r<br />
}<br />
.
Note that the constructors have no return type,<br />
not even void. It is not possible to call a constructor<br />
explicitly, so there is no point in specifying<br />
what type a call should return.<br />
The sort of constructor that the system will<br />
invoke is determined by the argument list<br />
passed to the new statement when an object is<br />
created:<br />
Complex a, b, c;<br />
Empty argument list to “new Complex” statement,<br />
//<br />
system will use default constructor, a=0+j0.<br />
//<br />
a = new Complex();<br />
Two arguments to “new Complex” statement,<br />
//<br />
system will use 2-argument constructor, b=2+j2.<br />
//<br />
b = new Complex(2.0, 2.0);<br />
Single argument to “new Complex” statement,<br />
//<br />
system will use 1-arg constructor, c= −4+j0.<br />
//<br />
c = new Complex(-4.0);
All but the most trivial classes should include a<br />
constructor.<br />
If a class does not have any constructors, the<br />
system will automatically create a default constructor<br />
for the class. This constructor will<br />
invoke the default constructor of the object’s<br />
superclass.<br />
“This” used in Constructors:<br />
It may sometimes be useful to have one constructor<br />
call another. For example, we might<br />
observe that using the default constructor to<br />
create a Complex object is the same as using<br />
the 2-argument constructor with argument list<br />
(0, 0). Similarly, using the 1-argument constructor<br />
with value x is the same as using the 2-<br />
argument constructor with argument list (x, 0).<br />
Hence, both the default and the 1-argument<br />
constructor might achieve their ends by calling<br />
the 2-argument constructor. But directly calling<br />
a constructor is, in general, not possible.
Fortunately, the designers of Java have anticipated<br />
this problem, and provide a special syntax<br />
to allow one constructor to call another,<br />
using the “this” reference:<br />
class Complex {<br />
public<br />
double r, i;<br />
public<br />
Constructor taking 2 arguments, the real and<br />
//<br />
imaginary components of a Complex number.<br />
//<br />
Complex(double r, double i) {<br />
public<br />
= r; this.i = i;<br />
this.r<br />
}<br />
// Default constructor.<br />
Complex() { this(0.0, 0.0); }<br />
public<br />
Single argument constructor.<br />
//<br />
Complex(double r) {<br />
public<br />
0.0);<br />
this(r,<br />
}<br />
Useful when a lot of very similar processing is<br />
needed by a number of related constructors.
Formal & Actual Parameter Lists:<br />
The list of arguments in a method declaration is<br />
called the Formal Parameter List of the<br />
method. The list of arguments in a message to<br />
an object is called the Actual Parameter List of<br />
the method call.<br />
Formal parameter list of method.<br />
Complex add(double real) {<br />
public<br />
new Complex(r + real, i);<br />
return<br />
}<br />
Message to object a contains the actual parameter<br />
//<br />
list of the method call, in this case the double<br />
//<br />
// value increment.<br />
c = a.add(increment);<br />
Actual parameter list of method call
When calling a method, Java first copies the<br />
values in the actual parameter list of the call<br />
into the “slots” in the formal parameter list of<br />
the method, then it executes the method’s code.<br />
real: 24<br />
.<br />
increment: 24<br />
slot for formal parameter<br />
of method “add”<br />
value stored in “increment”<br />
copied into “real”before<br />
“add” starts running.<br />
storage space for double<br />
variable “increment”<br />
memory<br />
This means that if a formal parameter of a<br />
method is a primitive type, changes made to the<br />
formal parameter within the method will not<br />
affect the value of the actual parameter passed<br />
to the method, because it is the copy which gets<br />
modified (“Pass by Value”).
But if the argument to a method is any sort of<br />
object, then it is the reference to that object that<br />
gets copied into the slot in the formal parameter<br />
list, not the object itself. Hence, any alteration<br />
made to the object in the method causes a<br />
global alteration to the state of the object.<br />
v: 0x346278<br />
.<br />
b: 0x346278<br />
slot for formal parameter “v”<br />
of method “add”<br />
value stored in “b”<br />
copied into “v”before<br />
“add” starts running.<br />
storage space for reference<br />
variable “b”<br />
memory<br />
Storage space for object<br />
referenced by “b”and“v”is<br />
at memory location 0x346278.<br />
Calling method “add(Complex v)” with<br />
message “a.add(b).” The variable b and the<br />
formal parameter v are reference types.<br />
(“Pass by Reference”).
Methods and Arrays:<br />
Methods may be defined to return arrays as<br />
well as scalars:<br />
double[] getRealandImag() {<br />
public<br />
elements = new double[2];<br />
double[]<br />
= r; elements[1] = i;<br />
elements[0]<br />
elements;<br />
return<br />
}<br />
An equivalent definition is:<br />
double getRealandImag()[] {<br />
public<br />
elements = { r, i };<br />
double[]<br />
return elements;<br />
}<br />
Here the method getRealandImag creates and returns a<br />
2-element double array which contains the real and<br />
imaginary components of the Complex object to which<br />
the method belongs.<br />
Note the two equivalent forms for declaring that the<br />
method returns an array. The former is preferred.
Because arrays are objects, hence pass by reference,<br />
we can also pass an array as a parameter<br />
to a method and have the method change its<br />
contents:<br />
void getRealandImag(<br />
public<br />
elements) {<br />
double[]<br />
elements[0] = r; elements[1] = i;<br />
}<br />
Note that the array “elements” must exist<br />
before the method is called (and be big enough<br />
to hold the result).<br />
elts = new double[2];<br />
double[]<br />
c = new Complex(-2.0, 5.4);<br />
Complex<br />
c == "<br />
System.out.println("Before:<br />
elts[0] + ", " + elts[1]);<br />
+<br />
c.getRealandImag(elts);<br />
c == "<br />
System.out.println("After:<br />
+ elts[0] + ", " + elts[1]);
Viewing the value of an object, the<br />
“toString” method:<br />
It is good practice for all classes to define a<br />
“toString” instance method with the following<br />
“signature”:<br />
When sent as a message to an object, “to-<br />
String” should return a String representation<br />
of the object’s current value.<br />
String toString()<br />
c(3.0, -4.8);<br />
Complex<br />
s = c.toString();<br />
String<br />
System.out.println(s);<br />
What makes toString interesting is that the<br />
Java language will automatically call it if it<br />
sees an attempt to concatenate a String object<br />
with a non-String.<br />
Java overloads the “+” operator to perform<br />
string concatenation when one of its elements<br />
is a String.
System.out.println("c == " + c);<br />
Concatenation of String "c == " with Complex<br />
object c.Callc.toString() to convert c.<br />
Here is an implementation of toString for<br />
Complex. It builds the character representation<br />
of the object in a StringBuffer first, then<br />
converts this to a String, which it returns:<br />
String toString() {<br />
public<br />
s<br />
StringBuffer<br />
= new StringBuffer(20);<br />
s.append(r);<br />
( i != 0.0 ) {<br />
if<br />
( i < 0.0 ) s.append(" -");<br />
if<br />
s.append(" +");<br />
else<br />
j");<br />
s.append("<br />
s.append(Math.abs(i));<br />
}<br />
s.toString();<br />
return<br />
}<br />
Complex c prints as “3.0 - j4.8”.
Data Abstraction & <strong>Information</strong><br />
Hiding<br />
Onewaytouseclassesisasawayofimplementing<br />
Abstract Data Types (ADTs). The<br />
Complex type developed earlier is a sort of<br />
ADT, we have Complex objects and methods<br />
for manipulating these objects. However, it is<br />
not a pure ADT, because the user of a Complex<br />
object can directly manipulate the data it<br />
encapsulates (i.e., the instance variables of the<br />
type.)<br />
This is not regarded as the best way to design<br />
an Abstract Data Type because it commits us to<br />
a particular way of representing a Complex<br />
object internally, (in terms of its Cartesian<br />
components.) However, this is just one way of<br />
representing a Complex object, we might<br />
instead prefer to use polar components (magnitude<br />
and angle), for some purposes. The problem<br />
with our current implementation is that,<br />
once it has been released for use, we can no
longer make this change, we are committed to<br />
one particular form of internal representation.<br />
In a pure ADT the user is denied access to any<br />
internal implementation details (<strong>Information</strong><br />
Hiding.) Instead, we present an Application<br />
Programming Interface (API) to the user.<br />
User Accesses<br />
ADT<br />
via<br />
API<br />
Application<br />
Programming<br />
ADT<br />
Kernel<br />
(private<br />
variables &<br />
methods)<br />
Interface<br />
(Public Methods)<br />
This interface acts as a “buffer” between the<br />
user of an ADT object and its internal implementation<br />
details. The user is only allowed to
manipulate an ADT object by calling methods<br />
defined in its API. Any other methods and data<br />
belonging to the object are hidden, and only<br />
accessible indirectly.<br />
(N.B., many of the Java library classes give you<br />
access to instance variables, but normally restrict<br />
you to read-only access, i.e., you are not allowed<br />
to assign to an instance variable. This is usually<br />
regarded as acceptable in ADT design.)<br />
Hence, the API of an abstract data type provides<br />
a Controlled and Documented way for<br />
the user of that ADT to employ ADT objects.<br />
The user is limited to using the ADT in predefined<br />
ways allowed by its designer: “Restricted<br />
Access.”<br />
Advantages of the API approach to ADT<br />
design:<br />
• Decoupling of interface & implementation.<br />
• Access control over ADT data.
Decoupling of Interface and Implementation:<br />
A well designed application programming<br />
interface is a “Contract” between the designer<br />
of an abstract data type and its users. The<br />
designer knows that the users of the type can<br />
only access it via its API. Hence, the designer<br />
is free to change the implementation of the<br />
ADT at any time, without affecting any userlevel<br />
software that employs that ADT.<br />
If the API is kept the same during revisions of<br />
the implementation, the user won’t even know<br />
that anything has changed (except that the<br />
ADT might become faster, or more efficient),<br />
its logical behaviour will remain constant.<br />
The designer guarantees that the ADT will<br />
behave as advertised in its API, but reserves<br />
the right to implement the ADT in the most<br />
appropriate fashion. The interface and implementation<br />
have been Decoupled.
a<br />
w<br />
r<br />
d<br />
h<br />
current-<br />
Balance<br />
t<br />
C<br />
Access Control over ADT data:<br />
Consider an ADT which models a bank<br />
account. It contains an instance variable “currentBalance”.<br />
It would be highly inappropriate<br />
if the users of the bank account object had<br />
the freedom to access this instance variable<br />
directly.<br />
C ash<br />
e d p<br />
o<br />
s<br />
i<br />
a<br />
s<br />
i<br />
t<br />
w<br />
h<br />
Instead, the designer “wraps” an API around<br />
currentBalance so that its value can only be<br />
changed via well controlled “depositCash”<br />
and “withdrawCash.” The former<br />
methods:<br />
method only allows positive amounts of cash to
e deposited in the account, and the latter only<br />
allows withdrawals of positive amounts of cash<br />
less than or equal to currentBalance.<br />
Here we have implemented an Access Control<br />
system on the ADT. The user can only get at<br />
the instance variable currentBalance through<br />
methods which restrict the sorts of operations<br />
that are legal on this variable.<br />
The “private” keyword, protecting<br />
class data:<br />
class Complex {<br />
public<br />
double r, i;<br />
private .<br />
}<br />
By declaring a variable “private”inaclass,<br />
we are saying that the only methods which are<br />
allowed to access (read or write) the variable
are methods belonging to this class. Asfaras<br />
all other methods in the program are concerned,<br />
the variable is invisible.<br />
Accessor and Mutator methods, accessing<br />
class data:<br />
class BankBalance {<br />
public<br />
double currentBalance;<br />
private<br />
BankBalance() {<br />
public<br />
= 0.0;<br />
currentBalance<br />
}<br />
Accessor method, find current balance in acc.<br />
//<br />
double getBalance() {<br />
public<br />
return currentBalance;<br />
}<br />
Mutator method, deposit an amount, c, of cash.<br />
//<br />
void depositCash(double c) {<br />
public<br />
( c > 0.0 )<br />
if<br />
+= c;<br />
currentBalance<br />
}<br />
.
class Complex {<br />
public<br />
double r, i;<br />
private<br />
public Complex() { r = i = 0.0; }<br />
Complex(double r, double i) {<br />
public<br />
= r; this.i = i;<br />
this.r<br />
}<br />
Accessor methods.<br />
//<br />
double getReal() {<br />
public<br />
return r;<br />
}<br />
double getImag() {<br />
public<br />
i;<br />
return<br />
}<br />
double getMag() {<br />
public<br />
Math.sqrt(r*r + i*i);<br />
return<br />
}<br />
See the java.lang.Math documentation for<br />
//<br />
information on atan2.<br />
//<br />
double getAngle() {<br />
public<br />
Math.atan2(i, r);<br />
return<br />
}
Mutator methods.<br />
//<br />
Complex setRealandImag(<br />
public<br />
r, double i) {<br />
double<br />
= r; this.i = i;<br />
this.r<br />
return this;<br />
}<br />
Complex setMagandAngle(<br />
public<br />
m, double a) {<br />
double<br />
= m * Math.cos(a);<br />
this.r<br />
= m * Math.sin(a);<br />
this.i<br />
this;<br />
return<br />
}<br />
// Rest of class definition here.<br />
.<br />
// End class Complex.<br />
}<br />
In this version of class Complex the user cannot<br />
tell (without reading the source code) what sort<br />
of internal representation is being used, Cartesian<br />
or polar. If the class designer were to<br />
decide to change the internal representation of<br />
this class, the user wouldn’t notice the change.<br />
In fact, programs written to use this class<br />
would not even need to be recompiled.
Developing <strong>Classes</strong><br />
This is a four-step process:<br />
1. Define the problem (problem statement).<br />
2. Define the API for the ADT.<br />
3. Decide on an appropriate internal data representation.<br />
4. Implement the methods.<br />
As an example we will present the design of a<br />
Matrix class.<br />
Define the problem:<br />
Thefirstthingtodoistodecideclearlywhatit<br />
is that the ADT models. This should be written<br />
out as a succinct statement:<br />
Design a Matrix ADT which implements<br />
simple matrix arithmetic, viz., addition,<br />
subtraction and multiplication of matrices.<br />
All indices should be 1 rather than 0-based.
Define the API:<br />
The API is the view that the user will have of<br />
the new ADT. It is important to try to get everything<br />
needed in here. Note that this definition<br />
is made before we know how the ADT will be<br />
implemented:<br />
Methods:<br />
add(Matrix b)<br />
Matrix<br />
Add a matrix, b, to the current matrix, cm, assuming<br />
they are the same size. The result will be returned<br />
in a new matrix, neither cm nor b being changed. If cm<br />
and b are not the same size, generate an error message<br />
and halt.<br />
sub(Matrix b)<br />
Matrix<br />
Similar to add, except that b is subtracted from cm.<br />
Matrix mul(Matrix b)<br />
Multiply cm by b, returning the result in a new<br />
matrix. Cm and b remain unchanged. If cm and b do<br />
not conform (i.e., if the number of columns in cm is<br />
not the same as the number of rows in b), generate an<br />
error message and halt.
Constructor:<br />
r, int c)<br />
Matrix(int<br />
Make a new Matrix object with r rows and c columns.<br />
The elements of the Matrix will all be doubles,<br />
and will be initialised to zeroes. If either r or c is negative<br />
or zero, generate an error message and halt.<br />
Accessor & Mutator methods:<br />
getRows()<br />
int<br />
Return the number of rows in the current Matrix.<br />
getCols()<br />
int<br />
Return the number of columns in the current Matrix.<br />
getElt(int r, int c)<br />
double<br />
Return the value of location (r, c) in the current<br />
Matrix. If either r or c is outside the bounds of the current<br />
Matrix, generate an error message and halt. Note<br />
that indices in the Matrix are 1-based, so that the first<br />
element is at location (1, 1), not (0, 0).<br />
setElt(int r, int c,<br />
Matrix<br />
val)<br />
double<br />
Set location (r, c) in the current Matrix to value val.<br />
If either r or c is outside the bounds of the current<br />
Matrix, generate an error message and halt. Note that<br />
indices in the Matrix are 1-based, as in getElt. This<br />
method returns a reference to the modified Matrix.
Utility method:<br />
toString()<br />
String<br />
Generate a printable representation of the current<br />
Matrix object and return it. The current object is not<br />
modified by this method.<br />
Decide on an internal data representation:<br />
We decide to use a 2-d array of double values,<br />
with rows × columns elements in it. Each<br />
Matrix object will also have to keep track of<br />
how big it is:<br />
class Matrix {<br />
public<br />
double[][] elts;<br />
private<br />
private int rows, columns;<br />
Implement the methods:<br />
The “add” and “sub” methods are very similar.<br />
In particular, they both need to check that the<br />
matrices being added are of the same size.
Matrix add(Matrix b) {<br />
public<br />
checkSize(b);<br />
res = new Matrix(rows,<br />
Matrix<br />
columns);<br />
(int r = 0; r < rows; r++)<br />
for<br />
(int c = 0; c < columns; c++)<br />
for<br />
= elts[r][c] +<br />
res.elts[r][c]<br />
b.elts[r][c];<br />
return res;<br />
}<br />
Matrix sub(Matrix b) {<br />
public<br />
checkSize(b);<br />
res = new Matrix(rows,<br />
Matrix<br />
columns);<br />
(int r = 0; r < rows; r++)<br />
for<br />
(int c = 0; c < columns; c++)<br />
for<br />
= elts[r][c] -<br />
res.elts[r][c]<br />
b.elts[r][c];<br />
return res;<br />
}<br />
Both of these methods need a way to check that<br />
the current matrix and the argument to the<br />
method have the same number of rows and columns.<br />
This job has been abstracted into method<br />
“checkSize.” But checkSize is an operation
which is private to the class, a user of the class<br />
should not be able to call it directly. We can<br />
make checkSize inaccessible to outside users<br />
by declaring it with the private keyword:<br />
void checkSize(Matrix b) {<br />
private<br />
( rows != b.rows ||<br />
if<br />
!= b.columns )<br />
columns<br />
mismatch");<br />
fatalError("Add/sub:<br />
}<br />
This is now a method which can only be<br />
accessed from within Matrix. It provides a<br />
service to the publicly accessible methods of<br />
the class.<br />
“fatalError” is another example of a private<br />
utility method. If called, it prints a message to<br />
the error stream and stops the program.<br />
void fatalError(String s) {<br />
private<br />
System.err.println(<br />
fatal error: " + s);<br />
"Matrix:<br />
System.exit(0);<br />
}
The method “mul” performs the multiplication<br />
operation between matrices. This method has<br />
little commonality with others, in particular, its<br />
test to see if the two matrices can be multiplied<br />
(i.e., that they “conform”), is unlike the size<br />
checks for addition and subtraction. Instead,<br />
the method must check to see if the number of<br />
columns in the multiplicand (the current<br />
object) is the same as the number of rows in the<br />
multiplier (the argument to the method.)<br />
Matrix mul(Matrix b) {<br />
public<br />
( columns != b.rows )<br />
if<br />
fatalError("Nonconforming matrices");<br />
res = new Matrix(rows,<br />
Matrix<br />
b.columns);<br />
(int r = 0; r < res.rows; r++) {<br />
for<br />
(int c = 0; c < res.columns; c++) {<br />
for<br />
acc = 0.0;<br />
double<br />
(int i = 0; i < columns; i++)<br />
for<br />
+= elts[r][i] * b.elts[i][c];<br />
acc<br />
= acc;<br />
res.elts[r][c]<br />
}<br />
}<br />
res;<br />
return<br />
}
The accessor methods “getRows” and “get-<br />
Cols” are straightforward. Their only purpose<br />
is to protect the inner workings of the class<br />
from modification by the user (which would be<br />
possible if the instance variables “rows”and<br />
“columns” weremadepublic.)<br />
int getRows() {<br />
public<br />
rows;<br />
return<br />
}<br />
int getCols() {<br />
public<br />
columns;<br />
return<br />
}<br />
(There is a way to allow “read-only” access to<br />
the instance variables rows and columns, but<br />
we won’t use it here.)<br />
The accessor methods “getElt” and “setElt”<br />
are quite similar to one another. Both need to<br />
check that their indices are within bounds, and<br />
both need to convert the 1-based Matrix indices<br />
which are passed as parameters to them, to<br />
0-based indices suitable for accessing the
“elts” array where the elements of the Matrix<br />
are stored.<br />
double getElt(int r, int c) {<br />
public<br />
= checkRowIndex(r);<br />
r<br />
= checkColumnIndex(c);<br />
c<br />
elts[r][c];<br />
return<br />
}<br />
Matrix setElt(int r, int c,<br />
public<br />
val) {<br />
double<br />
= checkRowIndex(r);<br />
r<br />
= checkColumnIndex(c);<br />
c<br />
= val;<br />
elts[r][c]<br />
this;<br />
return<br />
}<br />
The utility methods are “checkRowIndex”and<br />
“checkRowIndex.” As well as checking that<br />
their indices are within the appropriate ranges,<br />
these methods perform the 1-based to 0-based<br />
index conversion. Here is “checkRowIndex”:<br />
int checkRowIndex(int r) {<br />
private<br />
( r < 1 || r > rows )<br />
if<br />
index " + r<br />
fatalError("Row<br />
" out of range");<br />
+<br />
return r - 1;<br />
}
Finally there is the “toString” method, whose<br />
job it is to generate a printable representation<br />
of a Matrix. We choose to print a Matrix in<br />
the following, single-line, form:<br />
-4.2], [0.0, 1.0], [0.7, 4.5]]<br />
[[2.0,<br />
(Thisisa3x2Matrix.)Hereisthecode:<br />
String toString() {<br />
public<br />
buf = new StringBuffer(80);<br />
StringBuffer<br />
(int r = 0; r < rows; r++ ) {<br />
for<br />
== 0 "[" : ", "));<br />
buf.append((r<br />
(int c = 0; c < columns; c++) {<br />
for<br />
== 0 "[" : ", "));<br />
buf.append((c<br />
buf.append(elts[r][c]);<br />
}<br />
buf.append("]");<br />
}<br />
buf.append("]");<br />
return buf.toString();<br />
}<br />
As with the toString method from class Complex,<br />
weuseaStringBuffer to accumulate<br />
the result, because it can grow in size as<br />
needed, and later turn it into a String.80isan<br />
initial guess at how big the buffer should be.
Constructor. This class has no default<br />
//<br />
constructor.<br />
//<br />
= new double[rows][columns];<br />
elts<br />
else<br />
Here is the whole class definition for Matrix:<br />
Matrix.java<br />
class Matrix {<br />
public<br />
Instance variables. Data elements are<br />
//<br />
stored in a 2-d array with rows x columns<br />
//<br />
elements.<br />
//<br />
double[][] elts;<br />
private<br />
int rows, columns;<br />
private<br />
Matrix(int r, int c) {<br />
public<br />
= r;<br />
rows<br />
= c;<br />
columns<br />
( rows > 0 && columns > 0 )<br />
if<br />
fatalError("Constructor " + r + ", " + c);<br />
}<br />
// Accessor methods.<br />
Accessor method, get the number of rows in the<br />
//<br />
Matrix.<br />
//<br />
int getRows() {<br />
public<br />
rows;<br />
return<br />
}<br />
Accessor method, get the number of columns in<br />
//<br />
the Matrix.<br />
//<br />
int getCols() {<br />
public<br />
columns;<br />
return<br />
}
= checkRowIndex(r);<br />
r<br />
= checkColumnIndex(c);<br />
c<br />
(int c = 0; c < columns; c++)<br />
for<br />
= elts[r][c] - b.elts[r][c];<br />
res.elts[r][c]<br />
Matrix.java continued:<br />
Accessor method, get an element of the Matrix.<br />
//<br />
double getElt(int r, int c) {<br />
public<br />
return elts[r][c];<br />
}<br />
Mutator method, set an element of the Matrix.<br />
//<br />
Matrix setElt(int r, int c, double val) {<br />
public<br />
= checkRowIndex(r);<br />
r<br />
= checkColumnIndex(c);<br />
c<br />
= val;<br />
elts[r][c]<br />
this;<br />
return<br />
}<br />
add method. Add a Matrix passed as an argument<br />
//<br />
to the current Matrix object and return a new<br />
//<br />
result Matrix.<br />
//<br />
Matrix add(Matrix b) {<br />
public<br />
checkSize(b);<br />
res = new Matrix(rows, columns);<br />
Matrix<br />
(int r = 0; r < rows; r++)<br />
for<br />
(int c = 0; c < columns; c++)<br />
for<br />
= elts[r][c] + b.elts[r][c];<br />
res.elts[r][c]<br />
res;<br />
return<br />
}<br />
sub method. Very similar in structure to add,<br />
//<br />
but performs subtraction.<br />
//<br />
Matrix sub(Matrix b) {<br />
public<br />
checkSize(b);<br />
res = new Matrix(rows, columns);<br />
Matrix<br />
(int r = 0; r < rows; r++)<br />
for<br />
return res;<br />
}
eturn a new result Matrix.<br />
//<br />
Matrix mul(Matrix b) {<br />
public<br />
toString method. Generate a String object which<br />
//<br />
represents the current Matrix object.<br />
//<br />
(int c = 0; c < columns; c++) {<br />
for<br />
"[" : ", "));<br />
buf.append((c==0<br />
Matrix.java continued:<br />
mul method. Multiply the current Matrix object by<br />
//<br />
a Matrix passed as an argument to the method and<br />
//<br />
( columns != b.rows )<br />
if<br />
do not conform");<br />
fatalError("Matrices<br />
Matrix res = new Matrix(rows, b.columns);<br />
(int r = 0; r < res.rows; r++) {<br />
for<br />
(int c = 0; c < res.columns; c++) {<br />
for<br />
acc = 0.0;<br />
double<br />
(int i = 0; i < columns; i++)<br />
for<br />
+= elts[r][i] * b.elts[i][c];<br />
acc<br />
= acc;<br />
res.elts[r][c]<br />
}<br />
}<br />
res;<br />
return<br />
}<br />
String toString() {<br />
public<br />
buf = new StringBuffer(80);<br />
StringBuffer<br />
(int r = 0; r < rows; r++ ) {<br />
for<br />
"[" : ", "));<br />
buf.append((r==0<br />
buf.append(elts[r][c]);<br />
}<br />
buf.append("]");<br />
}<br />
buf.append("]");<br />
return buf.toString();<br />
}
fatalError prints an error message (which it<br />
//<br />
receives as an argument) to the standard error<br />
//<br />
fatal error: "<br />
System.err.println("Matrix:<br />
errorMessage);<br />
+<br />
int checkRowIndex(int r) {<br />
private<br />
( r < 1 || r > rows )<br />
if<br />
Matrix.java continued:<br />
// Private methods, unique to this class.<br />
stream, then halts the program. This is not an<br />
//<br />
ideal way to handle errors, it would be better<br />
//<br />
to use Java’s exception mechanism.<br />
//<br />
void fatalError(String errorMessage) {<br />
private<br />
System.exit(0);<br />
}<br />
checkSize checks to see if its argument (a Matrix)<br />
//<br />
has the same number of rows and columns as the<br />
//<br />
current Matrix object. Needed for addition and<br />
//<br />
subtraction.<br />
//<br />
void checkSize(Matrix b) {<br />
private<br />
( rows != b.rows || columns != b.columns )<br />
if<br />
fatalError("Add/sub: size mismatch");<br />
}<br />
checkRowIndex checks that its argument is a valid<br />
//<br />
row index (i.e., 1
then performs 1- to 0-based index conversion and<br />
//<br />
returns a 0-based index.<br />
//<br />
Matrix.java continued:<br />
checkColumnIndex checks that its argument is a<br />
//<br />
valid column index (i.e., 1
") = " + a.getElt(r, c)<br />
+<br />
" ");<br />
+<br />
+ b = " + a.add(b));<br />
System.out.println("a<br />
- b = " + a.sub(b));<br />
System.out.println("a<br />
Matrix.java continued:<br />
main method contined from previous page.<br />
//<br />
is now: " + a);<br />
System.out.println("a<br />
System.out.println("b is now: " + b);<br />
elements of a & b");<br />
System.out.println("Accessing<br />
(r = 1; r