Chapter 9

<< Chapter 8 | HomeworkTrailIndex | Chapter 10 >>

Interfaces and Polymorphism

video Parts 1 & 2

Video Part 3

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

  1. Take a look at the API for the java.awt.Polygon class. Can you make a measurer for Polygon?
  2. Can you find a way to make this work to find the most valuable coin?
  3. Can you adapt this to find the coin with the longest name?

Implementing an Interface to sort

States Applet Sort People


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

Watch a working demonstration

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();
	}

}