1 Abstracting over Common Methods
2 Inheritence
3 Abstracting over Common Fields
4 Abstract Classes
5 Abstracting over Shared Interfaces
6 Abstract Classes versus Interfaces
7 Class Extension without Abstraction
8 Summary

Abstracting over Classes

1 Abstracting over Common Methods

Recall our isNormalSize methods on animals:

  // in the Boa class

  public boolean isNormalSize () {

    return 5 <= this.length && this.length <= 10;

  }

  

  // in the Dillo class

  public boolean isNormalSize () {

    return 2 <= this.length && this.length <= 3;

  }

The method bodies on Boa and Dillo differ only in the numbers for the low and high bounds. We know that we should create helper functions to share common code in cases such as this. How do we do this in Java?

To make sure our goal is clear, consider what the helper function and revised code would have looked like in Racket:

  (define (len-within? len low high)

    (and (<= low len) (<= len high)))

  

  ;; normal-size? : animal -> boolean

  ;; determine whether animal is within an expected size range

  (define (normal-size? an-ani)

    (cond [(boa? an-ani) (len-within? (boa-length an-ani) 5 10)]

          [(dillo? an-ani) (len-within? (dillo-length an-ani) 2 3)]))

If we did something similar in Java, we should be able to write (once we define isLenWithin)

  // in the Boa class

  public boolean isNormalSize () {

    return isLenWithin(this.length, 5, 10);

  }

  

  // in the Dillo class

  public boolean isNormalSize () {

    return isLenWithin(this.length, 2, 3);

  }

The question is where to put isLenWithin. Remember that in Java, every method must be in a class. In Java, classes can’t directly see the contents of other classes. They can create objects and access them through methods, but that is different from using code that is defined in another class.

2 Inheritence

Fortunately, Java (like most class-based languages) follows a model of class hierarchies in which one class can build upon (or extend) the definitions in another. We can define a class for the shared information between Boa and Dillo and make isLenWithin a method in that class. Since our new class exists to abstract over animals, we name the class AbsAnimal.

  class AbsAnimal {

    // the constructor

    AbsAnimal () {}

  

    boolean isLenWithin(int len, int low, int high) {

      return low <= len && len <= high;

    }

  }

This is a start, but it isn’t quite finished (read on). We haven’t yet linked Boa and Dillo to AbsAnimal. In OO terminology, we want Boa and Dillo to inherit the contents of AbsAnimal. We indicate inheritence using a new keyword called extends in the class definition:

  class Dillo extends AbsAnimal implements IAnimal {

    ...

  }

  

  class Boa extends AbsAnimal implements IAnimal {

    ...

  }

  

In OO terminology, AbsAnimal is the superclass of each of Boa and Dillo. Each of Boa and Dillo is a subclass of AbsAnimal.

3 Abstracting over Common Fields

AbsAnimal is a good place to store any information common to Boa and Dillo. As both of those classes have a length field, we should move this field up to AbsAnimal. This mandates editing the constructor as well:

  class AbsAnimal {

    int length;

  

    // the constructor

    AbsAnimal (int length) {

      this.length = length;

    }

  

    ...

  }

Notice that now the length field is initialized in the AbsAnimal constructor. We shouldn’t need to also initialize it in the Boa and Dillo constructors. We edit the Boa and Dillo constructors to simply pass the length field along to initialize the super class:

  class Dillo extends AbsAnimal {

    boolean isDead;

  

    Dillo (int length, boolean isDead) {

      super(length);

      this.isDead = isDead;

    }

    ...

  }

  

  class Boa extends AbsAnimal {

    String name;

    String eats;

  

    Boa (String name, int length, String eats) {

      super(length);

      this.name = name;

      this.eats = eats;

    }

    ...

  }

In Java, super refers to the superclass of a class. The line super(length) says "create an instance of my superclass and initialize it with the length value". In Java, each class may extend at most one other class, so the meaning of super is unambiguous.

4 Abstract Classes

While this code is valid, it is too permissive in that someone could write new AbsAnimal(6). This would mean "some animal of length 6". If our goal were only to create instances of specific animals (which seems reasonable), we want to prevent someone from creating AbsAnimal objects instead of Boa and Dillo objects.

An abstract class is a class that can be extended but not instantiated as a standalone object. We specify this through the keyword abstract before a class name:

  abstract class AbsAnimal {

    int length;

  

    ...

  }

Now, new AbsAnimal(6) yields an error.

5 Abstracting over Shared Interfaces

We are almost done abstracting over Boa and Dillo. We note one final commonality: both Boa and Dillo implement IAnimal. We can move that requirement up to AbsAnimal as well. Since AbsAnimal is abstract, Java won’t complain that it doesn’t implement isNormalSize (as required in IAnimal). If AbsAnimal were not abstract, Java would require AbsAnimal to provide an isNormalSize method.

The revised class diagram appears as follows: Class
diagram with AbsAnimal

The diagram helps illustrate the concepts of subclass (Boa and Dillo) and superclass (AbsAnimal).

6 Abstract Classes versus Interfaces

We now have two mechanisms, abstract classes and interfaces, through which classes can share information. What is each one best used for?

Those with prior Java experience may have learned to use abstract classes for both situations, but this is not good OO programming. Interfaces are fairly permissive: a class must provide methods that implement those outlined in the interface, but how that implementation works (including what data structures get used) is entirely up to the author of the class. Abstract classes provide actual code, which means that any class which uses an abstract class must do so in a way that is consistent with the existing code or data structures. The restriction that a class may only ever extend one other class reflects this consistency problem. In contrast, a class can implement any number of interfaces, because interfaces do not constrain behavior.

Interfaces also recognize that the world is not neatly hierarchical. Different kinds of real world objects have all sorts of different properties that affect how we use them: can an object be ordered against other objects of the same class (ie, numbers), can the object be drawn in two dimensions, does the object correspond to physical form, etc. If we wanted to write programs that worked on, say, only objects with two-dimensional renderings, we need some way to say "this class has that property". Interfaces do this nicely. With only one super-class allowed, all the combinations of possible properties on objects would complicate class hierarchies quickly.

Put differently, there is an important distinction between stating which operations are required and stating how those operations are implemented (we’ll see a lot of this the rest of the week). The former is called specification; the latter implementation. Interfaces are for specification; abstract classes for implementation.

7 Class Extension without Abstraction

Abstract classes support field and method abstraction, but class extensions are also used to express hierarchy among data. For example, let’s add two kinds of animals to our class hierarchy: Fish, which have a length and an optimal saline level for water in their tanks; and Sharks, which are fish for which we record the number of times they attacked people. The new classes appear as follows:

  class Fish extends AbsAnimal {

    double salinity;

  

    Fish (int length, double salinity) {

      super(length);

      this.salinity = salinity;

    }

  

    public boolean isNormalSize () {

      return isLenWithin(this.length, 3, 15);

    }

  }

  

  class Shark extends Fish {

    int attacks;

  

    Shark (int length, int attacks) {

      super(length, 3.75);

      this.attacks = attacks;

    }

  }

A few things to note here:

8 Summary

This example introduced the following concepts:

While we did not do an example here, you can also inherit between non-abstract classes. For example, you could have a class of graphical icons (with a field for the icon’s picture), then a sub-class of icons placed on a drawing canvas, that would extend the first class with x- and y-coordinates. We will see more examples of this as the course progresses.