There's a reason the auto-generated equals method doesn't do what you're suggesting. The canEqual convention is a common and well-documented one, because it allows for handling a broader set of common use cases.
You don't always want to exclude all sub classes when implementing an equals method, only ones that override equals. If, as you described, there was a Position3D class extending Position, and Position3D had an additional field z that was part of the Position3D equals method, then of course that Position3D should not be equal to any normal 2D position.
But what if for some reason a user wants to instantiate an anonymous subclass of Position?
val pos = new Position(1,2){}
or
trait Foo
val pos = new Position(1,2) with Foo
This position should probably be considered equal to new Position(1,2)
. Disallowing this equality could in some situations be tedious, if the mixed-in functionality is trivial or just for some particular local-scope use case. To require, as the other response suggested, other.getClass == classOf[Position]
, a client API mixing in a trait or for some reason creating an anonymous class might find they're creating positions that are not even equal to themselves.
Consider this code:
class Position(val x:Int, val y:Int) {
override def equals(other:Any) = other match {
case o:Position if o.getClass==classOf[Position] && o.x==x && o.y==y => true
case _ => false
}
}
trait Foo
val p1 = new Position(1,2) with Foo
val p2 = new Position(1,2) with Foo
p1 == p2
the result is false