The whole rectangle square discussion only stems
from insufficiant care to distinguish between
value and storage objects.
Too true.
Whether something is a subclass of something else depends on the
operations on the class as much as it does on the concept.
In mathematics, every square is a rectangle. However, in mathematics,
you don't usually assign to the length of a side of an existing
rectangle.
This also gives us extra complexity wrt. generics.
If Foo is a class, and Bar is a properly designed subclass (so that
anywhere a Foo is expected, a Bar can be used instead), then we
would like to think that a parameterized Baz<T> class would satisfy
that Baz<Foo> was a proper super-type of Baz<Bar>.
This is not the case in Java, and for exactly the reason Stefan
was describing: it matters what operations are on the type.
The simplest "storage" class that displays the problem is something
like:
public class Box<T> {
private T value;
public T get() { return value; }
public void set(T value) { this.value = value; }
}
If Box<Foo> was a supertype of Box<Bar>, then everywhere a
Box<Foo> was expected, a Box<Bar> could be used. However, that
fails for:
public void doSet(Box<Foo> box, Foo foo) { box.set(foo); }
Calling this with a Box<Bar> and a Foo would try to store a Foo
in a Box<Bar>, but the Foo is not a Bar.
The other direction doesn't work either. A Box<Bar> is not a supertype
of Box<Foo>, because then the following method would be acceptable:
public Bar doGet(Box<Bar> box) { return box.get(); }
Passing a Box<Foo> as argument would be able to return a Foo, which
is not allowed.
The "Box" class displays both types of behavior: value (get method)
and storage (set method). Trying to specialize on the property being
gotten and set shows the problem in both directions.
Another example of how getting and setting vary (co- and contra-variantly,
respectively):
public interface Setter<T> {
set(T value);
}
public interface Getter<T> {
T get();
}
class Example {
public <T> void doSet(Setter<? super T> setter, T value) {
setter.set(value);
}
public <T> void doGet(Getter<? extends T> getter) {
return getter.get();
}
}
Notice the difference: super vs extends.
For a setter, you can allow a supertype as parameter, because it can
still receive all the values of the subtype.
For a getter, you can allow a subtype as parameter, because it can
still only produce values of the subtype.
If you need both a getter and a setter for the same property, you can't
allow anything but the exact type.
Which is also why a Collection<Foo> is not a Collection<Bar>, or the
other way around. Instead we use Collection<? super Foo> where we can
only add values, or Collection<? extends Foo> where we can only
extract values.
/L