From: Tom Anderson on
On Mon, 28 Jun 2010, Lew wrote:

> Tom Anderson wrote:
>
>> And of course, it goes without saying that:
>>
>> Hand.getBird().equals(Collections.asList(Bush.getBird(), Bush.getBird()))
>
> That works as a pun, but as Java code would fail to yield 'true' under
> conformant implementations of 'equals()'.

I forgot to mention that Bird implements List<Bird>. Weird, but there you
go.

tom

--
Fitter, Happier, More Productive.
From: Tom Anderson on
On Mon, 28 Jun 2010, Lew wrote:

> Arne Vajh?j wrote:
>>> And in general those figure examples often lead to problems like the
>>> wellknown Rectangle versus Square problem.
>
> ClassCastException wrote:
>> A problem that goes away when you make the durn things immutable.
>
> Not really, but it does go away when you make the classes final.

You mean both of them? Then you either have to introduce a common base
class ParallelRightQuadrilateral, or be unable to write code that
manipulates boxes uniformly. Making Square a subtype of Rectangle is still
very attractive from a programmer-friendliness point of view. Immutability
is one way of overcoming the problems that introduces - and it really does
solve them. Squares are only non-Rectangular when they'r mutable, because
it's only their mutability that is not a superset of Rectangle's

tom

--
Fitter, Happier, More Productive.
From: Lew on
Tom Anderson wrote:
>>> And of course, it goes without saying that:
>>>
>>> Hand.getBird().equals(Collections.asList(Bush.getBird(), Bush.getBird()))
>

Lew wrote:
>> That works as a pun, but as Java code would fail to yield 'true' under
>> conformant implementations of 'equals()'.
>

Tom Anderson wrote:
> I forgot to mention that Bird implements List<Bird>. Weird, but there you
> go.
>

That still would not be conformant, because the requirement for
'List#equals()' is, "Returns true if and only if the specified object
is also a list, both lists have the same size, and all corresponding
pairs of elements in the two lists are equal."

A List with one element cannot be equal to a List with two elements
under a conformant 'equals()'. Besides, it's not true that a bird in
the hand is *equal to* two in the bush, it's that a bird in the hand
is *worth* two in the bush.

--
Lew
From: Lew on
ClassCastException wrote:
>>> A problem that goes away when you make the durn things immutable.
>

Lew wrote:
>> Not really, but it does go away when you make the classes final.
>

Tom Anderson wrote:
> You mean both of them? Then you either have to introduce a common base
> class ParallelRightQuadrilateral, or be unable to write code that
> manipulates boxes uniformly. Making Square a subtype of Rectangle is still
> very attractive from a programmer-friendliness point of view. Immutability
> is one way of overcoming the problems that introduces - and it really does
> solve them. Squares are only non-Rectangular when they'r mutable, because
> it's only their mutability that is not a superset of Rectangle's
>

Oh, yeah. You're right.

--
Lew
From: ClassCastException on
On Mon, 28 Jun 2010 20:31:40 -0700, Peter Duniho wrote:

> Arne Vajhøj wrote:
>> [...]
>>> It's debatable whether one really needs methods in the type that
>>> return modified versions of the original. But assuming one does,
>>
>> It is rather common to have such. The Java API itself has some
>> important ones.
>
> Really? Where is the Square type in the Java API that inherits
> Rectangle, never mind the immutable version that returns modified
> versions of the original?
>
>>> could easily enforce Rectangles and Squares, either by throwing
>>> exceptions if misused (which is actually possible even with mutable
>>> types, though would involve more work), or by providing methods that
>>> always return the right kind of type (Square if the new dimensions are
>>> equal, Rectangle otherwise).
>>
>> What should the formal return type be?
>
> Whatever you want it to be. One option is Rectangle. If it matters
> whether the resulting type is a Square (e.g. Rectangle.FitInSquare()),
> you can check and cast.
>
> If you want the return type to be Square, you have to throw an exception
> if someone tries to return a non-Square from a Square. Or you provide
> an API that only returns a Square (e.g. adjusts both dimensions by
> identical amounts).
>
>> The sub class can return the super class but not the other way around.
>
> Rectangle can return a Square instance and Square can return a
> Rectangle. I have no idea why you think you can't do it that way.
>
> Of course, whatever API is chosen, it needs to make sense in context.
> But there's no fundamental reason Rectangle can't return a Square.
>
>>> There's no reason for weird behavior to exist. Just don't implement
>>> weird behavior. :)
>>
>> Difficult to avoid if the classes should have rich functionality.
>
> Now you are adding requirements not originally stipulated. No one said
> anything about "rich functionality", nor is it even clear what is meant
> in the case of the Square/Rectangle example.
>
> Pete

How about making the example more concrete, then.

public class Rectangle {
protected final double l;
protected final double t;
protected final double w;
protected final double h;

public Rectangle (double left, double top, double width,
double height) {
if (width <= 0.0) throw new IllegalArgumentException();
if (height <= 0.0) throw new IllegalArgumentException();
l = left; t = top; w = width; h = height;
}

// getters go here, and getRight and getBottom

public Rectangle getScaledInstance (double scaleFactor) {
if (scaleFactor == 0.0) throw new IllegalArgumentException();
// top left corner is center of scaling transform's effect
if (scaleFactor < 0.0)
return new Rectangle(l + w*scaleFactor, t + h*scaleFactor,
-w*scaleFactor, -h*scaleFactor);
return new Rectangle(l, t, w*scaleFactor, h*scaleFactor);
}

public Rectangle getWithWidth (double newWidth) {
if (newWidth <= 0.0) throw new IllegalArgumentException();
return new Rectangle(l, t, newWidth, h)
}

public Rectangle getWithHeight (double newHeight) {
if (newHeight <= 0.0) throw new IllegalArgumentException();
return new Rectangle(l, t, w, newHeight)
}

public Rectangle getMoved (double newLeft, double newTop) {
return new Rectangle (newLeft, newTop, w, h);
}
}

public class Square extends Rectangle {
public Square (double left, double top, double size) {
super(left, top, size, size);
}

public Square getScaledInstance (double scaleFactor) {
if (scaleFactor == 0.0) throw new IllegalArgumentException();
// top left corner is center of scaling transform's effect
// w = h = size
if (scaleFactor < 0.0)
return new Square(l + w*scaleFactor, t + h*scaleFactor,
-w*scaleFactor);
return new Square(l, t, w*scaleFactor);
}

public Square getMoved (double newLeft, double newTop) {
return new Square (newLeft, newTop, w);
}
}

The "copy-mutator" methods that would preserve squareness return Squares.
The ones that could produce non-square rectangles aren't even overridden,
and return Rectangles. This works, though it's possible to get square
Rectangles that are not Squares. Making the constructor protected, adding
a public factory method that calls it normally but only after if (width
== height) return new Square(left, top, width);, and changing the "copy-
mutators" to call this factory method would address this, if one felt it
necessary.

Of course if there's any problem with the above let me know and I'll try
to address it.