Lecture 3: More Classes, Interfaces and Abstract Classes

Balasubramanian Narasimhan
Department of Statistics
Stanford University
Stanford, CA 94305

Revision of Date

Table of Contents

Abstract

In the last class, we talked about classes, inheritance and scope. We'll continue for a bit and I will introduce Interfaces, a very powerful and useful construct for a particular style of programming. Basically, interfaces define contracts and those classes that implement interfaces honor the contract. I will also discuss Abstract Classes.

More on Classes

[*]

Let's begin with a note regarding invoking methods on objects and accessing an object's fields.

<A.java>=
public class A  {

    public String name = "This is an A";

    public String toString() {
        return "I am an A";
    }

} // A

<B.java>=
public class B extends A {

    public String name = "This is a B";
    
    public String toString() {
        return "I am a B";
    }
} // B

<TestAB.java>=
public class TestAB {
    public static void main(String[] args) {
        B aB = new B();
        A anA = aB;
        Object o =  (Object) aB;
        A anotherA = (A) o;
        
        System.out.println(anA.toString() + "; name is " + anA.name);
        System.out.println(aB.toString() + "; name is " + aB.name);
        System.out.println(o.toString());
        System.out.println(anotherA.toString() + "; name is " + anotherA.name);
    }
} // TestAB

What do you think the output of this program is?

When a method is invoked on an object, the actual type of the object determines which method is invoked. When you access a field, the declared type of the field determines the value.

Final Classes

[*]

Some classes can be marked final which means that they cannot be extended. Methods that are marked final cannot be overriden either. One can also make a class extensible and mark all methods final, in which case, an subclass will have to write other methods than the ones marked final.

Abstract Classes

[*]

Sometime, you may have a base class that factors out things common to all its subclasses. For example, consider a simple distribution class.

<Distribution.java>=
public abstract class Distribution {       
  /**
   * The Distribution Parameter object that handles parameter values.
   */
  protected DistributionParameter distributionParameter;

  /**
   * The String identifying the family Name.
   */
  protected String familyName;

  /**
   * Returns the cumulative distribution function
   * @param the value 
   */
  public abstract double cdf(double argValue);

  /**
   * Returns a dataset of random numbers from the distribution.
   * @param the sample size
   * @return a dataset.
   */
  public abstract Dataset rand(int argSampleSize);

  /**
   * Returns the minimum value this random variable can take.
   * If infinite, Double.NEGATIVE_INFINITY is returned.
   * @return the minimum
   */
  public abstract double minSupport();

  /**
   * Returns true if minSupport is inclusive.
   * @return true or false
   */
  public boolean minSupportInclusive();
        
  /**
   * Returns the maximum value this random variable can take.
   * If infinite, Double.POSITIVE_INFINITY is returned.
   * @return the maximum
   */
  public abstract double maxSupport();

  /**
   * Returns true if maxSupport is inclusive.
   * @return true or false
   */
  public boolean maxSupportInclusive();

  /**
   * Returns the family name of the distribution.
   * @return the family name.
   */
  public String getFamilyName() {
    return familyName;
  }

  /**
   * Returns the parameter of the distribution. 
   * @return the parameter object.
   */
  public DistributionParameter getParameter() {
    return distributionParameter;
  }

  /**
   * Sets the parameters of the distribution. 
   * @param the new DistributionParameter object.
   */
  public void setParameter(DistributionParameter argDistributionParameter) {
    distributionParameter = argDistributionParameter;
  }

  /**
   * Returns an identifier that identifies the specific member of
   * the distribution family
   * @return a string
   */
  public String identifier() {
    return familyName + "(" + distributionParameter.toString() + ")";
  }

  /**
   * Returns an short identifier that identifies the specific member of
   * the distribution family
   * @return a string
   */
  public String shortIdentifier() {
    return familyName + "(" + distributionParameter.toBriefString() + ")";
  }
}

Note the following.

Thus part of the implementation is completed, and classes that extend this class must complete the rest. Abstract classes are useful when some of the behavior is true for most or all objects of a given type but some of the behavior makes sense only for particular types of objects and not a general superclass.

In our case, many of the methods make sense for all distributions. However, some behavior does not. For example, I could not stick in a getPMF method, for that applies only to discrete distributions. Same for getPDF which applies only to continuous distributions.

Indeed, abstract classes can be extended by classes and other abstract classes as the following shows.

<DiscreteDistribution.java>=
package distribution;
import dataset.*;

public abstract class DiscreteDistribution extends Distribution {
  /**
   * Returns the probability mass function at the specified integer.
   * @param the integer at which the pmf is desired.
   * @return the probability 
   */
  public abstract double pmf(int argValue);

  /**
   * Returns the quantile for the specified probability.
   * @param the cumulative probability whose quantile is desired.
   * @return the quantile
   */
  public abstract int quantile(double argProbability);

  /**
   * Returns a random number from the distribution.
   * @return a random integer from the distribution.
   */
  public abstract int rand();

  /**
   * Returns a dataset of sample values from the distribution
   * Print format is set to %10.0f by default.
   * @return a dataset
   */
  public Dataset rand(int argSampleSize) {
    double data[] = new double[argSampleSize];
    for (int i = 0; i < argSampleSize; i++) {
       data[i] = (double) rand();
    }
    Dataset dataset = new Dataset("Sample from " + identifier(), 
                                   argSampleSize, data,
                                     "Sample Values");
    dataset.varPrintFormats[0] = "%10.0f";
    return dataset;
  }       
}

<BinomialDistribution.java>=
package distribution;

import Probability.*;

public class BinomialDistribution extends DiscreteDistribution {
  /**
   * Constructor.
   * @param the number of trials
   * @param the success probability
   */ 
  public BinomialDistribution (int argNumberOfTrials, 
                               double argSuccessProbability) {
    familyName = "Binomial";
    String[] parameterNames = new String[2];
    parameterNames[0] = "No. of Trials";
    parameterNames[1] = "Success Probability";
    String[] parameterPrintFormats = new String[2];
    parameterPrintFormats[0] = "%.0f";
    parameterPrintFormats[1] = "%.4f";
    double[] parameterValues = new double[2];
    parameterValues[0] = (double) argNumberOfTrials;
    parameterValues[1] = argSuccessProbability;
    distributionParameter = new DistributionParameter(parameterNames,
                                         parameterValues,
                                         parameterPrintFormats);
  }
  /**
   * Returns the CDF of the binomial distribution.
   * @param the point
   * @return the cdf
   */
  public double cdf (double argValue) {
    double[] parameterValues = distributionParameter.getParameterValues();
    return Probability.binomialCDF((int) argValue, 
                                   (int) parameterValues[0], 
                                   parameterValues[1]);
  }

  /**
   * Returns the minimum support
   * If infinite, Double.NEGATIVE_INFINITY is returned.
   * @return the minimum
   */
  public double minSupport() {
    return 0.0;
  }

  /**
   * Returns true if minSupport is inclusive.
   * @return true or false
   */
  public boolean minSupportInclusive() {
    return true;
  }

  /**
   * Returns the maximum support
   * If infinite, Double.POSITIVE_INFINITY is returned.
   * @return the maximum
   */
  public double maxSupport() {
    double[] parameterValues = distributionParameter.getParameterValues();
    return parameterValues[0];
  }

  /**
   * Returns true if maxSupport is inclusive.
   * @return true or false
   */
  public boolean maxSupportInclusive() {
    return true;
  }

  /**
   * Returns the PMF of the Binomial distribution.
   * @param the point
   * @return the pmf
   */
  public double pmf (int argValue) {
    double[] parameterValues = distributionParameter.getParameterValues();
    return Probability.binomialPMF(argValue, 
                                    (int) parameterValues[0], 
                                    parameterValues[1]);
  }
  /**
   * Returns the Quantile of the Binomial distribution.
   * @param the probability
   * @return the quantile
   */
  public int quantile(double argValue) {
    double[] parameterValues = distributionParameter.getParameterValues();
    return Probability.binomialQuantile(argValue,
                                         (int) parameterValues[0],
                                         parameterValues[1]);
  }
  /**
   * Returns a random value from the Binomial distribution.
   * @return a random int
   */
  public int rand () {
    double[] parameterValues = distributionParameter.getParameterValues();
    return Probability.binomialRand((int) parameterValues[0], 
                                     parameterValues[1]);
  }
}

Interfaces

[*]

An Interface declares a type of object by specifying what the object does. In other words, an Interface does not care about any implementation, only what functionality is provided. This is best understood in the context of a problem.

Consider writing a simple univariate function optimizer. For simplicity assume that your optimizer takes as input a differentiable univariate function, a range over which the function is to be optimized and returns the answer. Here is one way we could start:

<Optimizer>=
public class Optimizer {
  public double findMin(DifferentiableUnivariateFunction f, 
                        double left, 
                        double right) {
  ...
  } 
}

A little thought shows that we can refine this further. First let us define a univariate function as one that can compute a value at a point.

<UnivariateFunction.java>=
public interface UnivariateFunction {
   public double valueAt(double x);
}

<DifferentiableFunction.java>=
public interface DifferentiableFunction {
   public double getDerivateAt(double x);
}

<DifferentiableUnivariateFunction.java>=
public interface DifferentiableUnivariateFunction extends UnivariateFunction,
                                                          DifferentiableFunction {
}

<AFunction.java>=
public class AFunction implements DifferentiableUnivariateFunction {
   public double valueAt(double x) {
     return Math.exp(-x * x);
   }

   public double getDerivateAt(double x) {
     return -2.0 * x * Math.exp(-x * x);
   }   
}

Notice while Java classes have single inheritance, interfaces have multiple inheritance. Also note that unlike abstract classes, interfaces have public methods and only constant variables.

I find it almost always better to work with interfaces since they never tie me to a hierarchy. Inheritance on the other hand can be very limiting in this aspect and changing a hierarchy is usually not easy. Using interfaces also forces one to abstract features of a computation. There is always room for refinement.

The JDE environment is helpful when implementing interfaces. The Interface Wizard helps you implement the methods in an interface.

GUI for Whois

[*]

Last time I promised a GUI for whois. Here it is:

<WhoisGUI.java>=

/**
 * WhoisGUI.java
 *
 *
 * Created: Thu Jan 20 14:58:22 2000
 *
 * @author Balasubramanian Narasimhan
 * @version
 */

import javax.swing.*;
import javax.swing.text.html.*;
import javax.swing.border.*;
import javax.swing.event.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;

public class WhoisGUI  extends JFrame implements ActionListener {
    JTextField jt;
    JEditorPane output;
    
    public WhoisGUI() {
        JMenuBar mbar = new JMenuBar();
        JMenu fileMenu = new JMenu("File");
        mbar.add(fileMenu);
        JMenuItem exitItem = new JMenuItem("Exit");
        fileMenu.add(exitItem);
        exitItem.setActionCommand("exit");
        exitItem.addActionListener(this);
        
        getRootPane().setJMenuBar(mbar);
        
        // Store the content pane in a variable for easier 
        // access.
        // Components will all be added to this panel.
        JPanel contentPane = (JPanel) getContentPane();
        
        GridBagLayout gbl = new GridBagLayout();
        GridBagConstraints gbc = new GridBagConstraints();
        contentPane.setLayout(gbl);

        jt = new JTextField();
        AddComponent.addComponent(contentPane, jt,
                                  gbl,
                                  gbc,
                                  GridBagConstraints.BOTH, // fill
                                  GridBagConstraints.CENTER, // anchor
                                  100.0,  // weightx
                                  0,    // weighty
                                  0,      // gridx
                                  0,      // gridy
                                  2,      // ipadx
                                  2,      // ipady
                                  1,      // gridwidth 
                                  1);     // gridheight

        JButton jb = new JButton("Submit");
        jb.setActionCommand("Submit");
        jb.addActionListener(this);
        AddComponent.addComponent(contentPane, jb,
                                  gbl,
                                  gbc,
                                  GridBagConstraints.BOTH, // fill
                                  GridBagConstraints.CENTER, // anchor
                                  0.0,  // weightx
                                  0,    // weighty
                                  1,      // gridx
                                  0,      // gridy
                                  2,      // ipadx
                                  2,      // ipady
                                  1,      // gridwidth 
                                  1);     // gridheight

        output = new JEditorPane();
        //      output.setContentType("text/plain");
        output.setEditable(false);
        JScrollPane scrollPane = 
            new JScrollPane(output, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
                            JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        AddComponent.addComponent(contentPane, scrollPane,
                                  gbl,
                                  gbc,
                                  GridBagConstraints.BOTH, // fill
                                  GridBagConstraints.CENTER, // anchor
                                  100.0,  // weightx
                                  100.0,    // weighty
                                  0,      // gridx
                                  1,      // gridy
                                  2,      // ipadx
                                  2,      // ipady
                                  2,      // gridwidth 
                                  1);     // gridheight


        //    scrollPane.setViewportView( introPanel);
        // Add window closing event watcher
        addWindowListener(new BasicWindowMonitor());
    }
    
    /**
     * Handle Window closing event
     */
    public void actionPerformed(ActionEvent e) {
        String command = e.getActionCommand();
        if (command == "exit") {
            System.exit(0);
        } else if (command == "Submit") {
            output.setEditable(true);
            output.setText(getResult());
            output.setEditable(false);
        }
    }


    public String getResult() {
        Runtime r = Runtime.getRuntime();
        BufferedReader bir = 
            new BufferedReader(new InputStreamReader(System.in));
        String input;
        StringBuffer result = new StringBuffer(1024);
        try {
            input = jt.getText();
            Process p = r.exec("whois " + input);
            BufferedReader bpr 
                = new BufferedReader(new InputStreamReader(p.getInputStream()));
            boolean print = true;
            String output;
            while ((output = bpr.readLine()) != null) {
                if (print) {
                    if (output.startsWith(" -------")) {
                        print = false;
                    } else {
                        result.append(output + "\n");
                    }
                } else if (output.startsWith(" --------")) {
                    print = true;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result.toString();
    }
    
    public static void main (String[] args) {
        WhoisGUI w = new WhoisGUI();
        w.setTitle("Whois GUI");
        w.setSize(800, 600);
        w.show();
    }
} // WhoisGUI

<BasicWindowMonitor.java>=
import java.awt.event.*;
import java.awt.Window;

public class BasicWindowMonitor extends WindowAdapter {
    
    public void windowClosing(WindowEvent e) {
        Window w = e.getWindow();
        w.setVisible(false);
        w.dispose();
        System.exit(0);
    }
    
} // BasicWindowMonitor

<AddComponent.java>=
import java.awt.*;

public class AddComponent {
  public static void addComponent(Container container, Component component,
                                  GridBagLayout gbl,
                                  GridBagConstraints gbc, int fill,
                                  int anchor, double weightx, double weighty,
                                  int gridx, int gridy,
                                  int ipadx, int ipady,
                                  int width, int height) {
      gbc.insets = new Insets(2, 2, 2, 2);
      gbc.fill = fill;
      gbc.anchor = anchor;
      gbc.weightx = weightx;
      gbc.weighty = weighty;
      gbc.gridx = gridx;
      gbc.gridy = gridy;
      gbc.ipadx = ipadx;
      gbc.ipady = ipady;
      gbc.gridwidth = width;
      gbc.gridheight = height;
      gbl.setConstraints(component, gbc);
      container.add(component);
  }
}

Indices

[*]

Code Chunks

[*] This index is generated automatically. The numeral is that of the first definition of the chunk.

Index of Identifiers

[*] Here is a list of the identifiers used, and where they appear. Underlined entries indicate the place of definition. This index is generated automatically.