Chapter 9

<< Chapter 8 | HomeworkTrailIndex | Chapter 10 >>

Interfaces and Polymorphism

Demo1: Plinko

If you are not familiar with the game, here is a link to the game show (Skip to minute 4:00 if you're in a hurry)

Disk.java

import java.awt.Graphics2D;
import java.awt.geom.Ellipse2D;
import java.awt.Color;
public class Disk
{
     private int size;
     private int vSize;
     private int diam;
     private int numCol;
     private int numRow;
     private int col;
     private int row;

    /**
     * Constructor for objects of class Disk
     */

    public Disk(int aNumCol, int aNumRow, int s, int c)
    {
        this(aNumCol, aNumRow, s);
        col=c;
    }
    public Disk(int aNumCol, int aNumRow, int s)
    {
      numCol = aNumCol;
      numRow = aNumRow;
      diam=(int)(.5*s);
      size=s;
      vSize=(int)(s*Math.sin(Math.PI/3.0));
      col=(int)(Math.random()*numCol);
      row=0;
    }

    public void draw(Graphics2D g2)
   {
     g2.setColor(Color.BLUE );
     int hOffset=size/4;
     if (row%2==1) hOffset*=3;
     int vOffset=vSize/6;
     Ellipse2D.Double o=new Ellipse2D.Double(hOffset+col*size ,vOffset+row*vSize,diam,diam);
     g2.fill(o);
    }
   public void fall()
   {
       if (row<numRow-1) {
           row++;
           if (Math.random()>.5)
           {
              //move right

               int max=numCol-1;
               if (row%2==1){
                 if(col>=max)
                  col--;
                } else{
                  col++;
                  if (col>=max)
                  col=max;
                }
           } else{
               //move left
               col--;
               if (row%2==0)
                  col++;
               if (col<0)
                col=0;
           }
        }
    }
    public int getRow(){return row;}
    public int getCol(){return col;}
}

Board.java

import java.awt.Graphics2D;
import java.awt.Polygon;

import java.awt.Color;
/**
   This class displays a checkerboard with squares,
   alternating between white and black.
*/
public class Board
{

   private int numCol;
   private int numRow;
   private int size;   
/**
      Creates a Plinko Board object with a given number of rows and columns.
      @param aNumSquares the number of squares in each row
      @param aSize the size of each square
   */
   public Board(int aNumCol, int aNumRow, int aSize)
   {
      numCol = aNumCol;
      numRow = aNumRow;
      size = aSize;
   }

   /**
      Method used to draw the Plinko board.
      @param g2 the graphics content
   */
   public void draw(Graphics2D g2)
   {

       g2.setColor(new Color( 220,220,220 ) );
     int offset=0;
     for (int i = 0; i < numCol; i++)
      {
         int j=0;

         while (j<numRow)
         {
            if (j % 2 == 0){ 
               offset=0;
            } else {
               offset=size/2;
            }
            if ( i<numCol-1 || j%2==0){  //triange pointing down 
               Polygon t = new Polygon();
               int y=(int)(j*size*Math.sin(Math.PI/3.0));
               t.addPoint(offset+i * size, y);
               t.addPoint(offset+(i+1)*size,y);
               t.addPoint(offset+i*size+size/2, y+(int)(size*Math.sin(Math.PI/3.0)) ); 
               g2.fill(t);
            }
            j++;
         }
      }
      //Draw sides
      g2.setColor(Color.ORANGE);
      for (int j=0; j < numRow/2; j++){
          Polygon t=new Polygon();
          int vSize=(int)(2*size*Math.sin(Math.PI/3.0));
          t.addPoint(0,j*vSize);
          t.addPoint(size/2,vSize/2+j*vSize );
          t.addPoint(0,vSize+j*vSize);
          Polygon t2=new Polygon();
          t2.addPoint(size*numCol,j*vSize);
          t2.addPoint(size*numCol-size/2,vSize/2+j*vSize );
          t2.addPoint(size*numCol,vSize+j*vSize);
          g2.fill(t);
          g2.fill(t2);
        }
   }

}

PlinkClick.java

import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.awt.Rectangle;
public class PlinkClick extends JApplet implements MouseListener
{
   int c=5;
   final int NCOLS = 9;
   final int NROWS = 13;
   final String[] prize ={"$100","$500","$1,000","0","$10,000","0","$1,000","$500","$100"};
   public void init()
   {
       addMouseListener(this);
    }
    /**
     * Paint method for applet.
     * 
     * @param  g   the Graphics object for this applet
     */
    public void paint(Graphics g)
    {
      Graphics2D g2 = (Graphics2D) g;
       g2.setColor(Color.WHITE);
       Rectangle bg=new Rectangle(0,0,getWidth(),getHeight());
       g2.fill(bg);

      int size = Math.min(getWidth(), getHeight()) / NCOLS;
      Board pb = new Board(NCOLS,NROWS, size);
      Disk d = new Disk(NCOLS, NROWS, size, c);

      pb.draw(g2);
      d.draw(g2);
      for (int i=0;i<13;i++){
          d.fall();
          d.draw(g2);
        }
      g2.drawString( prize[ d.getCol() ], d.getCol()*size, getHeight()-15 );
    }
   /**
    * Implementation of the MouseListener
    */
   public void   mouseClicked(MouseEvent e) {}
   public void   mouseEntered(MouseEvent e) {}
   public void   mouseExited(MouseEvent e) {}
   public void   mousePressed(MouseEvent e) {}
   public void   mouseReleased(MouseEvent e) {
       int x=e.getX();
       c=(int)(x/(getWidth()/NCOLS));
       repaint();
    }
}

BoardComponent.java

import javax.swing.JComponent;
import java.awt.Graphics;
import java.awt.Graphics2D;

public class BoardComponent extends JComponent
{
   public void paintComponent(Graphics g)
   {
      Graphics2D g2 = (Graphics2D) g;

      final int NCOLS = 9;
      final int NROWS = 13;
      final String[] prize ={"$100","$500","$1,000","0","$10,000","0","$1,000","$500","$100"};
      int size = Math.min(getWidth(), getHeight()) / NCOLS;
      Board pb = new Board(NCOLS,NROWS, size);
      Disk d = new Disk(NCOLS, NROWS, size);

      pb.draw(g2);
      d.draw(g2);
      for (int i=0;i<13;i++){
          d.fall();
          d.draw(g2);
        }
      g2.drawString( prize[ d.getCol() ], d.getCol()*size, getHeight()-15 );
   }
}

BoardViewer.java

import javax.swing.JFrame;

/**
   This program displays a checkerboard.
*/
public class BoardViewer
{
   public static void main(String[] args)
   {
      JFrame frame = new JFrame();

      final int FRAME_WIDTH = 360;
      final int FRAME_HEIGHT = 500;

      frame.setSize(FRAME_WIDTH, FRAME_HEIGHT);
      frame.setTitle("Plinko Board Viewer");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

      BoardComponent component = new BoardComponent();
      frame.add(component);

      frame.setVisible(true);
   }
}

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? Out of luck... we can use one thing or another the way we implemented Measurable. Solution? Make the interface the most "generic" as possible.

public interface Measurer
{
   double measure(Object anObject);
}

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.

public class RectangleMeasurer implements Measurer
{
   public double measure(Object anObject)
   {
      Rectangle aRectangle = (Rectangle) anObject;
      double area = aRectangle.getWidth() * aRectangle.getHeight();
      return area;
   }
}

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]");
   }
}

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?


Review Exercises

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

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