Michael Bolin's 6.170 Recitation

This web site is for members of the recitation that I am teaching for 6.170 Spring 2004. The recitation meets Thursday mornings from 10-11am in 26-210. Here you will find recitation notes as well as links to software development tools that I consider important.
Recitation Notes
2/5 Recitation 1
2/12 Recitation 2
2/19 Recitation 3
2/26 Recitation 4
3/4 Recitation 5
Comments on Problem Sets
Problem Set 0
Problem Set 1
Problem Set 3
Things I Recommend
Software You Should Install
Books You Should Own
Web Sites You Should Use

2/12 Recitation 2

Today we looked at inheritance as well as a number of style issues.
  1. Java Keywords: super and this
  2. Method Overloading
  3. StringZilla Collections Example

1. Java Keywords: super and this

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.

2. Method Overloading

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?

  1. A new object of type TimTheBeaver is created.
  2. The method b() of TimTheBeaver is invoked.
  3. TimTheBeaver calls its superclass's (Jedi's) method a()
  4. 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.
  5. 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."

3. StringZilla Collections Example

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:
  1. An ArrayList is used in place of a LinkedList, as it has better performance.
  2. 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."
  3. 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.
  4. 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.
  5. 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."
  6. 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

}


©2004 Michael Bolin