|
2/12 Recitation 2
Today we looked at inheritance as well as a number of style issues.
- Java Keywords: super and this
- Method Overloading
- StringZilla Collections Example
Here are two classes, Cycle and Unicycle
that demonstrate subclassing:
Cycle.java |
Unicycle.java |
public class Cycle {
protected String name;
protected int numWheels;
/** creates a cycle with 2 wheels */
public Cycle(String name) {
this(name, 2);
}
public Cycle(String name,
int numberOfWheels) {
this.name = name;
numWheels = numberOfWheels;
}
}
|
public class Unicycle extends Cycle {
public Unicycle(String name) {
super(name, 1);
}
public String toString() {
String s = "Unicycle: ";
s += super.toString();
return s;
}
}
|
Because Unicycle extends Cycle,
Cycle is a superclass of Unicycle.
Therefore, it is also the case that Unicycle
is a subclass of Cycle.
Also, Unicycle redefines, or overrides
the toString method that takes no parameters and
returns a String.
The classes above demonstrate the two uses for this
and super:
- this as the first line of a constructor calls another
constructor of the same class with a different set of parameters.
This is often done to to initialize a class with a default
parameter. In this example, the first constructor is the same
as the second, using 2 as the default value for
numberOfWheels.
- this as a reference to the instance of the object
can use this to indicate which variable should be used,
in this case,
it is used to distinguish between the field name
and the local variable name. Note that this
can never be null since it will always refer to the instance
in which it is being used.
- super as the first line of a constructor
calls the constructor of the superclass.
Every subclass's constructor must first call some constructor
of its direct superclass before the rest of the class can
be initialized. Only when the direct superclass has
an empty constructor may super be omitted, in which case
Java implicitly calls super().
- super as a reference to the super class the only access
a subclass has to its direct superclass's methods is through
a reference to super. In the example above, Unicycle
uses the parent's toString method as part of its own
toString method.
Every object knows its type and what methods it has.
Thus, when you invoke a method of an object, it can only
use its own method, even if you refer to it as
one of its supertypes.
This may lead to some surprising behavior as shown
in the example below:
This is example is adapted from Problem 5 on
6.170 Quiz 2 from Spring 2002.
Jedi.java |
TimTheBeaver.java |
public class Jedi {
public void a() {
this.b();
}
public void b() {
System.out.println(
"May the force be with you.");
}
}
|
public class TimTheBeaver extends Jedi {
public void b() {
super.a();
}
public static void main(String[] argv) {
Jedi apprentice = new TimTheBeaver();
apprentice.b();
}
}
|
So what happens when the main method of TimTheBeaver
is called?
- A new object of type TimTheBeaver is created.
- The method b() of TimTheBeaver is invoked.
- TimTheBeaver calls its superclass's (Jedi's)
method a()
- a() looks for its method b() to invoke.
Since this is a reference to a TimTheBeaver
object, it invokes the method b() defined in
TimTheBeaver.
- Invoking b() jumps to step 2.,
so this creates an loop which only gets broken
when Java runs out of memory.
The way to avoid this problem is (1) to have a clear specification
for each method, and (2) to make sure that every subtype that
overrides the method still meets its specification.
Thus, you have to be careful when designing and using subclasses.
For more details, see Item 15 in Effective Java:
"Design and document for inheritance or else prohibit it."
Suppose you were assigned to make a class called StringZilla
that allowed the client to add a bunch of strings to StringZilla
and then get them out as one, long, concatenated string.
The client should also be able to remove strings from StringZilla
later, but you are not responsible for including that functionality
(though your implementation should easily be
able to be extended to support it).
Here is a reasonable first pass at StringZilla:
import java.util.Iterator;
import java.util.LinkedList;
public class StringZilla {
private LinkedList strings = new LinkedList();
public void addString(String s) {
if (s != null)
strings.add(s);
}
public String getMonsterString() {
String s = "";
Iterator iter = strings.iterator();
while (iter.hasNext()) {
s += (String)iter.next();
}
return s;
}
}
This meets the specification of the problem; however,
this implementation is extremely wasteful!
Instead, consider the following:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class StringZilla {
private List/*<String>*/ strings = new ArrayList();
public void addString(String s) {
if (s != null) {
strings.add(s);
}
}
public String getMonsterString() {
StringBuffer buffer = new StringBuffer();
for (Iterator iter = strings.iterator(); iter.hasNext(); ) {
String s = (String)iter.next();
buffer.append(s);
}
return buffer.toString();
}
}
Improvements:
- An ArrayList is used in place of a LinkedList,
as it has better performance.
- The variable
strings is referenced through its
interface as opposed to through its concrete class name.
For an explanation of why this is preferred, see Item 34 in
Effective Java: "Refer to objects by their interfaces."
- The variable
strings is commented in such a way
that it clearly indicates what type is being stored in the List
and it makes your code ready for Java 1.5!
For a further explanation of this, see the comments section of
Supplementary Handout 1: Java Style Guide.
- Braces are added around the if condition
in addString() for clarity.
Although the first example will compile, such style should be
avoided because it is error-prone.
For example, another developer may add more lines to this code,
expecting them to be executed only when the if-statement is true.
This error can be avoided by using braces.
- You should always use a StringBuffer to concatenate a
large string because string concatenation using +
requires O(n2) time
whereas string concatenation using StringBuffer
requires O(n) time.
See Item 33 in Effective Java:
"Beware the performance of string concatenation."
- The use of iter is localized appropriately
in the second example. In the first example, iter
is still in scope when the while loop finishes.
This is unnecessary and can lead to errors.
For a more detailed explanation of why this is bad style,
see Item 29 in Effective Java:
"Minimize the scope of local variables."
Note that the use of Iterator in the second example
exemplifies the most common usage of an Iterator:
// variable declaration
Collection/*<Type>*/ someCollection;
// use of Iterator over Collection
for (Iterator iter = someCollection.iterator(); iter.hasNext(); ) {
// do cast right away so all methods of Type
// are available throughout the for block
Type t = (Type)iter.next();
// perform operations on t
}
|