This chapter's title comes from a slogan that practicing programmers follow when they build an application:
Say that you receive a portable disc player as a gift. When you try to operate the player, nothing happens --- the player requires batteries. What batteries fit into the player? Fortunately, on the back of the player is the specification, ``This player requires two AA batteries.'' With this information, you can obtain the correctly sized components (the batteries) and fit them into the player. The completed ``assembly'' operates.
The specification of the disc player's batteries served several useful purposes:
Without size specifications of items like batteries, clothing, and auto parts, everyday life would would be a disaster.
When we assemble a program from components, the components must fit together. When a class, A, invokes methods from an object constructed from class B, class A assumes that B possesses the methods invoked and that the methods behave in some expected way --- in the way they are specified. Just like batteries have specifications (e.g., sizes AAA, AA, C,...), classes have specifications also. This explains why we have been writing specifications for the Java classes we code.
For example, in Chapter 7, we encountered a case study of a simulation of a ball bouncing in a box. The specifications of the ball and the box proved crucial to both writing the simulation's classes and fitting the classes together in the program.
The Java language and the Java compiler can help us write specifications of classes and check that a class correctly matches (implements) its specification. In this chapter we study several Java constructions for designing programs in separate classes:
We will study each of the constructions in turn in this chapter.
In the previous chapters, we used informal specifications to design classes and to connect them to their collaborator classes. But the Java language provides a construct, called an interface, that lets us include a specification as an actual Java component of an application---we type the interface into a file and compile it, just like a class. Then, we use the compiled interface in two ways:
FIGURE 1: Java interface===============================================
/** BankAccountSpecification specifies the behavior of a bank account. */
public interface BankAccountSpecification
{ /** deposit adds money to the account
* @param amount - the amount of the deposit, a nonnegative integer */
public void deposit(int amount);
/** withdraw deducts money from the account, if possible
* @param amount - the amount of the withdrawal, a nonnegative integer
* @return true, if the the withdrawal was successful;
* return false, otherwise. */
public boolean withdraw(int amount);
}
ENDFIGURE==============================================================
The interface states that, whatever class is finally written
to implement a BankAccountSpecification, the class must
contain two methods, deposit and withdraw, which
behave as stated.
Compare Figure 1 to Table 10 of Chapter 6, which presented a similar,
but informal, specification.
A Java interface is a collection of header lines of methods for a class that is not yet written. The interface is not itself a class---it is a listing of methods that some class might have.
The syntax of a Java interface is simply
public interface NAME
{ METHOD_HEADER_LINES }
where METHOD_HEADER_LINES is a sequence of header lines of
methods, terminated by semicolons.
As a matter of policy, we insert a comment with each
header line that describes the intended behavior of the method.
The BankAccountSpecification interface is placed in its own file, BankAccountSpecification.java, and is compiled like any other component. Once the interface is compiled, other classes can use it in their codings, as we now see.
Your friend starts work on the mortgage-payment class. Although your friend does not have the coding of the bank-account class, it does not matter --- whatever the coding will be, it will possess the methods listed in interface BankAccountSpecification. Therefore, your friend writes the class in Figure 2, which uses the BankAccountSpecification as the data type for the yet-to-be-written bank-account class.
FIGURE 2: class that references a Java interface=========================
/** MortgagePaymentCalculator makes mortgage payments */
public class MortgagePaymentCalculator
{ private BankAccountSpecification bank_account; // holds the address of
// an object that implements the BankAccountSpecification
/** Constructor MortgagePaymentCalculator initializes the calculator.
* @param account - the address of the bank account from which we
* make deposits and withdrawals */
public MortgagePaymentCalculator(BankAccountSpecification account)
{ bank_account = account; }
/** makeMortgagePayment makes a mortgage payment from the bank account.
* @param amount - the amount of the mortgage payment */
public void makeMortgagePayment(int amount)
{ boolean ok = bank_account.withdraw(amount);
if ( ok )
{ System.out.println("Payment made: " + amount); }
else { ... error ... }
}
...
}
ENDFIGURE===============================================================
The interface name, BankAccountSpecification, is used as a data type,
just like class names are used as data types. This lets us write a constructor
method that accepts (the address of) an object that has the behavior
specified in interface BankAccountSpecification and
it lets us use this object's withdraw method in the
method, makeMortgagePayment.
Class MortgagePaymentCalculator can now be compiled; the Java compiler validates that the class is using correctly the methods listed in interface BankAccountSpecification. (That is, the methods are spelled correctly and are receiving the correct forms of arguments, and the results that the methods return are used correctly.) In this way, your friend completes the class that makes mortgage payments.
Meanwhile, you are writing the class that implements interface BankAccountSpecification; this might look like Figure 3.
FIGURE 3: a class that implements a Java interface=======================
/** BankAccount manages a single bank account; as stated in its
* header line, it _implements_ the BankAccountSpecification: */
public class BankAccount implements BankAccountSpecification
{ private int balance; // the account's balance
/** Constructor BankAccount initializes the account */
public BankAccount()
{ balance = 0; }
// notice that methods deposit and withdraw match the same-named
// methods in interface BankAccountSpecification:
public void deposit(int amount)
{ balance = balance + amount; }
public boolean withdraw(int amount)
{ boolean result = false;
if ( amount <= balance )
{ balance = balance - amount;
result = true;
}
return result;
}
/** getBalance reports the current account balance
* @return the balance */
public int getBalance()
{ return balance; }
}
ENDFIGURE===============================================================
This is essentially Figure 11 of Chapter 6, but notice in the
class's header line the phrase, implements BankAccountSpecification.
This tells the Java compiler that class BankAccount
can be connected to those classes that use
BankAccountSpecifications. When you compile
class BankAccount, the Java compiler
verifies, for
each method named in interface BankAccountSpecification, that
class BankAccount contains a matching method.
(By ``matching method,'' we mean that
the class's method's header line is the same as the header
line in the interface---the number of parameters is the same,
the types of the parameters are the same, and the result type is the same.
But the names of the formal parameters need not be exactly the
same, and the order of the methods in the class need not
be the same as the order of the methods in the interface.)
Notice that class BankAccount has an additional method that is not mentioned in the interface; this is acceptable.
To connect together the two classes, we write a start-up method with statements like the following:
BankAccount my_account = new BankAccount(); MortgageCalculator calc = new MortgageCalculator(my_account); ... calc.makeMortgagePayment(500);Since the Java compiler verified that BankAccount implements BankAccountSpecification, the my_account object can be an argument to the constructor method of MortgageCalculator.
A major advantage of using Java interfaces is this:
In this way, components can be separately designed, written, compiled, and later connected together into an application, just like the disc player and batteries were separately manufactured and later connected together.
Figure 4 shows the class diagram for the example in Figures 1 to 3.
FIGURE 4: class diagram of Java interface=================================To distinguish it from the classes, the Java interface is written in italics. Since class MortageCalculator depends on (references) the interface, a dotted arrow is drawn from it to the interface. Since class BankAccount implements the interface, a dotted arrow with a large arrowhead is drawn from it to the interface.ENDFIGURE============================================================
The class diagram in Figure 4 shows that MortgageCalculator and BankAccount do not couple-to/depend-on each other (in the sense of Chapter 6)---they both depend on interface BankAccountSpecification, which is the ``connection point'' for the two classes. Indeed, MortgageCalculator and BankAccount are simple examples of ``subassemblies'' that connect together through the BankAccountSpecification interface. This makes it easy to remove BankAccount from the picture and readily replace it by some other class that also implements BankAccountSpecification. (See the Exercises that follow this section.)
It is exactly this use of Java interfaces that allows teams of programmers build large applications: Say that a team of programmers must design and build a complex application, which will require dozens of classes. Perhaps the programmers are divided into three groups, each group agrees to write one-third of the application, and the three subassemblies will be connected together. If all the programmers first agree on the interfaces where the three subassemblies connect, then each group can independently develop their own subassembly so that it properly fits into the final product
public interface Convertable
{ public double convert(int i); }
which of the following classes will the Java compiler accept as
correctly implementing the interface? Justify your answers.
public class C1 implements Convertable
{ private int x;
public C1(int a) { x = a; }
public double convert(int j)
{ return x + j; }
}
public class C2 implements Convertable
{ private int x;
public C1(int a) { x = a; }
public int convert(int i)
{ return i; }
}
public class C3
{ private int x;
public C3(int a) { x = a; }
public double convert(int i)
{ return (double)x; }
}
public interface Convertable
{ public double convert(int i); }
which of the following classes use the interface correctly?
Justify your answers.
public class Compute1
{ private Convertable convertor;
public Compute1(Convertable c)
{ convertor = c; }
public void printConversion(int x)
{ System.out.println(convertor.convert(x)); }
}
public class Compute2 uses Convertable
{ public Compute2() { }
public void printIt(double x)
{ System.out.println(Convertable.convert(x)); }
}
public class Compute3
{ Convertable c;
public Compute3()
{ c = new Convertable(); }
public void printIt(int v)
{ System.out.println(c.compute(v)); }
}
public class Start
{ public static void main(String[] args)
{ C1 c = new C1(1);
Compute1 computer = new Computer(c);
computer.printConversion(3);
}
}
Execute Start.
Repair interface BankAccountSpecification so that class BankWriter compiles without errors. Does class AccountManager of Figure 16 require any changes?
public class SillyAccount implements BankAccountSpecification
{ public SillyAccount() { }
public void deposit(int amount) { }
public boolean withdraw(int amount) { return true; }
public int getBalance() { return 0; }
}
Change class AccountManager in Figure 16 so that it
declares,
BankAccountSpecification account = new SillyAccount();Does AccountManager compile without error? Do any of the other classes require changes to compile?
This exercise demonstrates that Java interfaces make it simple to replace one class in an assembly without rewriting the other classes.
/** ThrobbingBall models a stationary ball that changes size */
public class ThrobbingBall implements MovingObjectBehavior
{ private int max_radius = 60;
private int increment = 10;
private int current_radius; // invariant: 0 <= current_radius < max_radius
private int position = 100;
public ThrobbingBall() { current_radius = 0; }
public int xPosition() { return position; }
public int yPosition() { return position; }
public int radiusOf() { return current_radius; }
public void move()
{ current_radius = (current_radius + increment) % max_radius; }
}
and in Figure 16, Chapter 7, replace the MovingBall object
constructed within class BounceTheBall by
ThrobbingBall ball = new ThrobbingBall();Recompile class BounceTheBall and execute the animation.
In Chapter 8, Section 6, we designed a database, named class Database, to hold a collection of ``record'' objects, each of which possessed a unique ``key'' object to identify it. The database was designed to be general purpose, in the sense that records might be library-book objects or bank-account objects, or tax records. As stated in Chapter 8, the crucial behaviors were
Clearly, the types Record and Key are not meant to be specific classes; they should be the names of two Java interfaces, so that we can compile class Database now and decide later how to implement the two interfaces.
Figure 5 shows how to transform the informal specifications of Record and Key from Table 3 of Chapter 8 into interfaces.
FIGURE 5: interfaces for Record and Key===============================
/** Record is a data item that can be stored in a database */
public interface Record
{ /** getKey returns the key that uniquely identifies the record
* @return the key */
public Key getKey();
}
/** Key is an identification, or ``key,'' value */
public interface Key
{ /** equals compares itself to another key, m, for equality
* @param m - the other key
* @return true, if this key and m have the same key value;
* return false, otherwise */
public boolean equals(Key m);
}
ENDFIGURE=============================================================
These interfaces are compiled first, then class Database from
Figure 4, Chapter 8, can be compiled --- please review that Figure, now.
Note how class Database
refers to Record and Key in its coding.
In particular, Record's keyOf method and Key's
equals method
are used in crucial ways to insert and find records in the database.
As noted in Section 8.6.5, we might use the database to hold information about bank accounts that are identified by integer keys. Figure 5 of Chapter 8, which defines bank accounts and integer keys, should be rewritten --- Figure 6 shows these two classes revised so that they implement the Record and Key interfaces, respectively. Note that both classes are properly named and implement their respective interfaces.
FIGURE 6: implementing the database interfaces==========================
/** BankAccount models a bank account with an identification key */
public class BankAccount implements Record
{ private int balance; // the account's balance
private Key id; // the identification key
/** Constructor BankAccount initializes the account
* @param initial_amount - the starting account balance, a nonnegative.
* @param id - the account's identification key */
public BankAccount(int initial_amount, Key id)
{ balance = initial_amount;
key = id;
}
/** deposit adds money to the account.
* @param amount - the amount of money to be added, a nonnegative int */
public void deposit(int amount)
{ balance = balance + amount; }
/** getBalance reports the current account balance
* @return the balance */
public int getBalance() { return balance; }
/** getKey returns the account's key
* @return the key */
public int getKey() { return key; }
}
/** IntegerKey models an integer key */
public class IntegerKey implements Key
{ private int k; // the integer key
/** Constructor IntegerKey constructs the key
* @param i - the integer that uniquely defines the key */
public IntegerKey(int i) { k = i; }
/** equals compares this Key to another for equality
* @param c - the other key
* @return true, if this key equals k's; return false, otherwise */
public boolean equals(Key c)
{ return ( k == ((IntegerKey)c).getInt() ); }
/** getInt returns the integer value held within this key */
public int getInt() { return k; }
}
ENDFIGURE============================================================
The first class, BankAccount, keeps its key as an attribute and gives it away with its getKeyOf method; the class knows nothing about its key's implementation. The second class, IntegerKey, uses an integer attribute as its internal state. Its equals method must compare its internal integer to the integer held in its argument, c. To do this, object c must be cast into its underlying type, IntegerKey, so that the getInt method can be queried for c's integer. (From the perspective of the Java compiler, an object whose data type is Key does not necessarily possess a getInt method; the cast is necessary to tell the compiler that c is actually an IntegerKey, which does possess a getInt method.)
Unfortunately, we cannot avoid the cast by writing equals's header line as
public boolean equals(IntegerKey c)because the parameter's data type would not match the data type of the parameter of equals in interface Key. We must live with this clumsiness.
Now, we can build a database that holds BankAccount records; study carefully the following, which shows how to insert and retrieve bank acccounts:
Database db = new Database(4); // see Figure 4, Chapter 8 BankAccount a = new BankAccount(500, new IntegerKey(1234)); boolean result1 = db.insert(a); IntegerKey k = new IntegerKey(567); BankAccount b = new BankAccount(1000, k); boolean result2 = db.insert(b); Record r = db.find(k); // retrieve object indexed by Key k System.out.println(((BankAccount)r).getBalance()); // why is the cast needed?Since Database's find method returns an object that is known to be only a Record, a cast to BankAccount is required by the Java compiler in the last statement of the above example.
Figure 7 shows the database example's class diagram.
FIGURE 7: class diagram with interfaces==================================The diagram shows that Database is coupled only to the two interfaces and not to the classes that implement the interfaces. This is a clear signal that other classes of records and keys can be used with class Database. (For example, Figure 6 of Chapter 8 shows codings of classes of library books and catalog numbers; by recoding the two classes so that they implement the Record and Key interfaces, the two classes can be readily used with the database.)ENDFIGURE================================================================
Also, note how BankAccount and IntegerKey are not coupled to each other, making it easy to ``unplug'' and replace both classes. Finally, by transitivity, the dotted arrows from BankAccount to Record to Key let us infer correctly that BankAccount depends on interface Key as well as interface Record.
import java.awt.*;
import javax.swing.*;
public class MyPanel extends JPanel
{ ...
public void paintComponent(Graphics g)
{ ... instructions for painting on a panel ... }
}
This tactic worked because, within the package
javax.swing, there is a prewritten class,
class JPanel, which contains the instructions for
constructing a blank graphics panel that can be displayed on a monitor.
We exploit this already written class by
MyPanel p = new MyPanel(...);the object we construct has all the private fields and methods within class JPanel plus the new methods and fields within class MyPanel. This gives object p the ability to display a panel on the display as well as paint shapes, colors, and text onto it.
This style of ``connecting'' to an already written class and adding new methods is called inheritance. We say that MyPanel is a subclass of JPanel (and JPanel is a superclass of MyPanel).
To understand inheritance further, study this small example developed from scratch: Say that a friend has written class Person:
public class Person
{ private String name;
public Person(String n)
{ name = n; }
public String getName()
{ return name; }
... // Other clever methods are here.
}
Pretend this class has proven popular, and many programs
use it.
Next, say that you must write a new application that models persons with their addresses. You would like to ``connect'' an address to a person and reuse the coding within class Person to save you time writing your new class. You can use inheritance to do this:
public class PersonAddress extends Person
{ private String address;
public PersonAddress(String the_name, String the_addr)
{ super(the_name); // this gives the_name to Person's constructor
address = the_addr;
}
public String getAddress()
{ return address; }
...
}
Because its title line states,
extends Person, class PersonAddress inherits the fields
and methods of class Person.
When we construct an object from the new class, say,
PersonAddress x = new PersonAddress("fred", "new york");
the object constructed in computer storage contains an
address variable and also a name variable.
Indeed,
the first statement in the constructor method for PersonAddress,
super(the_name);invokes the constructor method within Person (the superclass), so that the person's name is inserted into the name field. (When this tactic is used, the super instruction must be the first statement within the subclass's constructor method.)
When we use the object we constructed, e.g.,
System.out.println("Name: " + x.getName());
System.out.println("Address: " + x.getAddress());
the methods from class Person can be used alongside the
methods from class PersonAddress.
It is striking that we can say, x.getName(), even though there
is no getName method within class PersonAddress;
the inheritance technique ``connects'' the methods of Person
to PersonAddress.
We use a large arrowhead in class-diagram notation to denote inheritance:
FIGURE HERE: PersonAddress ---|> Person
Inheritance is fundamentally different from Java interfaces, because inheritance builds upon classes that are already written, whereas interfaces specify classes that are not yet written (or unavailable). Inheritance is often used when someone has written a basic class that contains clever methods, and other people wish to use the clever methods in their own classes without copying the code --- they write subclasses. A good example is our extension of class JPanel, at the beginning of this section.
Here is another situation where inheritance can be useful: Again, say that class Person has proved popular, and someone has written a useful class that writes information about persons:
public class WritePerson
{ ...
public void writeName(Person p)
{ ... clever instructions to write p's name ... }
}
If we write an application that manages persons plus their addresses, we can exploit both class Person and class WritePerson by writing class PersonAddress extends Person as shown above. Then, we can do this:
PersonAddress x = new PersonAddress("fred", "new york");
WritePerson writer = new WritePerson(...);
writer.writeName(x);
System.out.println("Address: " + x.getAddress());
The statement, writer.writeName(x) is crucial,
because the writeName method expects an argument that has data type
Person. Because x has data type
PersonAddress and because PersonAddress extends
Person, x is acceptable
to writeName.
This behavior is based on subtyping
and is explored in the next section.
public class Person
{ private String name;
public Person(String n) { name = n; }
public String getName() { return name; }
public boolean sameName(Person other)
{ return getName().equals(other.getName(); }
}
public class PersonAddress extends Person
{ private String address;
public PersonAddress(String the_name, String the_addr)
{ super(the_name);
address = the_addr;
}
public String getAddress() { return address; }
public boolean same(PersonAddress other)
{ return sameName(other) && address.equals(other.getAddress()); }
}
and these declarations:
Person p = new Person("fred");
Person q = new PersonAddress("ethel", "new york");
Which of the following statement sequences are acceptable to the Java compiler?
If a statement sequence is acceptable, what does it print when executed?
When we first encountered data types in Chapter 3, we treated them as ``species'' of values---int, double, and boolean were examples of such species. The classifying values into species prevents inappropriate combinations of values, such as true && 3 --- the Java compiler does data-type checking to spot such errors. Data-type checking also spots bad actual-formal parameter combinations, such as Math.sqrt(true).
Numerical, boolean, and character values are primitive (non-object), and their data types are called primitive types. The numeric primitive types are related by subtyping: int is a subtype of double, written int <= double, because an integer can be used in any situation where a double is required. For example, the integer 2 can be used within
double d = 4.5 / 2;because a double answer can be produced by dividing the double, 4.5, by 2. Similarly, if a method expects an argument that is a double, as in
public double inverseOf(double d)
{ return 1.0 / d; }
it is acceptable to send the method an actual parameter that is an
integer, e.g., inverseOf(3).
The Java compiler uses the
subtyping relationship, int <= double, to check the well formedness
of these examples.
Subtyping relationships simplify our programming; in particular, cumbersome cast expressions are not required. For example, it is technically correct but ugly to write double d = 4.5 / ((double)2), and thanks to subtyping, we can omit the cast.
Begin footnote: Here is a listing of the subtyping relationships between the numeric types:
byte <= int <= long <= float <= doubleThus, byte <= int, int <= long, byte <= long, etc. End footnote
In addition to the primitive data types, there are object or reference data types: Every class C defines a reference data type, named C --- its values are the objects constructed from the class. This explains why we write declarations like
Person p = new Person("fred");
class Person defines data type Person,
and variable p may hold only
addresses of objects that have data type Person.
Java interfaces and inheritance generate subtyping relationships between reference types as well. If we write the class,
public class MyPanel extends JPanel
{ ... }
then this subtyping relationship is generated: MyPanel <= JPanel.
This means that the object,
new MyPanel(), can be used in any situation where a
value of data type JPanel is expected.
We have taken for granted this fact, but it proves crucial when
we construct, a new MyPanel object and insert it into
a JFrame object:
// This example comes from Figure 12, Chapter 4:
import java.awt.*;
import javax.swing.*;
public class MyPanel extends JPanel
{ ... }
public class FrameTest3
{ public static void main(String[] args)
{ JFrame my_frame = new JFrame();
// insert a new panel into the frame:
my_frame.getContentPane().add(new MyPanel());
...
}
}
The add method invoked within main
expects a JPanel object as its argument. But since
MyPanel <= JPanel, the newly constructed
MyPanel object is acceptable.
This same phenomenon appeared at the end of the previous section when we used a PersonAddress object as an argument to the writeName method of PersonWriter.
Similar subtyping principles also apply to Java interfaces: The Java compiler uses interface names as data type names, and the compiler enforces a subtyping relationship when an interface is implemented: if class C implements I, then C <= I. This proves crucial when connecting together classes: Reconsider the database example in Figures 5-7; we might write
Database db = new Database(4); IntegerKey k = new IntegerKey(1234); BankAccount b = new BankAccount(500, k); boolean success = db.insert(b);The database method, insert, expects an arguments with data type Record, but it operates properly with one of type BankAccount, because BankAccount <= Record. This subtyping can be justified by noting that a BankAccount has all the methods expected of a Record, so insert executes as expected.
If we peek inside computer storage, we see that the above statements constructed these objects:
The diagram indicates that every object in storage is labelled with the name of the class from which the object was constructed. This is the run-time data type of the object. The diagram also illustrates that run-time data types are distinct from the data types that appear in assignments. For example, the object at address a4 retains its run-time data type, BankAccount, even though it was assigned into an element of an array declared to hold Records. (Look at a2's run-time data type.) The situation is acceptable because of the subtyping relationship.![]()
Consider the following statements, which build on the above:
Record r = db.find(k); System.out.println( ((BankAccount)r).getBalance() );The first statement extracts from the data base the record matching k, that is, a4 is assigned to r. But we cannot say, immediately thereafter, r.getBalance(), because variable r was declared to have data type Record, and there is no getBalance method listed in interface Record. The problem is that the data type in the statement, Record r = db.find(k), is distinct from the run-time data type attached to the object that is assigned to r.
If we try to repair the situation with the assignment, BankAccount r = db.find(k), the Java compiler complains again, because the find method was declared to return a result of data type Record, and Record is not a subtype of BankAccount!
This is frustrating to the programmer, who knows that db is holding BankAccount objects, but Java's compiler and interpreter are not intelligent enough to deduce this fact. Therefore, the programmer must write an explicit cast upon r, namely, (BankAccount)r, to tell the Java compiler that r holds an address of an object whose run-time type is BankAccount. Only then, can the getBalance message be sent.
If the programmer encounters a situation where she is not certain herself what is extracted from the database, then the instanceof operation can be used to ask the extracted record its data type, e.g.,
Record mystery_record = db.find(mystery_key);
if ( mystery_record instanceof BankAccount)
{ System.out.println( ((BankAccount)mystery_record).getBalance() ); }
else { System.out.println("unknown record type"); }
Stated precisely, the phrase, EXPRESSION instanceof TYPE,
returns true exactly when the run-time data type attached to the
object computed by EXPRESSION is a subtype of
TYPE.
We can use the instanceof method to repair a small problem in the database example in the previous section. In addition to class IntegerKey implements Key, say that the programmer writes this new class:
/** StringKey models a key that is a string */
public class StringKey implements Key
{ private String s;
public StringKey(String j)
{ s = j; }
public String getString()
{ return s; }
public boolean equals(Key m) { ... }
}
and say that she intends to construct some records that
use IntegerKeys and some that use StringKeys.
This seems ill-advised, because we see a problem
when an IntegerKey object is asked to check equality against
a StringKey object:
IntegerKey k1 = new IntegerKey(2);
StringKey k2 = new StringKey("two");
boolean answer = k1.equals(k2);
Surprisingly, the Java compiler will accept these statements as
well written, and it is only when the program executes that the
problem is spotted---execution stops in the middle of
IntegerKey's equals method at the statement,
int m = ((IntegerKey)another_key).getInt(),
and this exception message appears:
Exception in thread "main" java.lang.ClassCastException: StringKey
at IntegerKey.equals(...)
Despite the declaration in its header line,
IntegerKey's equals method is unprepared
to deal with all possible actual parameters whose data types are
subtypes of Key!
If an application will be constructing both IntegerKeys and StringKeys, then we should improve IntegerKey's equals method to protect itself against alien keys. We use the instanceof operation:
public boolean equals(Key another_key)
{ boolean answer;
// ask if another_key's run-time data type is IntegerKey:
if ( another_key instanceof IntegerKey )
{ int m = ((IntegerKey)another_key).getInt();
answer = (id == m);
}
else // another_key is not an IntegerKey, so don't compare:
{ answer = false; }
return answer;
}
The phrase,
if ( another_key instanceof IntegerKey )determines the address of the object named by another_key, locates the object in storage, extracts the run-time data type stored in the object, and determines whether that data type is a subtype of IntegerKey. If it is, true is the answer. If not, false is the result.
public class Person
{ private String name;
public Person(String n) { name = n; }
public String getName() { return name; }
public boolean sameName(Person other)
{ return getName().equals(other.getName(); }
}
public class PersonAddress extends Person
{ private String address;
public PersonAddress(String the_name, String the_addr)
{ super(the_name);
address = the_addr;
}
public String getAddress() { return address; }
public boolean same(PersonAddress other)
{ return sameName(other) && address.equals(other.getAddress()); }
}
and these declarations:
Person p = new Person("fred");
Person q = new PersonAddress("ethel", "new york");
Which of the following statement sequences are acceptable to the Java compiler?
If a statement sequence is acceptable, what does it print when executed?
public class C
{ private int x;
public C() { x = 0; }
}
public class D extends C
{ public D() { super(); }
public void increment() { x = x + 1; }
}
If you are interested in repairing the example, read the section,
``Subclasses and Method Overriding,'' at the end of the Chapter.
Database db = new Database(4); // see Figure 3, Chapter 8
BankAccount b = new BankAccount(500, new IntegerKey(1234));
IntegerKey k = new StringKey("lucy");
BankAccount lucy = new BankAccount(1000, k);
boolean result1 = db.insert(b);
boolean result2 = db.insert(lucy);
Record p = db.find(k);
BankAccount q = (BankAccount)p;
System.out.println(q.getBalance());
Key k = q.getKey();
if ( k instanceof IntegerKey )
{ System.out.println( ((IntegerKey)k).getInt() ); }
else if ( k instance of StringKey )
{ System.out.println( ((StringKey)k).getString() ); }
else { System.out.println("unknown key value"); }
What appears on the display?
public interface I
{ public int f(int i); }
public class C implements I
{ public C() { }
public int f(int i) { return i + 1; }
public void g() { }
}
public class D
{ public D() { }
public int f(int i) { return i + 1; }
}
I x = new C(); C y = new C(); C z = (C)x;Which of the following expressions evaluate to true? x instanceof I; x instanceof C; x instanceof D; y instanceof I; y instanceof C; y instanceof D; z instanceof I; z instanceof C.
I x = new C();
x.f(21);
x.g();
C y = x;
y.g();
if ( x instanceof C )
{ x.g(); }
D a = new D();
I b = a;
if ( a instanceof C )
{ System.out.println("!");
In the previous section, we pretended that a class Person was an important component of many programs. Perhaps the author of class Person intended that Person objects must have addresses, but the author did not wish to code the address part. (The address might be a string or an integer or a color or ....) An abstract class can state exactly the author's wishes:
public abstract class Person // note the keyword, abstract
{ private String name;
public Person(String n)
{ name = n; }
public String getName()
{ return name; }
public abstract String getAddress(); // method will be written later
...
}
This variant of class Person has two additions from the one
seen earlier:
public class PersonAddress extends Person
{ private String address;
public PersonAddress(String the_name, String the_addr)
{ super(the_name); // this gives the_name to Person's constructor
address = the_addr;
}
public String getAddress()
{ return address; }
...
}
We can construct new PersonAddress(...) objects,
as before. And, we can construct other variants, e.g.,
public class PersonWithInt extends Person
{ private int address;
public PersonWithInt(String the_name, int the_addr)
{ super(the_name);
address = the_addr;
}
public String getAddress()
{ return "" + address; } // make the int into a string
...
}
Note that the getAddress method must have the same
title line as the one in the superclass; this forces the integer to
be converted into a string when it is returned.
An abstract class is ``half interface'' and ``half superclass,'' and it is most useful for grouping, under a single data type name, classes that share methods.
Here is an example that illustrates good use of interfaces with abstract classes: In Chapter 8, we saw how to design cards and card decks for a card game. (See Tables 7 and 8 and Figures 9 and 10 from that Chapter.) If we continue developing the card game, we will design a class Dealer, which collaborates with cards and card decks as well as with objects that are card players. At this point, we do not know exactly how the card players will behave (this is dependent on the rules of the card game), but class Dealer would expect that a card player has a method to receive a card and has a method that replies whether a player wants to receive additional cards. The obvious step is to write an interface; Figure 8 shows it.
FIGURE 8: interface for card playing=====================================
/** CardPlayerBehavior defines expected behaviors of card players */
public interface CardPlayerBehavior
{ /** wantsACard replies whether the player wants one more new card
* @return whether a card is wanted */
public boolean wantsACard();
/** receiveCard accepts a card and adds it to the player's hand
* @param c - the card */
public void receiveCard(Card c);
}
ENDFIGURE=================================================================
Now we can write a
class Dealer whose coding uses the interface to
deal cards to players. (See Exercise 1, below.)
Next, we consider implementations of the CardPlayerBehavior interface. Perhaps there are two formats of card players---computerized players and human players. (A computerized player is an object that does card playing all by itself; a human player is an object that helps the human user join in the play.) The two classes of player receive cards in the same way, but when a human-player object is asked if it wants a card, it asks the user for a decision. In contrast, a computerized-player object will make the decision based on an algorithm --- that is, the two players share the same implementation of the receiveCard method but use different implementations of the wantsACard method.
To accommodate the situation, we invent the abstract class, CardPlayer, that acts as the superclass of both computerized and human card players. Figure 9 shows abstract class CardPlayer; its subclasses, ComputerPlayer and HumanPlayer, appear in Figure 10.
FIGURE 9: abstract class for card players================================
/** CardPlayer models an abstract form of card player */
public abstract class CardPlayer implements CardPlayerBehavior
{ private Card[] my_hand; // the player's cards
private int card_count; // how many cards are held in the hand
/** CardPlayer builds the player
* @param max_cards - the maximum cards the player can hold. */
public CardPlayer(int max_cards)
{ my_hand = new Card[max_cards];
card_count = 0;
}
/** wantsACard replies whether the player wants one more new card
* @return whether a card is wanted */
public abstract boolean wantsACard(); // method will be written later
public void receiveCard(Card c)
{ my_hand[card_count] = c;
card_count = card_count + 1;
}
/** showCards displays the player's hand
* @return an array holding the cards in the hand */
public Card[] showCards()
{ Card[] answer = new Card[card_count];
for ( int i = 0; i != card_count; i = i + 1 )
{ answer[i] = my_hand[i]; }
return answer;
}
}
ENDFIGURE=================================================================
CardPlayer is labelled abstract because it is incomplete: It is missing its wantsACard method (which it needs to implement CardPlayerBehavior). Only the header line for wantsACard appears; it contains the keyword, abstract, and is terminated by a semicolon. In contrast, receiveCard and another method, showCards, are written in entirety.
FIGURE 10: subclasses of CardPlayer=============================
/** HumanPlayer models a human who plays cards */
public class HumanPlayer extends CardPlayer
{ /** HumanPlayer builds the player
* @param max_cards - the maximum cards the player can hold */
public HumanPlayer(int max_cards)
{ super(max_cards); } // invoke constructor in superclass
public boolean wantsACard()
{ String response = JOptionPane.showInputDialog
("Do you want another card (Y or N)?");
return response.equals("Y");
}
}
/** ComputerPlayer models a computerized card player */
public class ComputerPlayer extends CardPlayer
{ /** ComputerPlayer builds the player
* @param max_cards - the maximum cards the player can hold. */
public ComputerPlayer(int max_cards)
{ super(max_cards); } // invoke constructor in superclass
public boolean wantsACard()
{ boolean decision;
Card[] what_i_have = showCards();
... statements go here that examine what_i_have and
calculate a decision ...
return decision;
}
}
ENDFIGURE================================================================
The two classes in Figure 8, HumanPlayer and ComputerPlayer, extend the abstract class, meaning the classes get the attributes and methods of a CardPlayer. The two classes also supply their own versions of the missing wantsACard method.
At this point, there are no more missing methods, and we say that the classes HumanPlayer and ComputerPlayer are concrete classes. Objects can be constructed from concrete classes. (Recall that they cannot be constructed from abstract classes.) Say that we construct two players:
ComputerPlayer p = new ComputerPlayer(3); HumanPlayer h = new HumanPlayer(3); CardPlayer someone = p; CardPlayerBehavior another = someone;The following situation appears in computer storage:
The fields (and methods) of class CardPlayer are adjoined to those of class ComputerPlayer to make a ComputerPlayer object. The same happens for the HumanPlayer object.![]()
The assignment, CardPlayer someone = p, reminds us that the data types ComputerPlayer and HumanPlayer are subtypes of CardPlayer; objects of either of the first two types can be used in situations where a CardPlayer object is required. This means we can send the message, boolean ask = someone.wantsACard(), because this is a behavior expected of a CardPlayer (even though no method was written for wantsACard in the abstract class!).
The last assignment, CardPlayerBehavior another = someone, is also acceptable, because interface names are data types. Although it is legal to say, boolean b = another.wantsACard(), the Java compiler will complain about another.showCards(), because the showCards method is not listed in interface CardPlayerBehavior; a cast would be necessary: e.g.,
if ( another instanceof CardPlayer )
{ ((CardPlayer)another).showCards(); }
The instanceof operation examines the data type stored within
the object named by another and verifies that the type is a
subtype of CardPlayer.
The above example has created these subtyping relationships:
ComputerPlayer <=
CardPlayer <= CardPlayerBehavior
HumanPlayer <=
Although the assignment, CardPlayer someone = p, was legal, this initialization is not---CardPlayer someone = new CardPlayer(3)---because objects cannot be constructed from abstract classes.
Figure 11 shows the architecture we have assembled with the example.
FIGURE 11: architecture of dealer and card players======================The diagram tells us that the Dealer collaborates through the CardPlayerBehavior interface, which is implemented by the abstract class, CardPlayer. (The abstract parts are stated in italics.)ENDFIGURE==========================================================
| class Dealer | models a dealer of cards |
| Responsibilities (methods) | |
| dealTo(CardPlayerBehavior p) | gives cards, one by one, to player p, until p no longer wants a card |
| Collaborators: | CardPlayerBehavior, CardDeck, Card |
/** Point models a geometric point */
public class Point
{ private int x;
private int y;
public Point(int a, int b)
{ x = a;
y = b;
}
public int xPosition() { return x; }
public int yPosition() { return y; }
}
/** Shape models a two-dimensional geometric shape */
public abstract class Shape
{ private Point upper_left_corner;
public Shape(Point location)
{ upper_left_corner = location; }
/** locationOf returns the location of the shape's upper left corner */
public Point locationOf()
{ return upper_left_corner; }
/** widthOf returns the width of the shape, starting from its left corner */
public abstract int widthOf();
/** depthOf returns the depth of the shape, starting from its left corner */
public abstract int depthOf();
}
/** Polygon models a polygon shape */
public abstract class Polygon extends Shape
{ private Point[] end_points; // the nodes (corners) of the polygon
public Polygon(Point upper_left_corner)
{ super(upper_left_corner); }
/** setCorners remembers the nodes (corners) of the polygon */
public void setCorners(Point[] corners)
{ end_points = corners; }
}
Now, write a concrete class,
Rectangle, that extends Polygon.
The constructor method for class Rectangle
can look like this:
public Rectangle(Point location, int width, int height)Next, write a concrete class, Circle, that extends Shape.
This approach comes from real-life taxonomies, like the one in Figure 12, which simplistically models the zoology of part of the animal kingdom.
FIGURE 12: hierarchy of animals==========================================ENDFIGURE=========================================================
The characteristics of animals appear at the internal positions of the hierarchy, and actual animals appear at the end positions---the ``leaves'' of the ``tree.'' (Animal is the ``root'' of the ``tree.'') When one characteristic is listed beneath another, it means that the first is more specific or a ``customization'' of the second, in the sense that the first has all the behaviors and characteristics of the second. For example, being Equine means having all the characteristics of a WarmBlooded entity.
The zoological taxonomy helps us make sense of the variety of animals and reduces the amount of analysis we apply to the animal kingdom. In a similar way, in a taxonomy of classes, when a class, A is a superclass of class B, we place B underneath A in the hierarchy.
For example, if we designed a computer program that worked with the animal-kingdom taxonomy, the program would create objects for the classes at the leaves, for example,
Horse black_beauty = new Horse(...);and it might assign,
Equine a_good_specimen = black_beauty;But the program would not create objects from the non-leaf classes, such as new Equine(), because there are no such animals.
This example suggests that the non-leaf entries of a class hierarchy represent typically (but not always!) abstract classes, and the leaves must be concrete classes from which objects are constructed. The hierarchy can be converted into a collection of classes, that extend one another, e.g.,
public abstract class Animal
{
// fields and methods that describe an animal
}
public abstract class WarmBlooded extends Animal
{
// additional fields and methods specific to warm-blooded animals
}
public abstract class Equine extends WarmBlooded
{
// fields and methods specific to equines
}
public class Horse extends Equine
{
// fields and completed methods specific to horses
}
Here is a more computing-oriented example: Figure 13 shows a hierarchy that might arise from listing the forms one would draw in a graphics window.
FIGURE 13: hierarchy of forms for drawing===============================The forms that one actually draws reside (primarily) at the leaves of the tree; the non-leaf entries are adjectives that list aspects or partial behaviors. (Although this rule is not hard and fast---arbitrary Polygon objects are possible.)ENDFIGURE==============================================================
The primary benefit of working with class hierarchies is that a hierarchy collects together related classes, meaning that commonly used attributes and methods can be written within abstract classes and shared by concrete classes. The major negative aspect of a class hierarchy is its size---one actual object might be constructed by extending many classes, which might be located in many different files. For example, a Triangle object constructed from Figure 13 is a composite of classes Triangle, Polygon, Shape, and Form; a programmer will quickly grow weary from reading four (or more) distinct classes to understand the interface and internal structure of just one object! If possible, limit the hierarchies you write to a depth of two or three classes.
A documentation tool, like javadoc, can generate helpful interface documentation for a class hierarchy; see the section, ``Generating Package APIs with javadoc,'' which follows.
/** Form is the root of the geometric forms hierarchy */
public abstract class Form { }
Alter classes Point and Shape so that they fit
into the hierarchy. Next, write the classes for
Line and Straight. (Remember that a line has two
end points. A straight line has no additional points,
whereas jagged lines require additional points and curves require
additional information.)
A framework is such an incomplete program---it is a collection of classes and interfaces organized into an architecture for a particular application area, such as building graphics windows, or generating animations, or building spreadsheets, or writing music, or creating card games. Some of the framework's classes are left abstract---incomplete. This is deliberate---a programmer uses the framework to build a specific graphics window (or a specific animation, or spreadsheet, or song, or game) by writing concrete classes that extend the abstract ones. The result is a complete application that creates what the programmer desires with little effort.
For example, say that someone wrote for us a framework for building graphics windows. Such a framework would contain classes that do the hard work of calculating colors, shapes, and sizes and displaying them on the computer console. Most importantly, the framework would also contain an abstract class, say, by the name of class GraphicsWindow, that already contains methods like setSize, and setVisible but lacks a paint method. A programmer would use the framework and class GraphicsWindow in particular to write concrete classes that extend GraphicsWindow with paint methods. By using the framework, the programmer generates graphics windows with minimal effort.
We have been doing something similar, of course, when we used Java's Abstract Window Toolkit (java.awt) and Swing (javax.swing) packages to build graphics windows. These two packages form a framework for a range of graphics applications, and by extending class JPanel, we ``complete'' the framework and create graphics windows with little effort on our own part.
The next chapter surveys the AWT/Swing framework.
Because interfaces and abstract classes have different purposes, it is acceptable to use both when a subassembly, as we saw in the case study with the varieties of card players.
One reason why interfaces and subclasses are confused is because both of them define subtype relationships. As noted earlier in the Chapter, if class C implements I, then data type C is a subtype of I written, C <= I. Similarly, if class B extends A, then B <= A. But remember that ``subtype'' is different from ``subclass'' --- if C <= D, it does not mean that C and D are classes and C is a subclass of D. Indeed, D and even C might be interfaces.
For example, if we wrote
public class A {...}
public class B extends A {...}
the Java compiler treats the first class as if it were written
public class A extends Object {...}
The practical upshot of this transformation is C <= Object, for every class, C. In this way, class Object defines a type Object which is the ``data type of all objects.''
Some programmers exploit the situation by writing methods whose arguments use type Object, e.g., here is a class that can hold any two objects whatsoever:
public class Pair
{ Object[] r = new Object[2];
public Pair(Object ob1, Object ob2)
{ r[0] = ob1;
r[1] = ob2;
}
public Object getFirst()
{ return r[0]; }
public Object getSecond()
{ return r[1]; }
}
For example,
Pair p = new Pair(new JPanel(), new int[3]);
Object item = p.getFirst();
if ( item instanceof JPanel )
{ (JPanel)item.setVisible(true); }
Because class Pair is written to hold and deliver objects of
type Object, a cast is needed to do any useful work with
the objects returned from its methods.
Of course, primitive values like integers, booleans, and doubles are not objects, so they cannot be used with class Pair. But it is possible to use class Integer, class Boolean, and class Double as so-called wrappers and embed primitive values into objects. For example, if we write
Integer wrapped_int = new Integer(3);this creates an object of type Integer that holds 3. Now, we might use it with class Pair:
Pair p = new Pair(wrapped_int, new int[3]);
Object item = p.getFirst();
if ( item instanceof Integer )
{ System.out.println( ((Integer)item).intValue() + 4 ); }
In a similar way, we can ``wrap'' a boolean within a
new Boolean and later use the booleanValue()
method and wrap a double within a
new Double and use the doubleValue() method.
Finally, since every object is built from a class that extends Object, every object inherits from class Object a method, toString, that returns a string representation of an object's ``identity.'' For example, if we write
Integer wrapped_int = new Integer(3); String s = "abc"; Pair p = new Pair(wrapped_int, s); System.out.println(wrapped_int.toString()); System.out.println(s.toString()); System.out.println(p.toString()); }We receive this output:
3 abc Pair@1f14c60The third line is the string representation of p; it displays the type of p's object as saved in computer storage and a coding, called a hash code, of the object's storage address. The toString method can prove useful for printing tracing information when locating program errors.
A Java package is a folder of classes where the classes in the folder are marked as belonging together. We have used several Java packages already, such as java.util, java.awt, and javax.swing. When you write a program that requires components from a package named P, you must insert an import P.* statement at the beginning of your program. The import statement alerts the Java compiler to search inside package P for the classes your program requires.
We can make our own packages. Say that we wish to group the components written for bank accounting, listed in Figures 1 and 3, into a package named Bank. We do the following:
package Bank;
/** BankAccountSpecification specifies the expected behavior of a
* bank account. */
public interface BankAccountSpecification
{
... // the body of the interface remains the same
}
and Figure 3 is revised as follows:
package Bank;
/** BankAccount manages a single bank account; as stated in its
* header line, it _implements_ the BankAccountSpecification: */
public class BankAccount implements BankAccountSpecification
{
... // the body of the class remains the same
}
If you are using the JDK, then you must state both the package name and the class name to execute. For example, pretend that the Bank package contained a file, MortgagePaymentApplication.java, that has a main method. We compile this file like the others, e.g., javac Bank\MortgagePaymentApplication.java. We execute the class by typing java Bank.MortgagePaymentApplication. Note the dot rather than the slash.
Once a collection of classes is grouped in a package, other applications can import the package, just like you have imported javax.swing to use its graphics classes. For example, perhaps we write a new application, class MyCheckingAccount, so that it uses classes in package Bank. to do this, insert import Bank.* at the beginning of the class. This makes the components in package Bank immediately available, e.g.,
import Bank.*;
/** MyCheckingAccount contains methods for managing my account */
public class MyCheckingAccount
{ private BankAccount my_account = new BankAccount();
private MortgagePaymentCalculator calculator
= new MortgagePaymentCalculator(my_account);
...
}
If other applications will import the Bank package, then the package must be placed where the applications can find it. You do this by adding Bank's directory path to the Java compiler's classpath.
(Begin Footnote: A classpath is a list of paths to folders that hold packages. Whenever you compile and execute a Java program, a classpath is automatically used to locate the needed classes. The usual classpath includes paths to the standard packages, java.lang, java.util, etc. If you use an IDE, you can read and update the classpath by selecting the appropriate menu item. (Usually, you ``Edit'' the ``Project Preferences'' to see and change the classpath.) If you use the JDK, you must edit the environment variable, CLASSPATH, to change the path. End Footnote)
For example, if you created the package, Bank, as a folder within the folder, C:\JavaPgms, then add C:\JavaPgms to the Java compiler's classpath. If you do not wish to fight classpaths but wish to experiment with packages nonetheless, you can move the Bank folder into the folder where you develop your programs. (For example, if you do your development work in the folder, A:\MyWork\JavaExperiments, move Bank into that folder.) This also works when one package contains classes that use classes from another package---keep both packages within the same folder.
javadoc P(If you use an IDE, consult the IDE's user guide for information about javadoc.) The javadoc program extracts the commentary from each class and creates a collection of HTML pages, most notably, package-summary.html, which contains links to the API pages for each class in the package.
For example, here is the page created for the Bank package:
By clicking on the link for, say, BankAccountSpecification, we see the documentation for the interface:![]()
We finish the chapter by applying interfaces and inheritance to design a complex model whose components connect together in multiple, unpredictable ways: We are building an ``adventure game,'' where players can enter and exit rooms. A player enters an unoccupied room by speaking the ``secret word'' that opens the room's door. A player exits a room whenever she chooses.
We want the adventure game to be as general as possible, so we retrict as little as possible the notions of ``player'' and ``room.'' To start, we write specifications (Java interfaces) that state the minimal expected behaviors of rooms and players. The interfaces might appear as in Figure 14.
FIGURE 14: Interfaces for an adventure game======================
/** RoomBehavior defines the behavior of a room */
public interface RoomBehavior
{ /** enter lets a player enter a room
* @param p - the player who wishes to enter
* @return whether the player sucessfully opened the door and entered. */
public boolean enter(PlayerBehavior p);
/** exit ejects a player from the room.
* @param p - the player who wishes to leave the room */
public void exit(PlayerBehavior p);
/** occupantOf returns the identity of the room's occupant
* @return the address of the occupant object;
* if room unoccupied, return null */
public PlayerBehavior occupantOf();
}
/** PlayerBehavior defines the behavior of a player of an adventure game */
public interface PlayerBehavior
{ /** speak lets the player say one word
* @return the word */
public String speak();
/** explore attempts to enter a room and explore it
* @param r - the room that will be explored
* @return whether the room was successfully entered */
public boolean explore(RoomBehavior r);
}
ENDFIGURE===============================================================
The two interfaces depend on each other for stating their respective
behaviors, so the two interfaces must be compiled together. (To do this,
place the files, RoomBehavior.java and
PlayerBehavior.java in the same folder and compile one of them;
the Java compiler will automatically compile both.)
Although we know nothing about the kinds of players in the adventure game, we can use the interfaces to write a basic class of room for the game; see Figure 15.
FIGURE 15: room for an adventure game======================================
/** BasicRoom models a room that can have at most one resident at a time */
public class BasicRoom implements RoomBehavior
{ private PlayerBehavior occupant; // who is inside the room at the moment
private String rooms_name;
private String secret_word; // the password for room entry
/** Constructor BasicRoom builds the room.
* @param name - the room's name
* @param password - the secret word for entry into the room */
public BasicRoom(String name, String password)
{ occupant = null; // no one is in the room initially
rooms_name = name;
secret_word = password;
}
public boolean enter(PlayerBehavior p)
{ boolean result = false;
if ( occupant == null && secret_word.equals(p.speak()) )
{ occupant = p;
result = true;
}
return result;
}
public void exit(PlayerBehavior p)
{ if ( occupant == p ) // is p indeed in this room at the moment?
{ occupant = null; }
}
public PlayerBehavior occupantOf()
{ return occupant; }
}
ENDFIGURE=============================================================
A BasicRoom object remembers the address of the player
that occupies it, and it
is initialized with a null address
for its occupant. When a PlayerBehavior object wishes to enter the
room, it sends an
enter message, enclosing its address as an argument.
The BasicRoom is not so interesting, but it is a good
``building block'' for designing more elaborate rooms.
Next, we might write a class of player that remembers who it is and where it is. Figure 16 shows the class.
FIGURE 16: a player that can explore rooms===================
/** Explorer models a player who explores rooms */
public class Explorer implements PlayerBehavior
{ private String my_name;
private String my_secret_word; // a password for entering rooms
private RoomBehavior where_I_am_now; // the room this object occupies
/** Constructor Explorer builds the Player
* @param name - the player's name
* @param word - the password the player can speak */
public Explorer(String name, String word)
{ my_name = name;
my_secret_word = word;
where_I_am_now = null; // player does not start inside any room
}
public String speak()
{ return my_secret_word; }
/** exitRoom causes the player to leave the room it occupies, if any */
public void exitRoom()
{ if ( where_I_am_now != null )
{ where_I_am_now.exit(this); // exit the room
where_I_am_now = null;
}
}
public boolean explore(RoomBehavior r)
{ if ( where_I_am_now != null )
{ exitRoom(); } // exit current room to go to room r:
boolean went_inside = r.enter(this); // ``this'' means ``this object''
if ( went_inside )
{ where_I_am_now = r; }
return went_inside;
}
/** locationOf returns the room that is occupied by this player */
public RoomBehavior locationOf()
{ return where_I_am_now; }
}
ENDFIGURE===============================================================
Method explore of class Explorer uses the enter method of interface RoomBehavior when it explores a room. The novelty within the method is the keyword, this. When we write,
boolean went_inside = r.enter(this);the keyword, this, computes to the address of this very object that sends the message to object r. Whenever you see the keyword, this, read it as ``this very object that is sending the message.''
To understand this, consider this example:
RoomBehavior[] ground_floor = new RoomBehavior[4];
ground_floor[0] = new BasicRoom("kitchen", "pasta");
ground_floor[3] = new BasicRoom("lounge", "swordfish");
Explorer harpo = new Explorer("Harpo Marx", "swordfish");
Explorer chico = new Explorer("Chico Marx", "tomato");
boolean success = harpo.explore(ground_floor[3]);
Upon completion of the last statment, where the
harpo.explore method is invoked, we have this storage
configuration, which shows that object a4 has entered room
a3:
Because the object named harpo lives at address a4, the invocation, harpo.explore(ground_floor[3]), computes to a4.explore(a3). Within a4's explore method, there is the invocation, r.enter(this), which computes to a3.enter(a4), because this computes to a4, the address of this object in which the invocation appears.![]()
class Dungeon implements RoomBehaviorso that any object with PlayerBehavior can enter the room; no object that enters the room can ever exit it; and when asked by means of its occupantOf method, the Dungeon replies that no one is in the room.
Explorer harpo = new Explorer("Harpo Marx", "swordfish");
and write a for-loop that makes harpo systematically try to
enter and then exit each room in the array.
How can we make a BasicRoom object ``smart'' enough so that it refuses to let a player be its occupant when the player currently occupies another room? To do this, add this method to interface PlayerBehavior:
/** locationOf returns the room that is occupied by this player */ public RoomBehavior locationOf();Now, rewrite the enter method of BasicRoom.
Classes BasicRoom and Explorer are too simplistic for an interesting game. We might design rooms that contain ``treasures'' so that players can take the treasures from the rooms they enter.
Since a treasure might be a variety of things, it is simplest to define an interface that gives the basic property of being a treasure. See Figure 17.
FIGURE 17: interface for treasures======================================
/** TreasureProperty defines a treasure object */
public interface TreasureProperty
{ /** contentsOf explains what the treasure is
* @return the explanation */
public String contentsOf();
}
ENDFIGURE================================================================
Next, we can define an interface that defines what it means
for an object to hold a treasure. We might write the interface in Figure 18.
FIGURE 18: interface for a treasury====================================
/** Treasury describes the method an object has for yielding
* a treasure */
public interface Treasury
{ /** yieldTreasure surrenders the room's treasure
* @param p - the player who requests the treasure
* @return the treasure (or null, if the treasure is already taken) */
public TreasureProperty yieldTreasure(PlayerBehavior p);
}
ENDFIGURE===============================================================
A room that possesses an entry and exit as well as a treasure must implement both interface RoomBehavior as well as interface Treasury. It is indeed acceptable for one class to implement two interfaces, and it might look like this:
public class Vault implements RoomBehavior, Treasury
{ private PlayerBehavior occupant; // who is inside the room at the moment
private String rooms_name;
private String secret_word; // the password for room entry
private TreasureProperty valuable; // the treasure item held in this room
/** Constructor Vault builds the room.
* @param name - the room's name
* @param password - the secret word for entry into the room
* @param item - the treasure to be saved in the room */
public Vault(String name, String password, TreasureProperty item)
{ occupant = null;
rooms_name = name;
secret_word = password;
valuable = item;
}
public boolean enter(PlayerBehavior p)
{ ... } // insert coding from Figure 15
public void exit(PlayerBehavor p)
{ ... } // insert coding from Figure 15
public PlayerBehavior occupantOf()
{ ... } // insert coding from Figure 15
public TreasureProperty yieldTreasure(PlayerBehavior p)
{ TreasureProperty answer = null;
if ( p == occupant )
{ answer = valuable;
valuable = null;
}
return answer;
}
}
In the general case, a class can implement an arbitrary number
of interfaces; the interfaces are
listed in the implements clause, separated by commas,
within the class's header line.
The coding of class Vault possesses a major flaw: It copies the codings of methods already present in class BasicRoom. For this reason, we should rewrite the class so that it uses inheritance to use BasicRoom's methods. Figure 19 shows the corrected coding.
FIGURE 19: VaultRoom defined by inheritance==============================
/** VaultRoom is a room that holds a treasure---it is constructed from
* class BasicRoom, by means of inheritance, plus the structure below. */
public class VaultRoom extends BasicRoom implements Treasury
// because BasicRoom implements RoomBehavior, so does VaultRoom
{ private TreasureProperty valuable; // the treasure item held in this room
/** Constructor VaultRoom builds the room.
* @param name - the room's name
* @param password - the secret word for entry into the room
* @param item - the treasure to be saved in the room */
public VaultRoom(String name, String password, TreasureProperty item)
{ // first, invoke class BasicRoom's constructor method:
super(name, password); // ``super'' means ``superclass''
valuable = item;
}
public TreasureProperty yieldTreasure(PlayerBehavior p)
{ TreasureProperty answer = null;
if ( p == occupantOf() ) // invokes the occupantOf method in BasicRoom
// You can also state, super.occupantOf()
// Another format is, this.occupantOf()
{ answer = valuable;
valuable = null;
}
return answer;
}
}
ENDFIGURE===============================================================
The header line of class VaultRoom indicates that VaultRoom extends BasicRoom; this makes VaultRoom a subclass of the superclass BasicRoom. The class also implements the interface, Treasury, because it has a yieldTreasure method that matches the one in that interface. (Indeed, the class also implements interface RoomBehavior, because it extends BasicRoom and BasicRoom implements RoomBehavior.)
Within VaultRoom's constructor method, we see super(name, password), which invokes the constructor method of the superclass, ensuring that the private fields within BasicRoom are initialized. (Recall that when the super-constructor is invoked, it must be invoked as the first statement in the subclass's constructor.)
Within the yieldTreasure method, we see an invocation of the occupantOf method of BasicRoom:
if ( p == occupantOf() )The invocation asks this object to execute its own occupantOf method to learn the player that occupies it. The invocation is required because the field occupant is declared as private to class BasicRoom, so subclass VaultRoom cannot reference it directly.
As the comment next to the invocation states, we can also invoke this particular method by
if ( p == super.occupantOf() )This format asserts that the occupantOf method must be located in a superclass part of this object. (If the method is not found in a superclass of VaultRoom, the Java compiler will announce an error.) Finally, it is also acceptable to state
if ( p == this.occupantOf() )which is equivalent to the first invocation and documents that the invocation message is sent to this very object.
Using the above classes, say that we construct one BasicRoom object and one VaultRoom object:
BasicRoom a = new BasicRoom("The Lounge", "Hello");
TreasureProperty the_treasure = new Jewel("diamond");
VaultRoom b = new VaultRoom("The Vault", "Open, please!", the_treasure);
where we define class Jewel this simply:
public class Jewel implements TreasureProperty
{ private String name; // the name of the jewel
public Jewel(String id)
{ name = id; }
public String contentsOf()
{ return name; }
}
Here is a picture of storage after the statements execute:
The objects in storage show us that the structure of class BasicRoom is used to build the objects at addresses a1 and a3. In particular, the VaultRoom object at a3 inherited the BasicRoom structure plus its own---all the methods of a BasicRoom object can be used with a VaultRoom object.![]()
Because VaultRoom extends BasicRoom, the resulting data types are related by subtyping---data type VaultRoom is a subtype of data type BasicRoom. Further, since BasicRoom is a subtype of RoomBehavior, then by transitivity, VaultRoom is a subtype of RoomBehavior as well. Here is a drawing of the situation:
<= BasicRoom <= RoomBehavior
VaultRoom
<= Treasury
The Java compiler uses these subtyping relationships to verify that
VaultRoom objects are sent appropriate messages.
Finally, the diagram for the classes defined in this and the previous sections appears in Figure 20.
FIGURE 20: diagram for model of adventure game=============================The diagram shows how interfaces act as the connection points for the concrete classes; it shows that the classes BasicRoom and VaultRoom form a subassembly that is kept separate from Explorer. We also see that a VaultRoom has RoomBehavior and depends on PlayerBehavior. Indeed, as we study the diagram further, we might conclude that an Explorer might be profitably extended by methods that fetch treasures from the rooms explored; this is left for the Exercises.ENDFIGURE===========================================================
public class GoldCoin implements TreasurePropertyand construct a Vault object with the name, "lounge" and the password, "swordfish", to hold an object constructed from class GoldCoin.
/** TreasureHunterBehavior describes the behavior of a player that tries
* to take treasures */
public interface TreasureHunterBehavior
{ /** takeTreasure tries to extract the treasure from the current room
* that this player occupies.
* @return true if the treasure was succesfully extracted from the
* room and saved by this player; return false otherwise */
public boolean takeTreasure();
/** getTreasures returns an array of all the treasures located so far
* by this player
* @return the array of treasures */
public TreasureProperty[] getTreasures();
}
Write this class:
/** TreasureHunter explores rooms and saves all the treasures it extracts * from the rooms. */ public class TreasureHunter extends Explorer implements TreasureHunterBehavior
We can create a treasure hunter that has more aggressive behavior: First write this method:
/** explore attempts to enter a room and explore it. If the room is * successfully entered, then an attempt is made to take the treasure * from the room and keep it (if the room indeed holds a treasure). * @param r - the room that will be explored * @return whether the room was successfully entered */ public boolean explore(RoomBehavior r)Now, insert your coding of the new explore into this class:
public class RuthlessHunter extends TreasureHunter
{ ...
public boolean explore(RoomBehavior r)
{ ... }
}
When we construct a RuthlessHunter, e.g.,
VaultRoom bank = new VaultRoom("Bank", "bah!", new Jewel("ruby"));
RuthlessHunter bart = new RuthlessHunter("Black Bart", "bah!");
bart.explore(bank);
The new explore method of the RuthlessHunter executes
instead of the old, same-named method in the superclass,
Explorer. We say that the new explore method
overrides the old one.
Construct this object:
Explorer indiana = new Explorer("Indiana Jones", "hello");
indiana.explore(bank);
Which version of explore method executes?
Method overriding is studied in the ``Beyond the Basics'' section at the
end of this Chapter.
/** TreasuryBehavior describes a room that holds a treasure */
public interface TreasuryBehavior extends RoomBehavior
{ /** yieldTreasure surrenders the room's treasure
* @param p - the player who requests the treasure
* @return the treasure (or null, if the treasure is already taken) */
public TreasureProperty yieldTreasure(PlayerBehavior p);
}
Because it extends RoomBehavior,
interface TreasuryBehavior requires all the methods of
RoomBehavior plus its own.
It so happens that class VaultRoom in Figure 13, because it
extends BasicRoom, also is capable of implementing
TreasuryBehavior. Indeed, we can change its header line
accordingly:
public class VaultRoom extends BasicRoom implements Treasury, TreasuryBehaviorof course, if we no longer use interface Treasury in the game's architecture, we can shorten the header line to read merely
public class VaultRoom extends BasicRoom implements TreasuryBehavior
Because TreasuryBehavior extends RoomBehavior, the expected subtyping relation is established: TreasuryBehavior <= RoomBehavior. The Java compiler uses the subtyping to check compatibility of classes to interfaces.
public interface BankAccountSpecification
{ public void deposit(int amount);
public boolean withdraw(int amount);
}
public class HumanPlayer extends CardPlayer
{ public HumanPlayer(int max_cards)
{ super(max_cards); }
public boolean wantsACard()
{ String response = JOptionPane.showInputDialog
("Do you want another card (Y or N)?");
return response.equals("Y");
}
}
public abstract class CardPlayer implements CardPlayerBehavior
{ private Card[] my_hand;
private int card_count;
public CardPlayer(int max_cards)
{ my_hand = new Card[max_cards];
card_count = 0;
}
public abstract boolean wantsACard(); // method will be written later
public void receiveCard(Card c)
{ my_hand[card_count] = c;
card_count = card_count + 1;
}
}
package Bank;
/** BankAccount manages a single bank account; as stated in its
* header line, it _implements_ the BankAccountSpecification: */
public class BankAccount implements BankAccountSpecification
{ ... }
Record mystery_record = db.find(mystery_key);
if ( mystery_record instanceof BasicPerson )
{ Syst