Chapter 9
<< Chapter 8 | HomeworkTrailIndex | Chapter 10 >>
Interfaces and Polymorphism
Demo 0: Gifts
Demo1: Plinko
Demo2:Reusing code
Recall (Chapter 6)l how you can use the DataSet
class to get the min, max and average of some numbers. You add a data point and then call the appropriate method.
DataSet.java
public class DataSet { private double sum; private int count; private double maximum; public DataSet() { sum=0; count=0; maximum=0; } public void add(double value) { count++; sum+=value; if (count == 1 || value>maximum) maximum=value; } public double getSum() { return sum; } public int getCount() { return count; } public double getMaximum() { return maximum; } public double getAverage() { return sum/count; } }
DataSetTester1.java
public class DataSetTester1 { public static void main(String[] args) { //Make some Data: int[] myInts = new int[10]; for (int i=0; i<10; i++) { myInts[i]=(int)(10*Math.random()+1); } //Make a DataSet DataSet data=new DataSet(); for (int x:myInts) { data.add(x); System.out.print(x+" "); } //Get results System.out.println("\nMax is "+data.getMaximum()); System.out.println("Average is "+data.average()); } }
Now instead of set of random integers, what would we have to do to get it to work for getting the average BankAccount or Coin?
BankAccount.java
package demo2; public class BankAccount { private double balance; public BankAccount() { balance=0; } public BankAccount(double d) { balance=d; } public double getBalance() { return balance; } public void setBalance(double d) { balance=d; } }
Coin.java
package demo2; public class Coin { private String name; private double value; public Coin() { name="Generic Coin"; value=0; } public Coin(double v, String n) { value=v; name=n; } public double getValue() { return value; } public String getName() { return name; } }
each time, the function is the same, but we have to adapt the DataSet
class to handle the type of object (BankAccount
, Coin
, etc.) Solution? Make DataSet
use a interface, rather than a specific class, and adapt any class that wants use DataSet
should implement it. At the core is the thing we want to average or find the maximum for: balance
for the BankAccount
, value for the Coin
. Let's call this interface Measurable
. All it needs to do is provide a common language for the information to pass from the DataSet
class to any other class that might want to use its functions. DataSet
can then use objects that are of type Measurable
(which could be a BankAccount
, and Coin
, or any other class that implements the Measurable
interface. In this case, all we need is a way to get the double that we are averaging...
Measurable.java
public interface Measurable { double getMeasure(); }
DataSetTester2.java
package demo2; /** This program tests the DataSet class after making the Measurable Interface. */ public class DataSetTester2 { public static void main(String[] args) { DataSet bankData = new DataSet(); bankData.add(new BankAccount(0)); bankData.add(new BankAccount(10000)); bankData.add(new BankAccount(2000)); System.out.println("Average balance: " + bankData.getAverage()); System.out.println("Expected: 4000"); Measurable max = bankData.getMaximum(); System.out.println("Highest balance: " + max.getMeasure()); System.out.println("Expected: 10000"); DataSet coinData = new DataSet(); coinData.add(new Coin(0.25, "quarter")); coinData.add(new Coin(0.1, "dime")); coinData.add(new Coin(0.05, "nickel")); System.out.println("Average coin value: " + coinData.getAverage()); System.out.println("Expected: 0.133"); max = coinData.getMaximum(); System.out.println("Highest coin value: " + max.getMeasure()); System.out.println("Expected: 0.25"); } }
Can you find a way to print the name of the largest coin?
There are limitations... We made the Coin
class and the BankAccount
class. If we wanted to use the DataSet
class to get the average area of a set of Rectangle
's we would out of luck. We don't have control f the source code to implement the Measurable
interface. Another problem... maybe you don't want to use the balance
of a BankAccount, but some other thing, like interestRate
? Or perhaps you have a Song
class and sometimes you want to use the DataSet
class to find the maximum Song.runningTime
, and sometimes you want it to find the maximum Song.rating
. We would be out of luck by implementing the Song
class... we can use one thing or another the way we implemented Measurable
. Solution? Make the interface the most "generic" as possible, and adapt the DataSet
class to handle multiple classes and/or criterion, without touching Coin
or BankAccount
or Song
or Rectangle
classes. All classes are subclasses of the Object
class. First we make an interface that can deal with any Object
:
public interface Measurer { double measure(Object anObject); }
Next we change the DataSet
class:
public void add(Object x) { sum = sum + measurer.measure(x); if (count == 0 || measurer.measure(maximum) < measurer.measure(x)) maximum = x; count ++; }
Now instead of implementing an interface for each class like Coin
, BankAccount
, Rectangle
etc., we make a class to implement Measurer
.
import java.awt.Rectangle; public class RectangleMeasurer implements Measurer { public double measure(Object anObject) { Rectangle aRectangle = (Rectangle) anObject; double area = aRectangle.getWidth() * aRectangle.getHeight(); return area; } }
But instead of making this a separate class, in can "live inside" another class-- this is called a "inner class"
DataSetTester3.java
import java.awt.Rectangle; /** This program demonstrates the use of an inner class. */ public class DataSetTester3 { public static void main(String[] args) { class RectangleMeasurer implements Measurer { public double measure(Object anObject) { Rectangle aRectangle = (Rectangle) anObject; double area = aRectangle.getWidth() * aRectangle.getHeight(); return area; } } Measurer m = new RectangleMeasurer(); DataSet data = new DataSet(m); data.add(new Rectangle(5, 10, 20, 30)); data.add(new Rectangle(10, 20, 30, 40)); data.add(new Rectangle(20, 30, 5, 15)); System.out.println("Average area: " + data.getAverage()); System.out.println("Expected: 625"); Rectangle max = (Rectangle) data.getMaximum(); System.out.println("Maximum area rectangle: " + max); System.out.println("Expected: java.awt.Rectangle[x=10,y=20,width=30,height=40]"); } }
Now you try
- Take a look at the API for the java.awt.Polygon class. Can you make a
measurer
forPolygon
? - Can you find a way to make this work to find the most valuable coin?
- Can you adapt this to find the coin with the longest
name
?
Implementing an Interface to sort
Review Exercises
These can by tricky, so check your answers here
R9.1. Suppose C
is a class that implements the interfaces I
and J
. Which of the following assignments require a cast?
C c = . . .; I i = . . .; J j = . . .;
a. c = i;
b. j = c;
c. i = j;
R9.2. Suppose C
is a class that implements the interfaces I
and J
, and suppose i
is declared as
I i = new C();
Which of the following statements will throw an exception?
a. C c = (C) i;
b. J j = (J) i;
c. i = (I) null;
R9.3. Suppose the class Sandwich
implements the Edible
interface, and you are given the variable definitions
Sandwich sub = new Sandwich(); Rectangle cerealBox = new Rectangle(5, 10, 20, 30); Edible e = null;
Which of the following assignment statements are legal?
a. e = sub;
b. sub = e;
c. sub = (Sandwich) e;
d. sub = (Sandwich) cerealBox;
e. e = cerealBox;
f. e = (Edible) cerealBox;
g. e = (Rectangle) cerealBox;
h. e = (Rectangle) null;
----
Programming Exercises
For 8 points do P9.1, for 9 points do P9.1 and P9.2, for 10 points do P9.1, P9.2, and P9.10, for 11 points do P9.1, P9.2, P9.10 and the BugComparator
P9.1. Have the Die
class of Chapter 6 implement the Measurable
interface. Generate dice, cast them, and add them to the implementation of the DataSet
class in Section 9.1. Display the average.
Die.java from Chapter 6
import java.util.Random; /** This class models a die that, when cast, lands on a random face. */ public class Die { /** Constructs a die with a given number of sides. @param s the number of sides, e.g. 6 for a normal die */ public Die(int s) { sides = s; generator = new Random(); } /** Simulates a throw of the die @return the face of the die */ public int cast() { return 1 + generator.nextInt(sides); } private Random generator; private int sides; }
DieSimulation.java
Use the following class as your main class: /** This program simulates casting ten dice and prints out the average. */ public class DieSimulation { public static void main(String[] args) { final int TRIES = 10; DataSet ds = new DataSet(); for (int i = 1; i <= TRIES; i++) { Die d = new Die(6); int n = d.cast(); ds.add(d); System.out.print(n + " "); } System.out.println(); System.out.println("Average: " + ds.getAverage()); } }
Measurable.java
public interface Measurable { double getMeasure(); }
DataSet.java
public class DataSet { /** Constructs an empty data set. */ public DataSet() { sum = 0; count = 0; maximum = null; } public void add(Measurable x) { sum = sum + x.getMeasure(); if (count == 0 || maximum.getMeasure() < x.getMeasure()) maximum = x; count++; } public Measurable getMaximum() { return maximum; } /** Gets the average of the added data. @return the average or 0 if no data has been added */ public double getAverage() { if (count == 0) return 0; else return sum / count; } private double sum; private Measurable maximum; private int count; }
Hint from Section 9.1
class YourClassNameHere implements Measurable { public double getMeasure() { //Implementation--return the appropriate thing that is measured for your class } //Additional methods and fields }
P9.2. Define a class Quiz
that implements the Measurable
interface. A quiz has a score and a letter grade (such as B+). Use the implementation of the DataSet class
in Section 9.1 to process a collection of quizzes. Display the average score and the quiz with the highest score (both letter grade and score).
QuizTester.java
Use the following class as your tester class:
/** This program tests the Quiz and DataSet classes. */ public class QuizTester { public static void main(String[] args) { DataSet quizData = new DataSet(); Quiz q1 = new Quiz(89, "B+"); Quiz q2 = new Quiz(90, "A-"); Quiz q3 = new Quiz(73, "C"); quizData.add(q1); quizData.add(q2); quizData.add(q3); double avg = . . .; Quiz max = . . .; System.out.println("Average score: " + avg); System.out.println("Expected: 84"); System.out.println("Highest score: " + max.getScore()); System.out.println("Expected: 90"); System.out.println("Highest grade: " + max.getGrade()); System.out.println("Expected: A-"); } }
P9.10. Modify the Coin
class to have it implement the Comparable
interface.
Coin.java
/** A coin with a monetary value. */ public class Coin implements Comparable { /** Constructs a coin. @param aValue the monetary value of the coin. @param aName the name of the coin */ public Coin(double aValue, String aName) { value = aValue; name = aName; } /** Gets the coin value. @return the value */ public double getValue() { return value; } /** Gets the coin name. @return the name */ public String getName() { return name; } /** Compares two Coin objects. @param otherObject the object to be compared @return a negative integer, zero, or a positive integer as this coin is less than, equal to, or greater than the specified coin */ public int compareTo(Object otherObject) { //Your code here } private double value; private String name; }
CoinTester.java
Use the following class as your tester class:
/** This program tests the use of the Comparable interface in the coin class. */ public class CoinTester { public static void main(String[] args) { Coin c1 = new Coin(0.05, "nickel"); Coin c2 = new Coin(0.01, "penny"); int b = c1.compareTo(c2); if (b < 0) System.out.println("less"); else if (b > 0) System.out.println("more"); else System.out.println("equal"); System.out.println("Expected: more"); } }
Extra Credit: Making a BugComparator to be used with Gridworld Bugs
Here is a customized World for Gridworld where the message on the top is updated to show how many bugs there are and the location of the furthest Bug
in the grid. You need to
implement the Comparator
interface for the grid world Bug
class. You need to define a compare
method that returns an int
. Get the location of each Bug
and use the compareTo
method of Location
. Here is the starter code:
BugComparator.java
import info.gridworld.actor.*; import java.util.Comparator; /** * Bug Comparator is used so an ArrayList of Bugs can sorted * by the Loaction. It uses the getLocation() method of the * two different bugs. Lucky for us, the Location class implements * the Comparable interface, so we can use the Loaction's compareTo() method! */ public class BugComparator implements Comparator<Bug> { //your code here }
FarthestBugWorld.java
import java.awt.Color; import java.util.ArrayList; import info.gridworld.actor.*; import info.gridworld.grid.*; import java.util.Collections; public class FarthestBugWorld extends ActorWorld { public FarthestBugWorld(int rows, int cols, int numberOfBugs){ super(); this.setGrid(new BoundedGrid<Actor>(rows,cols)); if (numberOfBugs<1) numberOfBugs=1; for (int i=0; i<numberOfBugs; i++) this.add(new Bug() ); updateMessage(); } public void step() { super.step(); updateMessage(); } public void updateMessage() { ArrayList<Location> locs= getGrid().getOccupiedLocations(); ArrayList<Bug> bugs=new ArrayList<Bug>(); for (Location loc: locs){ Grid<Actor> gr = getGrid(); Actor a = gr.get(loc); if (a instanceof Bug ) bugs.add((Bug)a); } Collections.sort(bugs,new BugComparator()); int n=bugs.size(); setMessage(n+" bugs, furthest is at "+bugs.get(n-1).getLocation() ); } }
BugComparatorTester.java
public class BugComparatorTester { public static void main (String[] args) { FarthestBugWorld myWorld = new FarthestBugWorld(4,5,3); myWorld.show(); } }