11. Inheritance Calls I

Approaching AP MCQ-type problems by thinking about functions.

A typical multiple choice question designed to assess understanding of inheritance assigns an instance of a subclass to a variable whose type is a superclass and then asks what methods would be called.

Setup

The classes to be used in this discussion are Mammal and Human. These are not supposed to be complete or make perfect sense. Please forgive the weird temperature function.

public class Mammal {
    public double temperature() { return 123.4; }
}
public class Human extends Mammal {
    public double temperature() { return 98.6; }
    public void speak(String s) { /* not shown */ }
}

Example A

A Human is assigned to a Mammal variable. Can you call its speak method?

{
    Mammal m = new Human();
    m.speak("is this ok?"); // Spoiler: nope, error
}

Discussion 1

The phrasing of this question makes it very confusing for many people. I used to teach this slogan (which is correct and may help some people):

  • The true type of a variable determines which method gets called. In this example, the true type of m is Human.
  • The declared type of a variable determines what methods you are allowed to call. In this example, the declared type if m is Mammal.

The thing that bothers me about this approach is that there is no logic to it; it is just a rule to follow.

Discussion 2

Another way of phrasing the same question is to create a separate function for the setup and the “action”. I think this makes the rules of which functions are allowed to be called clearer.

public static void action(Mammal m) {
    m.speak("is this ok?"); // (1) Spoiler: nope, error
}
public static void setup() {
    Human h = new Human();
    action(h); // (2)
}

action

First, an analysis of the action method. In this method, all you know about m is that it is a Mammal. Since action is a method, it can be called from more than one place, so the reader has no reason to believe that m is always a Human. In fact, you should imagine other places in the code with calls like:

action(new Mammal());

Looking at the problem this way, it should be clear that you cannot call the speak method of the m object.

setup

The other place something can go wrong is at the site where action is called, labeled (2) in the code. If the h object is not an instance of Mammal, then this would be an error.

Discussion 3

The original variable is unaltered by casting.

{
    Mammal m = new Human();
    m.speak(); // (1) nope

    Human h = (Human) m;
    h.speak(); // (2) ok

    m.speak(); // (3) still nope
}

Worked Example A

The code Mammal m = new Snake() corresponds to the setup code below. This is an error at the call to action because the Snake variable is not a Mammal.

public static void setup2() {
    Snake x = new Snake();
    action(x);
}

Exercise Example B

Imagine turning code into a program using classes like Variable with subclasses IntegerVariable and StringVariable. All Variable objects have a name method. IntegerVariable objects have an add method to add to the number. StringVariable objects have a prepend method that puts text in front of what is already there.

Is the following code legal? Explain.

{
    Variable v = new IntegerVariable("a",3); // code for: int a = 3;
    Variable w = new StringVariable("s","Turkey"); // code for: String s = "Turkey";
    v.add(5); // code for: a += 5;
    w.prepend("Republic of "); // code for: s = "Republic of " + s;
}

Exercise Example C

Break the code below into a setup and action step, then analyze it. You have to know how chess pieces move to understand this example. (See links in the following paragraph if you want.)

{
    ChessPiece q = new Queen();
    q.slide_horizontally(5); // move 5 squares to the right
}

Chess explanation: a queen can move an unlimited amount of squares in a line. The knight is one chess piece does not move in a straight line. from this you deduce that although a queen may have a slide_horizontally method, a generic ChessPiece should not.

Exercise Example D

Suppose there are classes Mammal, Human, and Dog. All mammals have an eat() method, humans have a talk() method, and dogs have a bark() method. Explain which parts of the following code work, and why explain the problems with the parts that do not work.

public static void setup() {
    Human h = new Human();
    Dog d = new Dog();
    Mammal m = new Dog();
    Mammal q = h;
    action(h, d, q); // (1)
    action(q, h, h); // (2)
}
public static void action(Mammal a, Mammal b, Human c) {
    a.talk(); // (3)
    a.eat();  // (4)
    b.bark(); // (5)
    c.talk(); // (6)
}

Exercise Example E

An extended example with many points to wonder about. This was used in a 20 minute in-class discussion.

Setup E

public class M {
  public void f() { f("Mongoose"); }
  public void f(String x) { System.out.println(x); }
}
public class P extends M {
  public void f(String x) { System.out.println("Pretty "+x); }
  public void f(int n) { f("Pink #"+n); }
  public void g() { System.out.println("Go for it"); }
}

Questions E, written AP style

public static void questionD() {
    M y = new P();
    y.g(); // (1)
    /* 2 */
    {
        P py = (P) y; // (2)
    }
    /* 3 */
    if (y instanceof P) {
        y.g();
    }
    /* 4 */
    if (y instanceof P) {
        P by = (P) y;
        // (4a)
        py.g();
        // (4b)
        ((P) y).g();
        // (4c - only for very advanced)
        (P) y.g();
    }
    /* 5: What happens? */
    y.f();
}

Questions E, written function style

This is the same code as above, but split into a setup function and an action function. The variable names have also been changed to show the declared type of the object, which is a strategy to keep track of the information.

public static void setup() {
    P p = new P();
    action(p);
}
public static void action(M m)
{
    m.g(); // (1)
    /* 2 */
    {
        P p = (P) m; // (2)
    }
    /* 3 */
    if (m instanceof P) {
        m.g();
    }
    /* 4 */
    if (m instanceof P) {
        P p = (P) m;
        // (4a)
        p.g();
        // (4b)
        ((P) m).g();
        // (4c)
        (P) m.g();
    }
    /* 5: What happens? */
    m.f();
}