Polymorphism

Learning Targets:

  • What is Polymorphism
  • Difference between Static and Dynamic Data Types
  • Compile Time vs Run time

  • “A reference variable polymorphic when it can refer to objects of different classes in the code”
  • “A method is polymorphic when it is overriden in at least one subclass”
  • Polymorphism is the act of executing an overriden non-static method from the correct class at runtime based on the actual object type”
class Water {
    public String typeOfWater(){
        return "water";
    }
}

class Lake extends Water {
    @Override
    public String typeOfWater(){
        return "lake";
    }
}

Water lake = new Lake(); //creates a "Water" class using a "Lake" constructor
System.out.println(lake.typeOfWater()); // Therefore the typeOfWater is overriden

How Does This Work?

Let’s start with this line: SuperClass myObject = new SubClass();

It may seem like you are magically creating a SuperClass Object with the SubClass, but you are not. Instead you actually are creating a SubClass Object. The difference is that the SuperClass variable is referencing the SuperClass parts of the SubClass.

  • “A reference variable can store a refernece to its declared class or any subclass of its declared class”

This also means that if the data type is the SuperClass and you try to call a method that doesn’t exist in the SuperClass, it will return an error. But you can cast the variable to the SubClass because the refrence is the SubClass. After casting if you call a method that is only in the subclass then it won’t return an error.

Next running methods: SuperClass.method() == SubClass.method(); //where the method is overriden by a subclass

When you run a method that the SuperClass has, it starts at the referenced object and checks if there is an override, if not it moves up the ancestry chain until it either finds an override or finds the orginal method.

Popcorn Hacks

  • Create an example of Polymorphism in your own project.

If you want some more information and examples of Polymorphism see the examples further down

Static vs Dynamic Types

static types: static types is simply the type.

dynamic types: dynamic type is the type of the value during runtime

class Shape {
    String getName(){
        return "Shape"
    }
}

class Square extends Shape{
    @Override
    String getName(){
        return "Square"
    }
}

Shape myShape = new Square();

Shape is a static type, but at runtime myShape is referencing Sqaure, so my myShape is the dynamic type of Square.

Popcorn Hacks

Using your previous polymorphism example, explain which parts are the static types and which are the dynamic types

Read this for more information

Compile-Time vs Run-Time methods

When you are creating a dynamic reference of a different with a type than the static type, it searches the superclass for the attributes of the static type, then overrides any that the child has overriden. But it doesn’t include the methods from the child. If you want to run a method that is only in the child class then you can use down-casting.

class Shape {
    String getName(){
        return "Shape"
    }
}

class Square extends Shape{
    @Override
    String getName(){
        return "Square"
    }

    int getSides(){
        return 4;
    }
}

Shape myShape = new Square(); //this does not have access to the Sqaure methods despite referencing a sqaure

Sqaure mySqaure = (Sqaure)myShape; //down-cast the Shape to a Sqaure to run the Sqaure specific methods
System.out.println(mySqaure.getSides());//after down-casting you can now run the Square methods

Popcorn Hacks

  • Define down-casting
  • add an example of down-casting to your previous polymorphism example


More examples of Polymorphism and the effects

Here are some examples of Polymorphism.

  1. Executing the overriden method from the referenced subclass in the datatype of the superclass.
class Water {
    public String typeOfWater(){
        return "water";
    }
}

class Lake extends Water {
    @Override
    public String typeOfWater(){
        return "lake";
    }
}

Water lake = new Lake(); //creates a "Water" class using a "Lake" constructor
System.out.println(lake.typeOfWater()); // Therefore the typeOfWater is overriden
  1. You can also cast from the subclass to the superclass. This means you can pass a subclass into an argument that is asking for the parent class.
class Shape{
    public int getSize(){
        return 1;
    }
}

class Square extends Shape{
    @Override
    public int getSize(){
        return 2;
    }
}

int getSizePlusOne(Shape s){ //argument of class "Shape"
    return s.getSize() +1;
}

Shape myShape = new Shape();
System.out.println(getSizePlusOne(myShape)); //passes through a "Shape"

Square mySquare = new Square();
System.out.println(getSizePlusOne(mySquare)); //passes through a "Square" as a "Shape" with the square's "getSize()" method
  1. And finally it allows you to cast from the superclass to the subclass (down-casting). The superclass must be referencing the subclass.
class Electronic{
    public void playSound(){
        System.out.println("Beep boop");
    }
}

class Computer extends Electronic{
    @Override
    public void playSound(){
        System.out.println("Click clack");
    }
}

Electronic device = new Computer(); //creates a "Electronic" class using a "Computer" constructor

Computer  computer = (Computer)device; //casts the "Electronic" to a "Computer"

computer.playSound();
class Electronic{
    public void playSound(){
        System.out.println("Beep boop");
    }
}

class Computer extends Electronic{
    @Override
    public void playSound(){
        System.out.println("Click clack");
    }
}

Electronic device = new Electronic(); //creates a "Electronic" class using a "Electronic" constructor

Computer  computer = (Computer)device; //cannot cast the "Electronic" to a "Computer"

Polymorphism with the Object class

Polymorphism also applies with the Object Superclass. Techically any class or object is an Object.

class Fruit{
    @Override
    public String toString(){
        return "I'm a Fruit!";
    }
}
class Banana extends Fruit{
    @Override
    public boolean equals(Object obj){ //overrides the equals
        return true;
    }
}


Object banana = new Banana();
System.out.println(banana.toString());
System.out.println(banana.equals(null)); //if ".equals()" was not overriden from the "Object" this should always return false
class Furniture{
    String getName(){
        return "Furniture";
    }
}

class Table extends Furniture{
    @Override
    String getName(){
        return "Table";
    }
}

class CoffeeTable extends Table{
    @Override
    String getName(){
        return super.getName() + " Coffee";
    }
}

Furniture myTable = new CoffeeTable();
System.out.println(myTable.getName()); //runs "CoffeeTable" method

Table myOtherTable = (Table)myTable;
System.out.println(myOtherTable.getName()); //method exectuted doesn't change despite casting-down