Polymorphism

CSCI 1913 – Introduction to Algorithms, Data Structures, and Program Development
Adriana Picoral

Polymorphism

poly means many, morphs means forms

one interface with multiple implementations

Polymorphism in java:

  • Compile-time Polymorphism: Method overloading
  • Runtime Polymorphism: Method overriding

We will also see polymorphism through generics in the near future

Dynamic vs Static binding

  • Dynamic (or late) binding: when the specific code to call is determined at runtime (overriding)
  • Static (or early) binding: when the specific code to call is determined at compile-time (overloading)

In java: Only static or final methods (and constructors) have static binding:

  • static methods are not subject to inheritance/overriding
  • final methods cannot be overridden

Overriding Methods

A sub-class is free to redefine (most) methods

  • (exception final methods - but this is rare)
  • This is called overriding a method
  • Common to make overridden methods @Override
    • Annotation syntax – more formal than comment, but not code
    • Doesn’t affect behavior
    • This one is a “hint” that we expect this to be on parent class too

Overriding example

Animal.java

public class Animal {
    private String species;
    private String genus;
    private String family;
    
    public Animal (String s, String g, String f) {
        species = s;
        genus = g;
        family = f;
    }
    
    @Override
    public String toString() {
        String message;
        message = "The " + species;
        message += " of genus " +  genus;
        message += " and family " + family;
        return message;
    }

}

Overriding creates shadowing

Broadly Shadowing is when two things share a name.

  • Scoping will block one name - making it inaccessible
  • This can happen with variables in inheritance
  • super.<name> syntax for disambiguation
public class Cat extends Animal {
  
  ...
  
  @Override
  public String toString() {
    return super.toString() + " this cat is " + color;
  }
}

Method Overloading

Methods whose signatures differ are different methods. Using the same name for two methods whose signatures differ is known as overloading.

Implementation example

public class Animal {
    private String species;
    private String genus;
    private String family;
    
    public Animal(String s, String g, String f) {
        species = s;
        genus = g;
        family = f;
    }
    
    public Animal() {
      species = "Feline";
      genus = "Felis";
      family = "Felidae";
    }

}

Inheritance example

Cat.java

public class Cat extends Animal {
    public Cat() {
        super("Feline", "Felis", "Felidae");
    }
    
    public String makesSound() {
        return "meow";
    }

}

Implementation example

CreateAnimals.java

public class CreateAnimals {

    public static void main(String[] args) {
        Cat garfield = new Cat();
        System.out.println(garfield.toString());
    }

}

Overriding equals

Compare two objects

Let’s take a look at .equals(Object obj) from the Object class:

public boolean equals(Object obj) {
        return (this == obj);
    }

What would we need to make sure is the same for Animal?

We can also take a look at how String implements the equals method

Some syntax

Use the keyword instanceof to check if an object is an instance of a class.

cat instanceof Animal;

Use (type) variableName to type cast.

double n = 10;
int nInt = (int) n;

Practice

Let’s say we want to make the following classes:

  • Polygon
  • Rectangle
  • Square
  • Triangle

Note, these have a clear is-a relationship

Practice

  • Polygon
  • Rectangle is-a Polygon
  • Square is-a Rectangle
  • Triangle is-a Polygon

What attributes and methods would we need?

Practice

Download tester files.

Overload: two Polygon constructors, one with array of side measures (double[]), another with no arguments

Override: toString() and equals(Object o)

Submit Polygon.java, Rectangle.java, Square.java and Triangle.java to gradescope

Solution

Polygon.java

public class Polygon {
    protected String name = "Polygon";
    protected int sides;
    private double[] measures = new double[100];

    public Polygon(double[] measures) {
        sides = measures.length;
        for (int i = 0; i < measures.length; i++) {
            this.measures[i] = measures[i];
        }
    }

    public Polygon() {}

    protected void setMeasures(double[] measures) {
        this.measures = measures;
    }

    public double[] getMeasures() {
        return measures;
    }

    public int getSides() {
        return sides;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        String message = name + " has " + sides + " sides";
        if (sides > 0) {
            message += "\nMeasures are:";
        }
        for (int i = 0; i < sides; i++) {
            message += " " + measures[i];
        }
        return message;
    }
    
    
    @Override
    public boolean equals(Object o) {
        // check if same object
        if (this == o) {
            return true;
        }
      
        // check if same type of object
        if (!(o instanceof Polygon)) {
            return false;
        }
      
        // check sides, must be same number of sides
        if (((Polygon) o).getSides() != sides){
            return false;
        }
      
        // check individual measures
        for (int i = 0; i < sides; i++) {
            if (((Polygon) o).getMeasures()[i] != measures[i]) {
                return false;
            }
        }

        // check name
        if (!((Polygon) o).getName().equals(name)) {
            return false;
        }

        return true;
    }

}

another equals(Object o) solution

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }

        if (o instanceof Polygon aPolygon) {
            if (aPolygon.getSides() == sides && aPolygon.getName().equals(name)) {
                for (int i = 0; i < sides; i++) {
                    if (aPolygon.getMeasures()[i] != measures[i]) {
                        return false;
                    }
                }
                return true;
            }
        }

        return false;
    }

Solution

Rectangle.java

public class Rectangle extends Polygon {

    public Rectangle(double[] measures) {
        super(measures);
        super.sides = 4;
        super.name = "Rectangle";
    }

    public Rectangle() {
        super.sides = 4;
        super.name = "Rectangle";
    }

}

Solution

Square.java

public class Square extends Rectangle {

    public Square(double measure) {
        double[] measures = new double[4];
        for (int i = 0; i < 4; i++ ) {
            measures[i] = measure;
        }
        super.setMeasures(measures);
        super.name = "Square";
    }

}

Solution

Triangle.java

public class Triangle extends Polygon {

    public Triangle() {
        super.name = "Triangle";
        super.sides = 3;
    }

    public Triangle(double[] measures) {
        super(measures);
        super.name = "Triangle";
        super.sides = 3;
    }
}