Eroxl's Notes
Supertypes and Subtypes

In Java, subtypes and supertypes define relationships between different types in an inheritance hierarchy.

  • A supertype is a more general type, typically an interface or a parent class.
  • A subtype is a more specific type that inherits from or implements the supertype.

Type Substitution

Type substitution refers to the ability to replace a super type with any of its sub type.

Apparent Type vs. Actual Type

  • Apparent Type (Declared Type): The type used when declaring a variable (e.g., List<Integer>).
  • Actual Type (Runtime Type): The specific class of the object being assigned (e.g., ArrayList<Integer> or LinkedList<Integer>).

Available Methods

When using type substitution, you can only call methods that are declared in the apparent type, even if additional methods exist in the actual type.

Example

List<Integer> myList = new LinkedList<Integer>();
myList.add(5);
myList.size();

// ⛔️ ERROR: `getFirst()` is only in `LinkedList`
myList.getFirst();

LinkedList<Integer> myOtherList = new LinkedList<Integer>();
myOtherList.add(5);
myOtherList.size();

// ✅ OK: `getFirst()` is allowed for `LinkedList`
myOtherList.getFirst();

Variable Assignment & Parameter Passing

Variable assignment and method parameter passing follow strict type compatibility rules, ensuring that the assigned value or argument is a sub type of the expected type.

  1. The apparent type of a variable or parameter must be a super type of the assigned value.
  2. sub types can be assigned to super types, but not vice versa.

Examples

Example 1 - Variable Assignment

// ✅ OK: `ArrayList` is a subtype of `List`
List<Integer> myList = new ArrayList<>();

// ✅ OK: Same type
ArrayList<Integer> myArrayList = new ArrayList<>();

// ✅ OK: Allowed subtype assigned to super type
myList = myArrayList; 

// ⛔️ ERROR: Super type assigned to subtype
myArrayList = myList;

The last assignment fails because myList has an apparent type of List<Integer>, which might hold any List<Integer> implementation, not necessarily an ArrayList<Integer>.

Example 2 - Parameter Passing

public class CollectionUtil {
    void addAll(ArrayList<Integer> list) { 
        ...
    }
}

CollectionUtil myCollection = new CollectionUtil();

List<Integer> myList = new ArrayList<>();
ArrayList<Integer> myArrayList = new ArrayList<>();

// ⛔️ ERROR: `List` is not an `ArrayList`
myCollection.addAll(myList); 
// ✅ OK: Allowed
myCollection.addAll(myArrayList);

addAll() accepts only an ArrayList<Integer>, so passing List<Integer> (even if it's actual type is ArrayList<Integer>) fails.

Example 3 - Returning a Subtype in a Method

public class CollectionUtil {
    List<Integer> makeList() {
        // ✅ OK: `ArrayList` is a subtype of `List`
        return new ArrayList<>();
    }

	LinkedList<Integer> makeLinkedList() {
        // ⛔️ ERROR: `ArrayList` is not a subtype of `LinkedList`
        return new ArrayList<>();
    }
}

Example

interface List<E> { ... }  // Supertype of ArrayList and LinkedList

class ArrayList<E> implements List<E> { ... }  // Subtype of List
class LinkedList<E> implements List<E> { ... }  // Subtype of List
  • List<E> is the supertype of both ArrayList<E> and LinkedList<E>.
  • ArrayList<E> and LinkedList<E> are subtypes of List<E>.