07.01.2015 Views

6 - Defining Classes - ECE Student Information

6 - Defining Classes - ECE Student Information

6 - Defining Classes - ECE Student Information

SHOW MORE
SHOW LESS

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

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!