Subtyping in practice

Subtyping terminology (subtype, is-a)


Subtyping for primitive values

boolean < byte < short < char < int < long < float < double

Run-time casts

if A is-a B, then you can use an A anywhere you need a B:

B x = new A();
float y = 7;

However, you cannot use a B wherever you need an A:

A x = new B(); // error!  a B is not (necessarily) an A!
int y = 1.0;   // error!  a float is not (necessarily) an int!

You can (but should almost never) cast a B to an A:

B b = ...;
A a = (A) b; // checks at runtime whether b is actually an A, fails if not

double d = ...;
int y = (int) d; // gives the closest int to d;

If you find yourself casting something, think carefully about redesigning your class hierarchy. We'll introduce some new tools soon that can help.

Note: It is safe and occasionally useful to explicitly cast to a supertype. For example, if you want to use double division instead of integer division, you will need to cast one of the operands to a double first:

int i = 1, j = 2;
double d = i/j; // not what you want: first does integer division (giving 0), _then converts to double_

double iAsDouble = i;
double e = iAsDouble / j; // works but is clunky; first convert to double, then use double division

double f = (double) i / j; // a good use of casting.

Widening an interface

When creating a subtype, you must satisfy all requirements of the supertype. If the supertype says the method must do x, the subtype may say the method must do both x and y, but it cannot throw out the requirement that the method do x.

One example of this that is present in other languages (though as it turns out, not Java) is that the arguments for the subtype may have more general types than the arguments of the supertype. For example, if type DogLover has a method called love(Dog), then you can (in principle) implement this interface with a method love(Animal).

In fact, Java rules this out, but it does allow a different example, which we will cover tomorrow.