TODO:

  • Can return subtype of overriden function’s return type

Inheritance

We can create a subclass of another using the extends syntax.

Multiple Inheritance

Note that a class cannot inherit from multiple super-classes (this is where interfaces come in)!

public class Lorenz extends Students {
	@Override
	public void someMethod() {
		...
		super.someMethod(); // Can call super's implementation of this
	}
}

We can then override the methods defined in the parent class. Note that the @Override decorator is optional.

Note that not all methods can be over-riden. The following cannot be over-riden:

  • static
  • final
  • private are not inherited either
  • constructors

Super Attribute

The subclass can access it’s parents attributes and methods through the super keyword.

Chained super

You cannot use super.super.xxx, nor x.super.xxx to access super methods or attributes. This gives a compile error.

Constructors

Constructors are not inherited by subclasses.

When writing a constructor for B extends A we get an automatic call to super() inserted. We can also make this call explicit.

No default constructor

If A has no default (or parameter-less) constructor defined, our public B() must have an explicit call to super(10, "test") with the required arguments.

This means that if A has no default or parameter-less constructor, we must define one explicitly for B.

Visibility

Class visibility:

  • public visible everywhere
  • default only in the package, also means class A does not necessarily need to be in the file A.java
  • private inner classes: only visible to the outer class.

Attribute visibility of parent:

  • private attributes of the parent cannot be accesses from any children
    • Note that inherited methods (not over-riden) can access private attributes
    • To fix this we can use getters and setters
  • protected are visible only to class and subclasses.

Changing visibility of methods: We cannot reduce the visibility, only make it more visible!

Polymorphism

Static vs. Dynamic Type

The static type is the type that the compiler sees as assigned to the variable. The dynamic type is the runtime type. The static type can be changed by casting, the dynamic type by changing the reference to an instance of a different subtype.

// Animal is static type
Animal a = new Dog();
// Dog is dynamic type

Subtype

The dynamic type is always a subtype of the static type.

Instance Of

instanceof tests whether an instance has a same dynamic type or if it implements an interface.

It also returns true if an object is a subclass of the given type: dog instanceof Animal returns true, as does dog instanceof Dog.

instanceof never throws an exception, just compile errors.

Note that:

  • obj = null then obj instanceof String returns false not an exception

Compile Errors:

  • Primitive types cannot be used with Instance Of
  • Cannot check for generics, type erasure prevents this
    • t instanceof List<String will not compile as it cannot check the parametrised type
  • Comparing siblings:
    • Animal -> Dog and Animal -> Cat.
    • Check for animal instanceof Dog/Cat allowed, but dog = new Dog(); dog instanceof Cat throws compile error.
  • Interface checks for final classes: A final class cannot implement an interface in a subclass, thus error
    • Animal a = getanimal() could get a Dog which might implement List thus a instanceof List is not a compile error.

Casting

Upcasting is always done automatically and not needed.

We can use typecasting (B) a for explicit downcasting. This will change the static type of a. This will lead to runtime errors if a is not of type or a subtype of B.

Compile Errors:

  1. Cat c = new Cat(); Dog d = (Dog) c; Casting Static type Cat to Dog as they’re siblings.
  2. Cat c = (Dog) d: Assigning static type of sibling or super-type to sub-type

Runtime Errors: Animal dog = new Dog();

  1. (Husky) dog;: Casting further down than dynamic type
  2. (Cat) dog; : Casting into sibling type

Edge Cases

Null is always castable, Animal cat = null; (Cat) cat; is allowed.

We can upcast an array which will then lead to runtime errors.

Dog[] dogs = new Dog[5];
Animal[] animals = dogs; // Allowed! (upcasting)
animals[0] = new Cat(); // Compiles but ArrayStoreException at runtime!

Type Erasure leads to a warning, but not error here:

List<Dog> dogs = new ArrayList<>();
List<Animal> animals = (List<Animal>) dogs; // Unchecked cast warning

Interface Casting

Interface casting can only throw a runtime error, except if the class is final. This is because the compiler assumes a subtype could implement the interface.

Attributes

The compiler uses the static type of the reference to find which attribute to return. So for an A a = new B() when calling a.test we get A’s test attribute.

If the static type does not define that property, we can’t access it unless we explicitly cast it.

Edge Cases

If we don’t shadow an attribute in a subtype, but instead override the parents attribute with this.x, it changes the parent’s attribute as well.

class A {  
    public int x = 5;  
  
    public void fct1() {  
        System.out.println(this.x);  
    }  
}  
  
class B extends A {  
    public B() {  
        super.x = 10;  
    }  
    public B(int a) {  
        super.x = a;  
    }  
}
 
A a1 = new B(1);
A a2 = new B(12); // As these are different instances, their attributes are separate
B b1 = new B(1);
B b2 = new B(12);
a1.fct1(); // 1 -> Even though static type is A
a2.fct1(); // 12 -> Different output as a2's instance of A has 12
b1.fct1(); // 1 -> same here, even though it dynamic dispatches
b2.fct1(); // 12

Methods

Subtype specific

Static Type Methods

The static type decides which methods can be called on an object. If the static type doesn’t implement it, we can’t call it, even if the dynamic type defines it.

Example

We need to type cast first before using the dynamic type specific methods. We can do this explicitly or anonymously:

Animal a = new Cat();
Cat c = (Cat) a; c.meow();
 
((Cat) a).meow(); // Anonymous cast

Private Methods

Private methods are not inherited from the superclass and cannot be called (same as for private attributes).

Overriden methods

For methods which the dynamic type overrides, Java uses dynamic dispatch calls the method of the dynamic type.

Dynamic Dispatch

Dynamic dispatch uses the actual dynamic type to see which methods to call. In the following example, even though S also defines a p() method, P.r() is called, as the dynamic type of x is R in the last example.

This is because the p(); call is an implicit this.p(); where this is of instance R.

Exceptions

Static type

private, static and final methods are selected by the static type.

Attributes in Dynamic Dispatch

When a function is called in dynamic dispatch, the attributes of the type in which the actual function is executed will be used. In the following example, the attribute data of S is used as the method s2() of S is called.

class T {
    int data = 50;
    public void s2() { System.out.println("T " + data); }
}
 
class S extends T {
    int data = 100;
    public void s2() { System.out.println(this.data); }
}
 
class R extends S {
    int data = 200;
}
 
T r = new R();
r.s2(); // Prints "100"

Object Type

Any type is a subtype of the Object type. The object type defines the methods:

  • .equals(Object o)
  • .toString()
  • a few more…

If we try to call any other methods on an object of static type Object, this will lead to a compile error as the symbol isn’t defined.

To compare objects, we should override the .equals() method.

class Rationals {
	public boolean equals(Object o) {
		// Should check with instance of to make sure no runtime error occurs
		Rational r = (Rational) o; // Otherwise we can't access .n and .d
		return r.d * this.n == r.n * this.d;
	}
}

Shadowing

Attributes cannot be over-riden.

Shadowing happens if a subclass hides the attribute of it’s superclass by defining an attribute that is already defined on the parent:

A a = new A();
a.x = 6; // OK
a.x = true; // Compile error, type mismatch
 
A a = new B();
a.x = 6; // OK
a.x = true; // Compile error, type mismatch because static type is used
 
B b = new B();
b.x = 6; // Type Error
b.x = true; // OK!

We can access the parent’s attribute by casting the static type to it’s parent: ((A) b).x is 6.

Final Attribute

  • primitive types final int pi = 3: immutable so no pi = 4 Compile error
  • Reference types final Cat c = new Cat() cannot change the pointer, but the class attributes are mutable
  • classes final class Cat means no extending the class
  • methods final public int means no overriding in subclasses

Abstract Classes

Abstract Classes cannot be instantiated. They are used to define some shared behaviour (where an Interface could only define the methods to implement) and already actually implement that behaviour.

They can be used as a static type AbstractClassCat cat = new RealCat() but the dynamic type is always a subtype.

Trying to create a new AbstractCat() fails with a compile error.