The homework from the previous class is really important, as it examines the mechanisms of inheritance, constructors for derived classes, and overloading vs. overriding for derived classes.
As a starting point, let's suppose we have the class Queue (from a previous homework), along with a simple testing program Ex1 (note: to finish entering input for Ex1, hit ctrl-d). We don't give you the Queue code since that is on a HW and possible Project solution, but you don't actually need it to implement inheritance!
public class Queue {
public void enqueue(String s) {
// valid code for adding a string
}
public String dequeue() {
// valid code for removing a string
}
public boolean empty() {
// valid code for testing if the queue is empty
}
private class Node {
public String data;
public Node next;
public Node(String d, Node n) { data = d; next = n; }
}
private Node head = null, tail = null;
}
import java.util.*;
public class Ex1 {
public static void main(String[] args) {
Queue Q = new Queue();
Scanner sc = new Scanner(System.in);
while( sc.hasNext() ) {
Q.enqueue(sc.next());
}
while( !Q.empty() ) {
System.out.println(Q.dequeue());
}
}
}
public class CountingQueue extends Queue {
private int count;
public void enqueue(String s) {
count++;
super.enqueue(s);
}
public String dequeue() {
count--;
return super.dequeue();
}
public int size() { return count; }
}
import java.util.*;
public class Ex2 {
public static void main(String[] args) {
if (args.length == 0) {
System.out.println("usage: java Ex2 ");
System.exit(0);
}
int N = Integer.parseInt(args[0]);
boolean flag = true;
CountingQueue Q = new CountingQueue();
Q.enqueue("");
while(!Q.empty()) {
String s = Q.dequeue();
System.out.println(s);
flag = flag && Q.size() < N;
for(int i = 0; flag && i < 26; i++) {
Q.enqueue(s + (char)('a' + i));
}
}
}
}
A question that got asked, in one form or another, several times, was whether or not we could make a call like "super.super.foo()", in order to get the function not from the super class, but from the super class's super class. The answer is "No!", but we're in a good position to understand why that would be undesirable. Suppose I was able to do this, and I produced the following code:
public class MyQueue extends CountingQueue {
public void enqueue(String s) {
super.super.enqueue("my:"+s); ← Not actually legal!!
}
...
}
By doing this, the "CountingQueue" functionality that I was supposed to have
inherited has been messed up! Why? Because something has been enqueued but not "counted".
Moreover, it's only possible to know whether or not this has messed something up, and in
what way it's been messed up, if you are intimately familiar with the implementation of CountingQueue, and that's the very thing we don't want in object oriented programming!
If you don't think this is bad, consider the following: what if the person in charge of CountingQueue
decides to override empty()
so that it checks whether count is zero to provide
it's true/false answer. From the CountingQueue guy's perspective, this should be thoroughly
safe and not break other people's code. But now MyQueue will break in an unanticipated way:
done() for MyQueue's will give incorrect results.
That's why super.super, or something like it, is not allowed!
protected
accessA really useful function to have with Queues is "peek", which tells you what's at the front of the Queue without actually removing it. Peek allows you to "look before you leap", so to speak, because once you dequeue(), you can't put what you've just removed back in front again. At first blush, it seems that peek() should be easy: all we have to do is extend Queue and add one little method.
public class PeekaQueue extends Queue {
public String peek() {
? ? ? ;
}
}
But how do we define that method? The super class Queue does not provide public methods that
allow us to implement this functionality, and since the fields of Queue are private, we can't
implement it for ourselves. From the perspective of the implementer of class Queue, you would
say that it's impossible to anticipate everything someone would want to do with a Queue and
to provide public methods that make all possible operations implementable. That's true. A
compromise between strict separation of interface from implementation and a wild west all-fields-are-public
approach is to use the protected
access modifier. When a field is marked "protected",
it means that it is accessible inside the class and any derived classes, but not from anywhere
else. If we change head and tail in Queue to be protected
, implementing PeekaQueue
becomes easy.
public class Queue {
public void enqueue(String s) {
// valid code for adding a string
}
public String dequeue() {
// valid code for removing a string
}
public boolean empty() {
// valid code for testing if the queue is empty
}
private class Node {
public String data;
public Node next;
public Node(String d, Node n) { data = d; next = n; }
}
protected Node head = null, tail = null;
}
public class PeekaQueue extends Queue {
public String peek() {
return head == null ? null : head.data;
}
}