Substituting types

Domains: Dart

When you override a method, you are replacing something of one type (in the old method) with something that might have a new type (in the new method). Similarly, when you pass an argument to a function, you are replacing something that has one type (a parameter with a declared type) with something that has another type (the actual argument). When can you replace something that has one type with something that has a subtype or a supertype?

When substituting types, it helps to think in terms of consumers and producers. A consumer absorbs a type and a producer generates a type.

You can replace a consumer’s type with a supertype and a producer’s type with a subtype.

Let’s look at examples of simple type assignment and assignment with generic types.

Simple type assignment

When assigning objects to objects, when can you replace a type with a different type? The answer depends on whether the object is a consumer or a producer. Consider the following type hierarchy:

Consider the following simple assignment where Cat c is a consumer and Cat() is a producer:

Cat c = Cat(); 

In a consuming position, it’s safe to replace something that consumes a specific type (Cat) with something that consumes anything (Animal), so replacing Cat c with Animal c is allowed, because Animal is a supertype of Cat.

Animal c = Cat(); 

But replacing Cat c with MaineCoon c breaks type safety, because the superclass may provide a type of Cat with different behaviors, such as Lion:

MaineCoon c = Cat(); 

In a producing position, it’s safe to replace something that produces a type (Cat) with a more specific type (MaineCoon). So, the following is allowed:

Cat c = MaineCoon();

Generic type assignment

Are the rules the same for generic types? Yes. Consider the hierarchy of lists of animals—a List of Cat is a subtype of a List of Animal, and a supertype of a List of MaineCoon:

In the following example, you can assign a MaineCoon list to myCats because List<MaineCoon> is a subtype of List<Cat>:

List<Cat> myCats = List<MaineCoon>(); 

What about going in the other direction? Can you assign an Animal list to a List<Cat>?

List<Cat> myCats = List<Animal>(); 

This assignment passes static analysis, but it creates an implicit cast. It is equivalent to:

List<Cat> myCats = List<Animal>() as List<Cat>; 

The code may fail at runtime. You can disallow implicit casts by specifying implicit-casts: false in the analysis options file.

Methods

When overriding a method, the producer and consumer rules still apply. For example:

For a consumer (such as the chase(Animal) method), you can replace the parameter type with a supertype. For a producer (such as the parent getter method), you can replace the return type with a subtype.

 

Similar pages

Page structure
Terms

List

Methods

Types

Generics

Functions

Getters and setters