Welcome to Lesson 13!
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
Final Methods
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 ClassesAbstract 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 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
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.
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
- 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
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
|