Inheritance1 Study Guide
Author: Wayne Li

Lecture Code

Code from this lecture available at

https://github.com/Berkeley-CS61B/lectureCode-sp19/tree/master/inheritance1.

Overview

Method Overloading In Java, methods in a class can have the same name, but different parameters. For example, a Math class can have an add(int a, int b) method and an add(float a, float b) method as well. The Java compiler is smart enough to choose the correct method depending on the parameters that you pass in. Methods with the same name but different parameters are said to be overloaded.

Making Code General Consider a largestNumber method that only takes an AList as a parameter. The drawback is that the logic for largestNumber is the same regardless of if we take an AList or SLList. We just operate on a different type of list. If we use our previous idea of method overriding, we result in a very long Java file with many similar methods. This code is hard to maintain; if we fix a bug in one method, we have to duplicate this fix manually to all the other methods.

The solution to the above problem is to define a new reference type that represents both AList and SLList. We will call it a List. Next, we specify an “is-a” relationship: An AList is a List. We do the same for SLList. Let’s formalize this into code.

Interfaces We will use the keyword interface instead of class to create our List. More explicitly, we write:

public interface List<Item> { ... }

The key idea is that interfaces specify what this List can do, not how to do it. Since all lists have a get method, we add the following method signature to the interface class:

public Item get(int i);

Notice we did not define this method. We simply stated that this method should exist as long as we are working with a List interface.

Now, we want to specify that an AList is a List. We will change our class declaration of AList to:

public AList<Item> implements List<Item> { ... }

We can do the same for SLList. Now, going back to our largestNumber method, instead of creating one method for each type of list, we can simply create one method that takes in a List. As long as our actual object implements the List interface, then this method will work properly!

Overriding For each method in AList that we also defined in List, we will add an @Override right above the method signature. As an example:

@Override
public Item get(int i) { ... }

This is not technically necessary, but is good style and thus we will require it. Also, it allows us to check against typos. If we mistype our method name, the compiler will prevent our compilation if we have the @Override tag.

Interface Inheritance Formally, we say that subclasses inherit from the superclass. Interfaces contain all the method signatures, and each subclass must implement every single signature; think of it as a contract. In addition, relationships can span multiple generations. For example, C can inherit from B, which can inherit from A.

Default Methods Interfaces can have default methods. We define this via:

default public void method() { ... }

We can actually implement these methods inside the interface. Note that there are no instance variables to use, but we can freely use the methods that are defined in the interface, without worrying about the implementation. Default methods should work for any type of object that implements the interface! The subclasses do not have to re-implement the default method anywhere; they can simply call it for free. However, we can still override default methods, and re-define the method in our subclass.

Static vs. Dynamic Type Every variable in Java has a static type. This is the type specified when the variable is declared, and is checked at compile time. Every variable also has a dynamic type; this type is specified when the variable is instantiated, and is checked at runtime. As an example:

Thing a;
a = new Fox();

Here, Thing is the static type, and Fox is the dynamic type. This is fine because all foxes are things. We can also do:

Animal b = (Animal) a;

This is fine, because all foxes are animals too. We can do:

Fox c = (Fox) b;

This is fine, because b points to a Fox. Finally, we can do:

a = new Squid()

This is fine, because the static type of a is a Thing, and Squid is a thing.

Dynamic Method Selection The rule is, if we have a static type X, and a dynamic type Y, then if Y overrides the method from X, then on runtime, we use the method in Y instead. Student often confuse overloading and overriding.

Overloading and Dynamic Method Selection Dynamic method selection plays no role when it comes to overloaded methods. Consider the following piece of code, where Fox extends Animal.

1  Fox f = new Fox();
2  Animal a = f;
3  define(f);
4  define(a);

Let’s assume we have the following overloaded methods in the same class:

public static void define(Fox f) { ... }
public static void define(Animal a) { ... }

Line 3 will execute define(Fox f), while line 4 will execute define(Animal a). Dynamic method selection only applies when we have overridden methods. There is no overriding here, and therefore dynamic method selection does not apply.

Exercises

A Level

  1. Problem 4 from my Spring 2017 midterm 1.