If you're interested in functional programming, you might also want to checkout my second blog which i'm actively working on!!

Monday, March 10, 2014

Ceylon: overriding equals and hash

/**
Best practises for implementing Equals and Hash methods
Source: http://www.artima.com/lejava/articles/equality.html
------------------------------------------------------------
Common pitfalls when overriding equals:
- Defining equals with the wrong signature.
- Changing equals without also changing hashCode.
- Defining equals in terms of mutable fields.
- Failing to define equals as an equivalence relation.
The contract of the equals method in Object specifies that equals must
implement an equivalence relation on non-null objects:
It is reflexive:
for any non-null value x, the expression x.equals(x) should return true.
It is symmetric:
for any non-null values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
It is transitive:
for any non-null values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true,
then x.equals(z) should return true.
It is consistent:
for any non-null values x and y, multiple invocations of x.equals(y) should consistently return true
or consistently return false, provided no information used in equals comparisons on
the objects is modified. For any non-null value x, x.equals(null) should return false.
**/
shared abstract class Colour(shared String name) of yellow | orange | green {
shared actual Boolean equals(Object that) {
if (is Colour that) {
return that.canEqual(this) && name == that.name;
}
return false;
}
shared actual default Integer hash => name.hash;
shared default Boolean canEqual(Object that) => that is Colour;
}
shared object yellow extends Colour("yellow") {}
shared object orange extends Colour("orange") {}
shared object green extends Colour("green") {}
shared interface Coloured {
shared formal Colour colour;
}
shared class Fruit(name) {
shared String name;
shared actual default Boolean equals(Object that) {
if (is Fruit that) {
return that.canEqual(this) && name == that.name;
}
return false;
}
shared actual default Integer hash => name.hash;
shared default Boolean canEqual(Object that) => that is Fruit;
}
shared class ColouredFruit(String name, colour) extends Fruit(name) satisfies Coloured {
shared actual Colour colour;
shared actual Boolean equals(Object that) {
if (is ColouredFruit that) {
return that.canEqual(this) && name.equals(that.name) && colour.equals(that.colour);
}
return false;
}
shared actual default Integer hash => name.hash + colour.hash;
shared actual default Boolean canEqual(Object that) => that is ColouredFruit;
}
/**
comparefruits() prints
banana1 equal to banana2 = false
banana2 equal to banana1 = false
**/
void comparefruits() {
Fruit banana1 = Fruit("banana");
ColouredFruit banana2 = ColouredFruit("banana", yellow);
print("banana1 equal to banana2 = ``banana1.equals(banana2)``");
print("banana2 equal to banana1 = ``banana2.equals(banana1)``");
}

1 comment:

  1. you could defined :
    shared class Fruit(name) {
    shared default Boolean canEqual(Fruit that) => true;
    }

    shared class ColouredFruit(String name, colour) extends Fruit(name) satisfies Coloured {
    shared actual default Boolean canEqual(Fruit that) => that is ColouredFruit;
    }

    (In ceylon, we have to declare a method 'shared' to be able to declare it 'default' and refine it ? we don't have the 'protected' java equivalent ?)

    ReplyDelete