Let's use an example with a non-abstract base class:
public class Human {
public string getName() {
// ...
}
}
public class Student extends Human {
public void learn(Subject subject) {
// ...
}
}
public class Teacher extends Human {
public void teach(Subject subject) {
// ...
}
}
At any place where a Human
is expected, a Student
or Teacher
will do just as well, as they fully implement the Human
interface. (In this case, that getName()
can be called on them.) Java inheritance guarantees that this is the case technically. Making it work semantically is the class author's job, so that his code fulfils the Liskov substitution principle.
So doesn't this mean that we can also substitute Collection<Teacher>
where a Collection<Human>
is expected? Not always. Consider the following method:
public class Human {
// ...
public void join(Set<Human> party) {
party.add(this);
}
}
Now, if Java allowed a Set<Student>
to be passed as party, any attempts of non-Student
Human
s to join that party would have to fail at runtime.
As a general rule, a container of a subtype is unsuitable if the receiver (callee in case of a function argument, caller in case of a function return value) wants to put something into it, but acceptable if the receiver only want to take stuff out and use it. A container of a supertype is unsuitable if the receiver wants to take stuff out and use it, but acceptable if the receiver only ever puts stuff into it. As a result, if the receiver both takes stuff out of the collection and puts stuff into the collection, they usually must require a collection of a fixed type.
Our join
method only puts Human
s into the party
, so we could also allow a Set<Object>
or a non-generic Set
or equivalently a Set<?>
. Java allows us to do that with lower-bounded wildcards:
public class Human {
// ...
public void join(Set<? super Human> party) {
party.add(this);
}
}
For opening up the possibilities towards subclasses, there's upper-bounded wildcards:
public class Teacher extends Human {
public void teach(Subject subject, Set<? extends Student> schoolClass) {
for (Student student : class) {
student.learn(subject);
}
}
}
Now, if we ever subclass Student
, the passed schoolClass
can be a Set
of that subtype, too.