I. Polymorphism A. Different classes can implement the same interface ------------------------------------------ SECOND ITERATION Allow subtraction r1-r2, and multiplication as formulas. ------------------------------------------ 1. In Eclipse, rename Formula to Sum, then add Sub, Mult classes. 2. Add test cases for simple products and differences. 3. Test it, and fix the type errors in the FormulaMap class What will be the return type of FormulaMap.get()? When we get a Formula from the FormulaMap, how can we evaluate it without knowing it's class? 4. Summary of use of interfaces and polymorphism ------------------------------------------ INTERFACES AND POLYMORPHISM |--------------------| | <> | | FormulaType | |--------------------| |--------------------| | evaluate(): double | |--------------------| / \ | | - - - - - - - - - - - - - - - - - | | | |-------| |-------| |-------| | Sum | | Diff | | Mult | |-------| |-------| |-------| |---------------------------| | FormulaMap | |---------------------------| |---------------------------| | get(String): FormulaType | | put(String, FormulaType) | |---------------------------| We can then write: FormulaType f = FormulaMap.get(name); f.evaluate(); ------------------------------------------ What's the advantage of using 3 separate classes, instead of one class, another instance variable, and a bunch of if-then-else statements? ------------------------------------------ POLYMORPHISM def: Code is polymorphic when it works for objects of more than one class. Example: FormulaType f = FormulaMap.get(name); f.evaluate(); ------------------------------------------ ------------------------------------------ IMPORTANCE OF POLYMORPHISM Helps progammers understand large software systems: they learn interfaces and standard method names they can ignore details Examples: All Java Objects have: boolean equals(Object o); String toString(); int hashCode(); All Iterators have: boolean hasNext(); Object next(); ------------------------------------------ II. inheritance A. motivation ------------------------------------------ WHY INHERITANCE? Want to avoid duplication of code in Sum, Mult, Diff classes. Want to make it easier to make similar classes (Divide, ...) ------------------------------------------ What problems happen if you write the same code twice? B. example What code was duplicated between Sum, Mult, and Diff? C. semantics 1. What happens when a subclass object is created? ------------------------------------------ OBJECT CREATION FormulaType f = new Diff(0, 1); ------------------------------------------ 2. What happens when a subclass method is called? ------------------------------------------ METHOD CALLS import calc.*; import java.util.Random; public class Client { private static Random rand = new Random(); public static void main(String [] argv) { BinaryFormula d = new Diff(0,1); BinaryFormula s = new Sum(2,3); Registers.put(0, 3.14); Registers.put(1, 10); Registers.put(2, 0); Registers.put(3, -100.0); System.out.println(d.evaluate()); System.out.println(s.evaluate()); BinaryFormula f; if (rand.nextBoolean()) { f = new Mult(0,3); } else { f = new Sum(1,2); } System.out.println(f.evaluate()); } } ------------------------------------------ 3. subtype polymorphism ------------------------------------------ SUBTYPE POLYMORPHISM The ability to call instance methods of a type on objects of its subtypes is called *(subtype) polymorphism*. For example: BinaryFormula f; ... f.evaluate() Also: Object x; ... x.toString(); x.equals(y); x.hashCode(); ------------------------------------------ How does subtype polymorphism help in our example? D. two aspects of inheritance ------------------------------------------ TWO ASPECTS OF INHERITANCE I. Subclassing (deriving) of Code Implementations of non-private instance declarations are: - available in subclass by default - methods can be overridden - fields can be shadowed Code for a subclass is derived from its superclass. II. Behavioral Subtyping of ADTs Specifications of non-private instance declarations are: - imposed on subclasses - methods specifications can be more restrictive (refined) Objects of a subtype can be act like objects of their supertype. ------------------------------------------ 1. these are distinct 2. it's best to make subclasses be behavioral subtypes 3. add new abstract classes to avoid NOPing methods ------------------------------------------ REFACTORING TO PRESERVE BEHAVIORAL SUBTYPING Wrong: public class Dequeue { public void addFront(Object o) {...} public void addRear(Object o) {...} /* ... */ } public class Stack extends Dequeue { public void addRear(Object o) { throw new ...Exception(); } } Refactoring 1: public abstract class List { public void addFront(Object o) {...} /* ... */ } public class Dequeue extends List { public void addRear(Object o) {...} /* ... */ } public class Stack extends List { /* ... */ } ------------------------------------------ 4. use interfaces to get subtyping without inheritance ------------------------------------------ ANOTHER REFACTORING public interface ListType { public void addFront(Object o); /* ... */ } public class Dequeue implements ListType { public void addFront(Object o) {...} public void addRear(Object o) {...} /* ... */ } public class Stack implements ListType { public void addFront(Object o) {...} public void addRear(Object o) {...} /* ... */ } ------------------------------------------ E. bad example ------------------------------------------ package calc; /** Negation of a register * @author Gary T. Leavens */ public class Negate extends BinaryFormula { /** Initialize this negation formula * to refer to the given register. */ public Negate(int reg) { super(reg, 0); } /** * @see calc.FormulaType#evaluate() */ public double evaluate() { return - leftValue(); } } ------------------------------------------ Why is this bad? How could we fix it? ------------------------------------------ BETTER package calc; /** Negation of a register * @author Gary T. Leavens */ public class Negate implements FormulaType { private /*@ spec_public @*/ int reg; /** Initialize this negation formula to refer * to the given register. */ /*@ requires 0 <= reg && reg < Registers.NUM_REGS; @ ensures this.reg == reg; @*/ public Negate(int reg) { this.reg = reg; } /** * @see calc.FormulaType#evaluate() */ public double evaluate() { return - Registers.get(reg); } } ------------------------------------------ Is there still any repeated code? F. Method inheritance (super) and abstract methods 1. motivation ------------------------------------------ ADDING 3 REGISTERS What code is duplicated below? package calc; /** Sum three registers * @author Gary T. Leavens */ public class Sum3 extends Sum { /** The third register to sum. */ private /*@ spec_public @*/ int reg3; /** * Initialize this Sum object to calculate * the sum of the given registers. */ public Sum3(int reg1, int reg2, int reg3) { super(reg1, reg2); this.reg3 = reg3; } /** * Evaluate the formula and return its value. */ /*@ also @ ensures (* \result is the sum of the value of @ all the register's values. *); @*/ public double evaluate() { return leftValue() + rightValue() + Registers.get(reg3); } } ------------------------------------------ ------------------------------------------ USE SUPER TO ELIMINATE THE DUPLICATION package calc; /** Sum three registers * @author Gary T. Leavens */ public class Sum3 extends Sum { /** The third register to sum. */ private /*@ spec_public @*/ int reg3; /** * Initialize this Sum object to calculate * the sum of the given registers. */ public Sum3(int reg1, int reg2, int reg3) { super(reg1, reg2); this.reg3 = reg3; } /** * Evaluate the formula and return its value. */ /*@ also @ ensures (* \result is the sum of the value of @ all the register's values. *); @*/ public double evaluate() { return super.evaluate() + Registers.get(reg3); } } ------------------------------------------ What would happen if you wrote "this.evaluate()" in evaluate()? toString() is defined in Object, which is the superclass of BinaryFormula. How does a call super.toString() work from inside Sum3? 2. abstract methods ------------------------------------------ ABSTRACT METHODS, CLASSES Def: an *abstract method* Def: an an *abstract class* ------------------------------------------ how do you write an abstract method declaration in C++? In Java? Does BinaryFormula have any abstract methods? What classes have we seen that are abstract? ------------------------------------------ INTERFACES AS EXTREMES OF ABSTRACT CLASSES package calc; /** Operations on formulas. * @author Gary T. Leavens */ public interface FormulaType { /** * Evaluate the formula and return its value. */ //@ ensures (* \result is the value of the formula. *); double evaluate(); /** * Does this formula have a positive value? */ boolean isPositive(); } ------------------------------------------ If we change FormulaType by adding the isPositive method, without adding an implementation in BinaryFormula, what does that do to BinaryFormula? 3. down calls (skip if already discussed) Can we write an implementation of isPositive() in BinaryFormula? ------------------------------------------ DOWN CALLS (TEMPLATE METHOD PATTERN) Def: A method call is a *down call* if it may call a method of a subclass. package calc; /** Binary formulas (like a+b). * @author Gary T. Leavens */ public abstract class BinaryFormula implements FormulaType { /* ... no evaluate() here ... */ /** * Does this formula have a * positive value? */ public boolean isPositive() { return this.evaluate() > 0; } } ------------------------------------------ 4. method overriding ------------------------------------------ METHOD OVERRIDING package calc; /** Binary formulas (like a+b). * @author Gary T. Leavens */ public abstract class BinaryFormula implements FormulaType { /* ... */ public int hashCode() { return left + right; } public boolean equals(Object o) { if (o == null || !(o instanceof BinaryFormula)) { return false; } BinaryFormula bf = (BinaryFormula) o; return bf.getClass() == this.getClass() && bf.left == this.left && bf.right == this.right; } public String toString() { return "r" + left + " " + this.opString() + " r" + right; } /** A string that represents the operation. * This is used in decoding. * @see #toString() */ //@ ensures \result != null; protected abstract String opString(); } ------------------------------------------ Where do we have to define opString? What happens when we call new Diff(1, 2).toString()? G. privacy in subclasses ------------------------------------------ PROTECTED PRIVACY LEVEL protected = ------------------------------------------ ------------------------------------------ EXAMPLES package examples; public class Privacy { protected int prot = 2; public final int pub = 3; protected void protM() {} public void pubM() {} } ------------------------------------------ ------------------------------------------ PROTECTED ACCESS Access depends both on the class where the call occurs, and the class of the object. For example... import examples.*; public class Client2 extends Privacy { protected void protM() { super.protM(); prot = 3; } public void pubM() { super.pubM(); } public void testPrivacy() { Client2 o = new Client2(); int i = 3; i = o.prot; o.prot = i; i = o.pub; o.protM(); o.pubM(); // Note: some below is illegal Privacy p = new Privacy(); i = p.prot; p.prot = i; i = p.pub; p.protM(); p.pubM(); } } ------------------------------------------ which parts are illegal? H. inheritance vs. composition ------------------------------------------ COMPOSITION VS. INHERITANCE Def: *composition* means ------------------------------------------ 1. when to use inheritance Given that both inheritance and composition allow objects in the subclass to hold information from another type's objects, why use inheritance at all? ------------------------------------------ WHEN TO USE INHERITANCE ------------------------------------------ Which of these have we followed? 2. when not to use inheritance ------------------------------------------ WHEN NOT TO USE INHERITANCE ------------------------------------------ III. User Interface Example A. Writing GUIs in Java 1. plan ------------------------------------------ INITIAL GUI SKETCH |-------------------------------------| | Formula-Register Calculator | |-------------------------------------| | [ 0.0 ] | |-------------------------------------| | Named Formulas | | | | [ add0 ][\/] [ r1 + r2] (Eval) | | | |-------------------------------------| | Registers | | | | r0 [ 0.0] r1 [ 0.1] | | r2 [ 2.2] r3 [ 0.3] | | r4 [ 0.4] r5 [ 0.5] | | r6 [ 60.6] r7 [ 0.7] | | r8 [ 0.8] r9 [ 0.9] | | | |-------------------------------------| | Formula Composition | | | | First Second | | Register Operator Register | | [ r0][\/] [ +][\/] [ r1][\/] | | | | Name [ ] ( Save ) | | ( Reset ) | |-------------------------------------| ------------------------------------------ 2. Infrastructure, building the Frame a. main program ------------------------------------------ package gui; import javax.swing.JFrame; /** The main program for the calculator. * @author Gary T. Leavens */ public class Calculator { /** * Create a calculator frame and show it. */ public static void main(String[] args) { CalculatorFrame frame = new CalculatorFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.show(); } } ------------------------------------------ b. JFrame ------------------------------------------ package gui; import javax.swing.*; /** A frame for the Calculator. * @author Gary T. Leavens */ public class CalculatorFrame extends JFrame { /** * Initialize this frame. */ public CalculatorFrame() { setTitle("Formula-Register Calculator"); setSize(WIDTH, HEIGHT); } /** The width of this frame, in pixels. */ public static final int WIDTH = 500; /** The height of this frame, in pixels. */ public static final int HEIGHT = 600; } ------------------------------------------ c. Layout ------------------------------------------ GRIDBAG LAYOUT RECIPE (From "Core Java 2, Volume 1", p. 519) 1. Sketch the layout on paper 2. Find a grid such that the smallest components fit inside single cells. 3. Label rows 0, 1, 2,..., and columns 0, 1, 2, ... from top left. The gridx, gridy are coordinates gridwidth, gridheight sizes of components (>1 if span cells) 4. Decide on fill (horizontal?, vertical? both?), and anchor (alignment) if not filled 5. Set weights to 100, unless you want components in a row or column to stay at their default size (use 0 for them). 6. Write code and double check constraints 7. Compile, run, and enjoy. ------------------------------------------ ------------------------------------------ STARTING A GRIDBAG LAYOUT Container contentPane = getContentPane(); GridBagLayout layout = new GridBagLayout(); contentPane.setLayout(layout); Then - create the components, and - for each component: a. set its constraints b. add it to the contentPane A sample: // textBox for output JTextField outputText = new JTextField(25); outputText.setEditable(false); outputText.setHorizontalAlignment(JTextField.RIGHT); // add components to the grid GridBagConstraints constraints = new GridBagConstraints(); constraints.fill = GridBagConstraints.NONE; constraints.anchor = GridBagConstraints.CENTER; constraints.gridx = 0; constraints.gridy = 0; constraints.gridwidth = 7; constraints.gridheight = 1; contentPane.add(outputText, constraints); ------------------------------------------ 3. Other stuff a. borders ------------------------------------------ MOTTO: YOU CAN DO IT YOU JUST HAVE TO LOOK UP HOW Example: - Make the output text border blue - give it a title Border lined = BorderFactory .createLineBorder(Color.BLUE); Border outputTitle = BorderFactory .createTitledBorder(lined, "Output"); outputText.setBorder(outputTitle); ------------------------------------------ b. a helper method for adding components: ------------------------------------------ A HELPER METHOD FOR ADDING COMPONENTS /** * Add the given component to the given grid bag layout. * This simplifies otherwise tedious code. See Core Java 2, * Volume 1, pp. 521-522. * @param c the component to add * @param constraints the grid bag constraints object to use * @param x the gird x position * @param y the grid y position * @param w the grid width (columns spanned) * @param h the grid height (rows spanned) */ private void add(Component c, GridBagConstraints constraints, int x, int y, int w, int h) { constraints.gridx = x; constraints.gridy = y; constraints.gridwidth = w; constraints.gridheight = h; getContentPane().add(c, constraints); } ------------------------------------------ 4. Adding the main labels: ------------------------------------------ JLabel nfLabel = new JLabel("Named Formulas"); JLabel regsLabel = new JLabel("Registers"); JLabel fcLabel = new JLabel("Formula Composition"); // add components to the grid add(nfLabel, constraints, 0, 1, 7, 1); add(regsLabel, constraints, 0, 3, 7, 1); add(fcLabel, constraints, 0, 9, 7, 1); ------------------------------------------ 5. Adding text fields and labels for registers ------------------------------------------ for (int i = 0; i < 10; i++) { regLabels[i] = new JLabel("r" + i); regTexts[i] = new JTextField(25); regTexts[i].setEditable(true); regTexts[i].setHorizontalAlignment(JTextField.RIGHT); Border rb = BorderFactory.createLineBorder(Color.BLACK); regTexts[i].setBorder(rb); } for (int i = 0; i < 10; i = i + 2) { constraints.anchor = GridBagConstraints.EAST; int row = 4+i/2; add(regLabels[i], constraints, 0, row, 1, 1); add(regLabels[i+1], constraints, 4, row, 1, 1); constraints.anchor = GridBagConstraints.WEST; constraints.fill = GridBagConstraints.NONE; add(regTexts[i], constraints, 1, row, 2, 1); add(regTexts[i+1], constraints, 5, row, 2, 1); } ------------------------------------------ 6. Adding listeners for the text fields ------------------------------------------ package gui; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import javax.swing.JTextField; import calc.Registers; /** Listener for the registers. * These keep track of a textfield and a register number, * and they put the contents of the text field into the register. * @author Gary T. Leavens */ public class RegisterInputListener implements FocusListener, ActionListener { /** The text field we listen to. */ private JTextField tf; /** The register we put the input into. */ private int regNum; /** * Initialize this RegisterInputListener to listen to events * on the given JTextField and output to the given register. * @param tf the text field * @param regNum the number of the register */ //@ requires tf != null && 0 <= regNum; public RegisterInputListener(JTextField tf, int regNum) { this.tf = tf; this.regNum = regNum; } /** Done when the user moves focus away. */ public void focusLost(FocusEvent e) { Registers.put(regNum, parseDoubleFrom(tf)); } /** Done when the user types the enter key. */ public void actionPerformed(ActionEvent e) { Registers.put(regNum, parseDoubleFrom(tf)); } /** Parse a Double from the given JTextField */ private double parseDoubleFrom(JTextField t) { try { return Double.parseDouble(t.getText().trim()); } catch (NumberFormatException e) { return Double.NaN; } } /** What is done when focus is gained. */ public void focusGained(FocusEvent e) { } } ------------------------------------------ ------------------------------------------ for (int i = 0; i < 10; i++) { /* ... */ regTexts[i] = new JTextField(25); /* ... */ // Add Listener for events on this text field RegisterInputListener ril = new RegisterInputListener(regTexts[i], i); regTexts[i].addActionListener(ril); regTexts[i].addFocusListener(ril); } ------------------------------------------ 7. Code cleanup