CISC 3115
Modern Programming Techniques
Lecture 7
Class Inheritance

Sample Classes — Counter & UpperBoundedCounter

A Sample Application Program

App.java App.out
public class App {
	public static void main(String [] args) {
		System.out.println("Playing with Counter");

		Counter c = new Counter();
		System.out.println("Initially: " + c);

		for (int i = 1; i <= 20; i++)
			c.up();
		System.out.println("After 20 up's: " + c);

		for (int i = 1; i <= 3; i++)
			c.down();
		System.out.println("After 3 down's: " + c);

		System.out.println();	//---------

		System.out.println("Playing with UpperBoundedCounter");

		UpperBoundedCounter ubc = new UpperBoundedCounter(10);
		System.out.println("Initially: " + ubc);

		for (int i = 1; i <= 20; i++)
			ubc.up();
		System.out.println("After 20 up's: " + ubc);

		for (int i = 1; i <= 3; i++) 
			ubc.down();
		System.out.println("After 3 down's: " + ubc);
	}
}
			
Playing with Counter
Initially: A counter with value 0
After 20 up's: A counter with value 20
After 3 down's: A counter with value 17

Playing with UpperBoundedCounter
Initially: A upper-bounded counter with value 0 and limit 10
After 20 up's: A upper-bounded counter with value 10 and limit 10
After 3 down's: A upper-bounded counter with value 7 and limit 10
			

Some Terminology

Implementation 1 — Unrelated Types

Counter.java UpperBoundedCounter.java
public class Counter {
	Counter() {val = 0;}

	void up() {val++;}	
	void down() {val--;}	

	int getVal() {return val;}

	public String toString() {
		return "A counter with value " + val;
	}

	private int val;
}
			
public class UpperBoundedCounter {
	UpperBoundedCounter(int limit) {
		val = 0;
		this.limit = limit;
	} 

	void up() {if (val < limit) val++;}	
	void down() {val--;}	

	int getVal() {return val;}
	int getLimit() {return limit;}

	public String toString() {
		return "A upper-bounded counter with value " + val + 
				" and limit " + limit;
	}

	private int val;
	private int limit;
}
			

Implementation 2 — Composition

Counter.java UpperBoundedCounter.java
public class Counter {
	Counter() {val = 0;}

	void up() {val++;}	
	void down() {val--;}	

	int getVal() {return val;}

	public String toString() {
		return "A counter with value " + val;
	}

	private int val;
}
			
public class UpperBoundedCounter {
	UpperBoundedCounter(int limit) {
		counter = new Counter();
		this.limit = limit; 
	}	

	// New methods
	void up() {if (counter.getVal() < limit) counter.up();}	
	int getLimit() {return limit;}
	public String toString() {
		return "A upper-bounded counter with value " + getVal() + 
				" and limit " + limit;
	}

	//Delegation methods
	void down() {counter.down();}
	int getVal() {return counter.getVal();}

	
	private Counter counter;	
	private int limit;
}
			

Implementation 3 — Inheritance

Removing the private of the parent's instance variable (val)

Counter.java UpperBoundedCounter.java
public class Counter {
	Counter() {val = 0;}

	void up() {val++;}	
	void down() {val--;}	

	int getVal() {return val;}

	public String toString() {
		return "A counter with value " + val;
	}

	/*private*/ int val;
}
			
public class UpperBoundedCounter extends Counter {
	UpperBoundedCounter(int limit) {this.limit = limit;} 

	// Overridden methods
	void up() {if (val < limit) val++;}
	public String toString() {
		return "A upper-bounded counter with value " + val + 
				" and limit " + limit;
	}

	// new method
	int getLimit() {return limit;}

	private int limit;
}
			
Notes

Using protected

Counter.java UpperBoundedCounter.java
public class Counter {
	Counter() {val = 0;}

	void up() {val++;}	
	void down() {val--;}	

	int getVal() {return val;}

	public String toString() {
		return "A counter with value " + val;
	}

	protected int val;
}
	
			
public class UpperBoundedCounter extends Counter {
	UpperBoundedCounter(int limit) {this.limit = limit;} 

	// Overridden methods
	void up() {if (getVal() < limit) super.up();}	
	public String toString() {i
		return "A upper-bounded counter with value " + val + 
				" and limit " + limit;
	}

	int getLimit() {return limit;}

	private int limit;
}
			
Notes

Using the parent's methods (super)

Counter.java UpperBoundedCounter.java
public class Counter {
	Counter() {val = 0;}

	void up() {val++;}	
	void down() {val--;}	

	int getVal() {return val;}

	public String toString() {
		return "A counter with value " + val;
	}

	private int val = 0;
}
	
			
public class UpperBoundedCounter extends Counter {
	UpperBoundedCounter(int limit) {this.limit = limit;} 

	// Overridden methods
	void up() {if (getVal() < limit) super.up();}	
	public String toString() {
		return "A upper-bounded counter with value " + 
				getVal() + " and limit " + limit;
		}

	int getLimit() {return limit;}

	private int limit;
}
			

More Terminology

The Substitution Principle — A Consequence of the is-a Nature of Inheritance

Polymorphism

App2.java App2.out
public class App2 {
	public static void main(String [] args) {
		Counter [] counters = {new Counter(), new UpperBoundedCounter(10)};

		for (int i = 0; i <  counters.length; i++) {
			System.out.println("Playing with counters[" + i + "]:");
			doIt(counters[i]);
		}
	}

	static void doIt(Counter counter) {
		System.out.println("\tInitially: " + counter);

		for (int i = 1; i <= 20; i++)
			counter.up();
		System.out.println("\tAfter 20 up's: " + counter);

		for (int i = 1; i <= 3; i++)
			counter.down();
		System.out.println("\tAfter 3 down's: " + counter);
	}
}
			
Playing with counters[0]:
	Initially: A counter with value 0
	After 20 up's: A counter with value 20
	After 3 down's: A counter with value 17
Playing with counters[1]:
	Initially: An upper-bounded counter with value 0 and limit 10
	After 20 up's: An upper-bounded counter with value 10 and limit 10
	After 3 down's: An upper-bounded counter with value 7 and limit 10
			

What's going on here?

Static vs Dynamic Types

Given the following declarations:
Counter c;
UpperBoundedCounter ubc;
it is clear that the type of the variable c is the class Counter (and similarly for the type of ubc. If we now add code to create objects to be referenced by the variables:
Counter c = new Counter();
UpperBoundedCounter ubc = new UpperBoundedCounter(12);
while we can tell the types of the objects reference by c (it's a Counter object) and y ubc (an UpperBoundedCounter object), it's important to realize that the objects are not actually created until we execute the program (and actually call the new operator).

The Rule of Polymorphism

As a result of the above, at runtime, there are two types associated with a reference variable: The 'rule of polymorphism' states that when we call an instance method of a class on a receiver, we look for the method in the class of the object rather than the class of the variable, i.e., we use the dynamic type rather than the static type to determine which class' methods we use.

Saying it Another Way

A Consequence of the Above Consequence

Up- and Down-Casting

The is-arelationship between subclass and superclass, and the consequential Substitution Principle, tells us that an object of a subclass can always appear in any context that an object of the superclass can appear in.

Runtime vs Compile-time Polymorphism

Initializing the Superclass

Consider the following class:

class Name {
	Name(String first, String last) {
		this.first  first;
		this.last = last;
	}

	…

	private String first, last;
}

Note that one must supply first and last names when creating a Name object.

Name name = new Name("Gerald", "Weiss");

Now, consider a subclass FormalName that inherits from Name and adds a salutation:

class FormalName extends {

	…

	private String salutation;
}

FormalName is-a Name as well, and thus when one creates a FormalName you are also (implicitly) creating a Name object and thus need to supply a first/last name as well as the salutation, e.g.:

FormalName formalName = new FormalName("Mr.", "Gerald", "Weiss");

this gives us the constructor:

FormalName(String salutation, String first, String last) {
	this.salutation = salutation;
	…
}

The question becomes, how do we initialize the instance variables of the Name class? FormalName's constructor cannot/should-not do it:

The issue is resolved through yet another use of super:
FormalName(String salutation, String first, String last) {
	super(first, last);
	this.salutation = salutation;
}
in the context of constructors, super is used by a subclass to pass constructor arguments to the superclass

What if We Had Used Composition?

The situation is different if FormalName had been defined using composition instead of inheritance:
class FormalName {
	FormalName(String salutation, String first, String last) {
		name = new Name(first, last);
		this.salutation = salutation;
	}

	…

	private Name name;
	private String salutation;
}

Finally, … Something to Think About

What sort of hierarchy could we construct if we wanted: UpCounter, DownCounter, Counter, UppperBoundedUpCounter, UppperBoundedCounter, LowerBoundedDownCounter, LowerBoundedCounter, BoundedCounter?

Code Relevant to This Lecture