Home CPSC 330

Polymorphism

Overview

Polymorphism comes from the Greek "many forms". In programming, it refers to the ability of the same interface to be implemented in different ways.

In the example in the previous lecture, we saw two classes that overrode a method in different ways:


public class Records2 {
  public static void main(String args[]) {
    Student student = new Student("Bob Smith", 23488347, 3.2, 40);
    student.printInfo();

    Employee employee = new Employee("Alice Sanders", 485734, 56000, 4);
    employee.printInfo();
  }
}

Here the student and employee objects each have the same interface for printing their info, but do so in different ways.

We can take this a step further by referring to them not as Students or Employees, but as People:


public class Records3 {
  public static void main(String args[]) {
    Person p1 = new Student("Bob Smith", 23488347, 3.2, 40);
    Person p2 = new Employee("Alice Sanders", 485734, 56000, 4);
    
    p1.printInfo();
    p2.printInfo();
  }
}

This demonstrates an important facet of Java: A reference to a base class can refer to any derived object.

This demonstrates polymorphism as we have two objects that look the same (both Persons), yet work differently.


Abstract Classes

In the previous lab exercise, you wrote a class called Polygon that looked something like this:


class Polygon {
  public Polygon(int ns) {
    numSides = ns;
  }

  public double calculateArea() {
    return 0.0;
  }

  protected int numSides;
}

All polygons have an area, so it makes sense to have that method, but there is no way to reasonably implement it in the Polygon class.

In this case, we can mark the method as abstract. This specifies that there is no implementation of the method.

This example shows the polygon class as an abstract one:


abstract class Polygon {
  protected int sides;
  public Polygon(int s) {
    sides = s;
  }

  public abstract double calculateArea();
}

class Triangle extends Polygon {
  Triangle(double b, double h) {
    super(3);
    base = b;
    height = h;
  }

  @Override
  public double calculateArea() {
    return 0.5 * base * height;
  }

  private double base, height;
}

class Rectangle extends Polygon {
  Rectangle(double w, double l) {
    super(4);
    width = w;
    length = l;
  }
  
  @Override
  public double calculateArea() {
    return width * length;
  }

  private double width, length;
}


public class Shapes {
  public static void main(String args[]) {
    Polygon a = new  Triangle(3, 4);
    Polygon b = new  Rectangle(3, 4);

    System.out.printf("Area of a is %f.\n", a.calculateArea());
    System.out.printf("Area of b is %f.\n", b.calculateArea());
  }
}

Any class with at least one abstract method must be marked abstract. An abstract class can not be instantiated.

Any class that inherits from an abstract class must override all abstract methods, or also be marked abstract.


Heterogeneous Structures

One common use of polymorphism is to create data structures that contain multiple types of elements.

Normally an array or other data structure can only be of one type. However, since a reference to a base class can refer to any derived object, a structure of base class references can contain a mix of derived objects.

In this example, we create an ArrayList of different types of shapes:


public class Shapes2 {
  public static void main(String args[]) {
    // make an array list
    ArrayList<Polygon> shapes = new ArrayList<Polygon>();

    // fill it with some random shapes
    for(int i = 0; i < 100; i++) {
      // add either a triangle or rectangle
      if(Math.random() > 0.5) {
        shapes.add(new Rectangle(Math.random(), Math.random()));
      } else {
        shapes.add(new Triangle(Math.random(), Math.random()));
      }
    }

    // iterate through the list
    for(Polygon shape : shapes) {
      System.out.printf("Shape with %d sides and area of %f.\n",
          shape.numSides(), shape.calculateArea());
    }
  }
}

Interfaces

In Java, each class can only have one base class. However, there are times when this would be useful.

In order to get around this, Java has something called an interface. An interface is like a base class except:

An interface specifies a set of method declarations:


interface Animal {
  public void speak();
  public void eat();
}

If we want a class to inherit from this interface, we say that it implements it:


class Cat implements Animal {
  public void speak() {
    System.out.println("Meow.");
  }

  public void eat() {
    // eat cat food
  }
}

When implementing an interface, we must define all methods in the interface.

Like classes, interfaces must be defined in their own file if they are public.


Logging Example

Interfaces allow us to specify high-level functions without relying on a particular implementation.

For example, many programs have facilities for logging messages. These can be useful in debugging complex programs.

Instead of always writing log messages directly to the screen, or a file, we can use an interface:


// a level used for severity logged messages
enum Level {
  MESSAGE,
  WARNING,
  ERROR
}

// interface for all Loggers
interface Logger {
  // log a piece of text at a given severity level
  public void log(String text, Level level);
}

Code that needs to do logging can now take a parameter that refers to any class that implements the Logger interface.

The following class implements logging to the screen with colored output (using ANSI escape codes):


// a logger that prints to the screen in colors
class ColorLogger implements Logger {
  public void log(String text, Level level) {
    switch(level) {
      case MESSAGE:
        // print in regular color
        System.out.println(text);
        break;
      case WARNING:
        System.out.println("\u001B[33m" + text);
        break;
      case ERROR:
        System.out.println("\u001B[31m" + text);
        break;
    }
  
    // reset terminal
    System.out.print("\u001B[0m");
  }
}

We could also make Logger implementations that write the logged strings to a file, send them over the web, ignore them, stop the program on errors.

Here is the code for a program using the ColorLogger class.


The Comparable Interface

There are several interfaces that come with Java. Comparable is used to handle ordering and sorting of objects.

Consider the following program that attempts to sort a list of Person objects:


import java.util.*;


class Person {
  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }

  public String getName() {
    return name;
  }

  public int getAge() {
    return age;
  }

  private String name;
  private int age;
}


public class Comparison {
  public static void main(String args[]) {
    // add some random people
    ArrayList list = new ArrayList();
    list.add(new Person("Jay", 41));
    list.add(new Person("Elvia", 21));
    list.add(new Person("Chad", 75));
    list.add(new Person("Chad", 41));
    list.add(new Person("Ivette", 31));
    list.add(new Person("Maegan", 29));
    list.add(new Person("Imelda", 18));
    list.add(new Person("Avis", 54));
    list.add(new Person("Scott", 38));
    list.add(new Person("Elliot", 66));
    list.add(new Person("Illa", 23));
    list.add(new Person("Felipe", 31));
    list.add(new Person("Bettyann", 93));
    list.add(new Person("Raguel", 44));
    list.add(new Person("Staci", 61));
    list.add(new Person("Scott", 27));
    list.add(new Person("Lashon", 16));
    list.add(new Person("Rolando", 42));
    list.add(new Person("Desire", 71));
    list.add(new Person("Eleni ", 47));

    // sort them
    Collections.sort(list);

    // print them
    for(Person p : list) {
      System.out.printf("%s is %d years old.\n", p.getName(), p.getAge());
    }
  }
}

This code does not compile because there is no way to compare Person objects. We can specify this by having Person implement Comparable:


import java.util.*;


class Person implements Comparable {
  public Person(String name, int age) {
    this.name = name;
    this.age = age;
  }

  // this method satisfies the Comparable interface
  public int compareTo(Object other) {
    Person otherPerson = (Person) other;

    // compare names first
    int nameComparison = name.compareTo(otherPerson.name);
    if(nameComparison != 0) {
      return nameComparison;
    }

    // otherwise go by age
    if(age < otherPerson.age) {
      return -1;
    } else {
      return 1;
    }
  }

  public String getName() {
    return name;
  }

  public int getAge() {
    return age;
  }

  private String name;
  private int age;
}


public class Comparison2 {
  public static void main(String args[]) {
    // add some random people
    ArrayList list = new ArrayList();
    list.add(new Person("Jay", 41));
    list.add(new Person("Elvia", 21));
    list.add(new Person("Chad", 75));
    list.add(new Person("Chad", 41));
    list.add(new Person("Ivette", 31));
    list.add(new Person("Maegan", 29));
    list.add(new Person("Imelda", 18));
    list.add(new Person("Avis", 54));
    list.add(new Person("Scott", 38));
    list.add(new Person("Elliot", 66));
    list.add(new Person("Illa", 23));
    list.add(new Person("Felipe", 31));
    list.add(new Person("Bettyann", 93));
    list.add(new Person("Raguel", 44));
    list.add(new Person("Staci", 61));
    list.add(new Person("Scott", 27));
    list.add(new Person("Lashon", 16));
    list.add(new Person("Rolando", 42));
    list.add(new Person("Desire", 71));
    list.add(new Person("Eleni ", 47));

    // sort them
    Collections.sort(list);

    // print them
    for(Person p : list) {
      System.out.printf("%s is %d years old.\n", p.getName(), p.getAge());
    }
  }
}

Conclusion

Polymorphism is a powerful programming technique. It allows us to treat multiple different objects in a consistent manner.

Interfaces allow us to separate an interface from a particular implementation.

Copyright © 2018 Ian Finlayson | Licensed under a Creative Commons Attribution 4.0 International License.