Welcome to Lesson 13!

Learning Objectives

By the end of today's class, you should know...
  • What is a final class?
  • How to write abstract classes
  • How to describe the difference between abstract classes and regular classes
  • Why use abstract classes?
  • What is polymorphism?
  • What is dynamic binding and how does it apply to polymorphism?


 1. Final Classes, Methods and Parameters

  • The keyword final is used in many ways

Final Classes

  • Use final to prevent a class from being extended
  • For example:
    public final class String {}
    
  • String is an example of a class which cannot be extended from the Java API

Final Methods

  • Specifies that a method definition cannot be overridden with a new definition in a subclass
  • For example:
    private final void special() {
        System.out.println("I am special");
    }
    
  • Since you would never want the method to do anything else, it makes sense to make it final
  • Allows the compiler to generate more efficient code

Final Parameters

  • Use final in a parameter list to prevent a method from assigning a new value
  • For example:

    public void printArray(final char[] array, final int numChars) {
       for (int i = 0; i < numChars; i++) {
          System.out.println(array[i]);
        }
    }


  • This can prevent a bug where you might set new values to the parameter variables in error
  • The keyword final is not part of the method signature and does not affect how a method is overridden


2. Abstract Classes

Abstract Classes and Methods

  • When designing a program using inheritance, you often create a superclass that you do not want instantiated
  • Instead, you only want subclasses of this superclass to be instantiated
  • The superclass contains code common to all the subclasses but should not be instantiated
  • For example: Shape
  • You cannot draw a generic shape -- what would it look like?
  • Instead, you create subclasses that implement specific shapes
  • Here is an example of an inheritance hierarchy for drawing various shapes

Inheritance Hierarchy

abstract class Shape is inherited from by Line, Rectangle and Oval classes. Square inherits from Rectangle

Abstract Classes

  • One solution to preventing a superclass from being instantiated is to make it abstract
  • Abstract classes provide common code for all its subclasses
  • A subclass inherits all non-private variables and methods from the abstract class
  • An abstract class often defines a set of operations for subclasses, providing a common interface for the subclasses

Writing an Abstract Class

  • Any class can become abstract using the keyword abstract
  • Declaring a class abstract means that you cannot instantiate an object of the class
  • Instead, you must subclass the abstract superclass to make use of its operations
  • The following example shows a class defined as abstract

Example Abstract Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public abstract class Shape {
    // regular instance variable declarations
    private int x1, x2, y1, y2;

    // regular constructor and method declarations
    public Shape() { }
    public Shape(int newX1, int newY1, int newX2,
            int newY2) {
        x1 = newX1;
        x2 = newX2;
        y1 = newY1;
        y2 = newY2;
    }
    public int getX1() { return x1; }
    public int getX2() { return x2; }
    public int getY1() { return y1; }
    public int getY2() { return y2; }

    // Abstract method declaration
    public abstract void draw();
}

Abstract Methods

  • An abstract class may or may not contain abstract methods
  • An abstract method is a method declaration without a method body:
    public abstract void draw();
    
  • Any abstract methods must be implemented in any non-abstract subclass
  • You must declare a class abstract if it contains an abstract method

Example Inheriting from an Abstract Class:

public class Line extends Shape {
    @Override public void draw() { //do not include keyword abstract when overriding this method
        System.out.println("Drawing a Line.");
    }
}


Activity 13.1: An Abstract Account Class (10 pts)

  • Create a new project folder in Eclipse for this activity.
  • In this folder, create two new classes - Account.java and Savings.java and put your name in a Javadoc comment at the top.
  • Make the Account class an abstract class by adding the abstract keyword before the class keyword
  • Inside the Account class, add the following variables and methods:
    • A private variable called name (String)
    • A private variable called balance (double)
    • A two-argument constructor which takes in String and double parameters
    • A no argument constructor, which calls the two-argument constructor, passing in "name unknown" and 0.0 as arguments.
    • Getter and setter methods for the two variables
    • An abstract method called updateBalance
      • This method is void and has no parameters
    • A toString() method
  • Make the Savings class final by adding the final keyword before the class keyword - now this class cannot be extended (no inheritance from this class).
    • Also make Savings a subclass of Account
  • Inside the Savings class, add the following variables and methods:
    • A private variable called interestRate (double)
    • A three-argument constructor which takes in a String and two double parameters, and calls the two argument constructor of the Account class
    • A no argument constructor, which calls the three-argument constructor of Savings, passing in "name unknown", 0.0, and 0.0 as arguments.
    • Getter and setter methods for the interestRate variable
    • A method called updateBalance that overrides the abstract method of the super class
      • Note that it is required to override this method or your class will have an error
      • This method is void and has no parameters
      • It multiplies balance by 1 + interestRate and assigns the result to balance
    • A toString() method that overrides toString from the superclass.
  • Add a final class to your project folder called SavingsTest.java. Copy and paste the below code into this class.
/**
 * SavingsTest.java
 * @author
 * CIS 36B, Activity 13.1
 */

import java.util.Scanner;

public class SavingsTest {
    public static void main(String args[]) {
        Scanner input = new Scanner(System.in);
        System.out.println("Welcome!\n");
        System.out.print("Enter your name: ");
        String name = input.nextLine();
        System.out.print("Enter the balance to invest: $");
        double balance = input.nextDouble();
        System.out.print("Enter the interest rate on your target account: ");
        double interest = input.nextDouble();
       
        Savings savings = new Savings(name, balance, interest);
        System.out.println("\nHere is your account summary: \n" + savings);
       
        savings.updateBalance();
       
        System.out.printf("In one year, your balance"
                + " will be: $%.2f", savings.getBalance());
       
        input.close();
       
    }
}

  • When your program works as shown in the sample output, upload Account.java and Savings.java to Canvas.

Sample Output:


Welcome!

Enter your name: Grace Kang
Enter the balance to invest: $50000
Enter the interest rate on your target account: .025

Here is your account summary:
Name: Grace Kang
Balance: $50000.0
Interest Rate: 0.025
In one year, your balance will be: $51250.00


3. Introduction to Polymorphism

  • Any object oriented language must implement three programming mechanisms: Polymorphism, Inheritance and Encapsulation (PIE)
    • We have already covered encapsulation and inheritance
    • Thus, polymorphism is the last piece of the PIE.
  • The name polymorphism comes from the discipline of Biology, where is it is used to describe the phenomenon where one species can take on multiple forms (poly means many).
    • The most common example is sexual dimorphism, wherein individuals of a species might come in two or more forms (or sexes), such as male and female.
  • In object-oriented programming, we see polymorphism when subclasses can define their own unique behaviors (e.g. overriding methods of the parent class) while still retaining some behaviors of the parent class.
Polymorphism: the ability to redefine methods for subclasses and decide which definition to use at runtime.
  • Polymorphism lets us treat subclasses just like their superclass
  • This is an important ability because it allow us to generalize program code

Simple Polymorphism Example

  • What will the following display?

/**
 * Phone.java
 * @author Jennifer Parrish
 *
 */

public class Phone {
    private String brand;
    private String model;
    private double price;
   
    /**Constructor(s)*/
   
    public Phone() {
        this("Unknown brand", "Unknown model", 0.0);
    }
   
    public Phone(String theBrand, String theModel, double thePrice) {
        brand = theBrand;
        model = theModel;
        price = thePrice;
    }
   
    public void makeCall() {
        System.out.println("Making call from Phone.");
    }
   
    @Override public String toString() {
        return "Brand: " + brand 
            + "\nModel: " + model
            + "\nPrice: " + price;
    }
}


public class IPhone extends Phone {
    private boolean iTunesInstalled;
   
    IPhone() {
        this("model unknown", 0.0, false);
    }
   
    IPhone(String model, double price, boolean hasITunes) {
        super("iPhone", model, price);
        iTunesInstalled = hasITunes;
    }
   
    @Override public void makeCall() {
        System.out.println("Making call from iPhone.");
    }
}


public class PhoneTest {
    public static void main(String[] args) {
       
        Phone phone1 = new Phone();
        Phone phone2 = new IPhone();
        IPhone phone3 = new IPhone();
       // IPhone phone4 = new Phone(); //won't compile. Why not?
       
        phone1.makeCall();
        phone2.makeCall();
        phone3.makeCall();
       
       
    }
}


  • Why does the above work? Answer: Dynamic Binding

Static vs. Dynamic Binding

  • Binding: the process of associating a method definition with a method call.
  • Static binding: done at compile time
  • Dynamic binding: done at run-time
  • Compilation binds memory locations to methods before running a program
  • Binding done at compile time is called static, or early, binding
  • In contrast, binding done at run time is called dynamic, or late, binding
  • Java uses dynamic binding for all instance methods
  • In contrast, static methods are bound at compile time (static binding)
  • Polymorphism in Java relies on late binding of instance methods
  • For example, at compile-time, it looks as though we are making a Phone object:
Phone phone2 = new IPhone();
  • However, run-time is when memory is allocated on the heap (keyword new is invoked).
  • Thus, by waiting until run-time to determine correct type of phone2, this variable can be stored as an IPhone object and the overridden IPhone methods will be invoked, rather than the Phone methods:
    phone2.makeCall(); //invokes overridden method

Another Polymorphism Example

  • Suppose you are designing a graphics package
  • You have classes for several figures such as lines, rectangles and squares
  • Each figure is an object of a different class
  • In a well-designed program, all of these shapes would be subclasses of one superclass, call it Shape
    inheritance hierarchy for Shape

  • The superclass has code common to all the subclasses
  • For example, it might have variables for x and y coordinates to locate the object on the screen
  • Also, it would have get and set methods for the variables
  • In addition, the Shape class has an abstract methods named draw() that is overridden in each subclass
  • Each subclass only implements the code it needs, such as overriding the draw() method to draw its shape on the screen

The Application Program

  • To finish our design, we need to have a way to draw all the shapes on the screen
  • We make this easy, we decide to store all our shapes in an array named shapes[]
    private Shape shapes[];
  • We can store each object reference to our subclasses in the array
  • For instance:
    shape[x] = new Line()
  • All we have to do now is call the draw() method of each Shape subclass
  • This is easily and elegantly done using a for loop:
    for (int i = 0; i < shape.length; i++) {
        shape[i].draw();
    }
    
  • Will this design work?

Implementing Polymorphism

  • It turns out that this design works out well in Java
  • Calling the correct drawing method of the subclass is done automatically
  • Here is a simple implementation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
abstract class Shape {
    private int x1, x2, y1, y2;

 // regular constructor and method declarations public Shape() { } public Shape(int newX1, int newY1, int newX2, int newY2) { x1 = newX1; x2 = newX2; y1 = newY1; y2 = newY2; } public int getX1() { return x1; } public int getX2() { return x2; } public int getY1() { return y1; } public int getY2() { return y2; } // Abstract method declaration public abstract void draw(); } class Line extends Shape { @Override public void draw() { System.out.println("Drawing a Line."); } } class Rectangle extends Shape { @Override public void draw() { System.out.println("Drawing a Rectangle."); } } class Square extends Rectangle { @Override public void draw() { System.out.println("Drawing a Square."); } } class Oval extends Shape { @Override public void draw() { System.out.println("Drawing an Oval."); } } public class DrawingApp { private Shape shapes[] = {new Line(), new Rectangle(), new Square(), new Oval()}; public static void main(String args[]) { DrawingApp app = new DrawingApp(); app.drawShapes(); }     public void drawShapes() {
    for (int i = 0; i < shapes.length; i++) {         shapes[i].draw();         }
}
}

  • So why does this work?
  • Answer: dynamic binding

Making Changes

  • Let us suppose that our program is already written and in use
  • Now a customer wants us to add a new shape like a triangle
  • To make the change, we create a Triangle class and add it to the program
  • We want to make minimal changes and so have Triangle subclass Shape
  • Do we need to change Shape to implement this new class?

Adding a Triangle

  • Here is the code for our Triangle class:
    class Triangle extends Shape {
        @Override public void draw() {
            System.out.println("Drawing a Triangle.");
        }
    }
    
  • All need now is a way to add Triangle objects to the shapes array
  • For our simple application:
    private Shape shapes[] = { new Line(),
            new Rectangle(), new Square(),
            new Oval(), new Triangle() };
    
  • Note how easy it is to change a polymorphic program
  • One of the chief advantages of polymorphism is extensibility

More Information:


Activity 13.2: Candy Polymorphism (10 pts)

  • For this activity we will represent a bag of candy using polymorphism and ArrayLists
  • Create a new project folder, and copy and paste the below abstract class into a new Java filed called Candy.java:
/**
 * Candy.java
 * @author
 * CIS 36B, Activity 13.2
 */

import java.util.ArrayList;

public abstract class Candy {
    private int numCalories;
    private ArrayList<String> ingredients;
   
    public Candy() {
        this(0, new ArrayList<String>());
    }
   
    public Candy(int numCalories, ArrayList<String> ingredients) {
        this.numCalories = numCalories;
        this.ingredients = ingredients;
    }
   
    public Candy(Candy c) {
        if (c != null) {
            numCalories = c.numCalories;
            ingredients = new ArrayList<String>(c.ingredients); //calling ArrayList copy constructor
        }
    }
   
    public int getNumCalories() {
        return numCalories;
    }
   
    public void setNumCalories(int numCals) {
        numCalories = numCals;
    }
   
    public void addIngredient(String ingredient) {
        ingredients.add(ingredient);
    }
   
    public abstract void printCandyGreeting();
   
    @Override public String toString() {
        String result = "Total Calories " + numCalories;
        result += "\nIngredients:\n";
        for (int i = 0; i < ingredients.size(); i++) {
            result += ingredients.get(i) + "\n";
        }
        return result;
               
    }
   
}   
   
  • Next, copy and paste the below class into a file called MilkyWay.java
/**
 * MilkyWay.java
 * @author
 * CIS 36B, Activity 13.2
 */
public class MilkyWay extends Candy {
   
    private String size;
    private String flavor;
   
    public MilkyWay() {
        super();
        size = "unknown size";
        flavor = "unknown flavor";
    }
   
    public String getSize() {
        return size;
    }
   
    public String getFlavor() {
        return flavor;
    }
   
    public void setSize(String size) {
        this.size = size;
    }
   
    public void setFlavor(String flavor) {
        this.flavor = flavor;
    }
   
    @Override public void printCandyGreeting() {
        System.out.println("Welcome to the Milky Way " + flavor + "!");
    }
   
    @Override public String toString() {
        return "Flavor: " + flavor
                + "\nSize: " + size
                + "\n" + super.toString();
    }

}
  • Finally, copy and paste the below class into a file called CandyBag.java
/**
 * CandyBag.java
 * @author
 * CIS 36B, Activity 13.2
 */

import java.util.ArrayList;

public class CandyBag {
    private ArrayList<Candy> bag = new ArrayList<Candy>();
   
    public void printBag() {
        System.out.println("Candy Greetings:\n");
        for (int i = 0; i < bag.size(); i++) {
            bag.get(i).printCandyGreeting();
        }
        System.out.println("\n");
        for (int i = 0; i < bag.size(); i++) {
            System.out.println(bag.get(i));
        }
       
    }
   
    public static void main(String[] args) {
        CandyBag candy = new CandyBag();
       
        MilkyWay midnight = new MilkyWay();
        midnight.setSize("MINI");
        midnight.setFlavor("MIDNIGHT");
        midnight.addIngredient("SEMISWEET CHOCOLATE");
        midnight.addIngredient("CORN SYRUP");
        midnight.addIngredient("SUGAR");
        midnight.addIngredient("HYDROGENATED PALM KERNEL OIL");
        midnight.addIngredient("SKIM MILK");
        midnight.setNumCalories(38);
       
        candy.bag.add(midnight);
       
        MilkyWay original = new MilkyWay();
        original.setSize("FUN SIZE");
        original.setFlavor("ORIGINAL");
        original.addIngredient("MILK CHOCOLATE");
        original.addIngredient("CORN SYRUP");
        original.addIngredient("SUGAR");
        original.addIngredient("HYDROGENATED PALM KERNEL OIL");
        original.addIngredient("SKIM MILK");
        original.setNumCalories(80);
       
        candy.bag.add(original);
       
        candy.printBag();
    }
}

  • Run the code to see the output and make sure you understand how the program works.
    • Where does the Polymorphism occur in this code?
  • Next, it is your job to write one more class that extends Candy.
    • Please select any brand of candy that you would like to represent your class
    • Note that your class should be similar but not identical to MilkyWay.java
    • In other words, it is up to you to decide what private member variables to declare - try to invent different variables from MilkyWay
  • Requirements for your class:
    • It must declare two additional member variables, along with getters and setters
    • It must implement a toString() and a printCandyGreeting() method
    • It must have at least one constructor (a default constructor) that calls super
  • Next, in CandyBag.java, you will need to add an object of your candy type to the bag.
    • Make sure to provide calorie and ingredient information for the candy objects
    • You can find this information online if you want to be accurate.
  • When you are finished, you should get the below output - but with an additional candy type displayed:
Candy Greetings:

Welcome to the Milky Way MIDNIGHT!
Welcome to the Milky Way ORIGINAL!


Flavor: MIDNIGHT
Size: MINI
Total Calories 38
Ingredients:
SEMISWEET CHOCOLATE
CORN SYRUP
SUGAR
HYDROGENATED PALM KERNEL OIL
SKIM MILK

Flavor: ORIGINAL
Size: FUN SIZE
Total Calories 80
Ingredients:
MILK CHOCOLATE
CORN SYRUP
SUGAR
HYDROGENATED PALM KERNEL OIL
SKIM MILK

  • Upload CandyBag.java, and your additional class to Canvas when you are finished.

Wrap Up: 

  • Answer the Practice Exam questions from this lesson on Canvas.

Upcoming Assignments:

  • Activities 13.1 and 13.2 due Tuesday at 11:59pm
  • Lesson 13 Practice Exam Questions due Tuesday at 11:59pm
  • Quiz 6 due Friday at 11:59pm
  • Lab 7 due next Monday at 11:59pm