Minimizing the use of instanceof

Maybe the following problem has a very simple solution, however i do not see it:

Consider the following simple design:

class a{

class b extends a{

...

}

class c extends a{

...

}

}

Now I want to overwrite Object.equals() in the following way:

equals(o) == false if o ist not an instance of class a

else

equals should be dispatched to b or c if o is an instance of b or c respectively.

If an instance of b is compared to an instance of c this should be acclompished by a method implemented just in class a.

I am aware that everything can be arranged by using the instanceof operator. However I am looking for a

polymorphic solution where the usage of the instanceof operator is minimized.

any hints are welcome

[822 byte] By [lotharp] at [2007-9-30 22:14:48]
# 1

You should pass in references to class a and then let polymorphism take over:

int maxA = 3;

a [] arrayOfA = new a[maxA];

arrayOfA[0] = new a();

arrayOfA[1] = new b();

arrayOfA[1] = new c();

for (int k = 0; k < maxA; ++k)

{

arrayOfA[k].someMethod();

}

Polymorphism will pick out the right method for classes a, b, and c, depending on the runtime type.

If you're using instanceof, you're not doing it right.

%

duffymo at 2007-7-7 11:27:57 > top of Java-index,Other Topics,Patterns & OO Design...
# 2

> Now I want to overwrite Object.equals() in the

> following way:

> equals(o) == false if o ist not an instance of class a

> else

> equals should be dispatched to b or c if o is an

> instance of b or c respectively.

> If an instance of b is compared to an instance of c

> this should be acclompished by a method implemented

> just in class a.

Hmm, you know the transitivity requirement of the equals contract is easily broken with designs like this. You might want to read what Joshua Bloch has to say on this issue:

http://java.sun.com/developer/Books/effectivejava/Chapter3.pdf

thomas.behr at 2007-7-7 11:27:57 > top of Java-index,Other Topics,Patterns & OO Design...
# 3

> Now I want to overwrite Object.equals() in the

> following way:

> equals(o) == false if o ist not an instance of class a

> else

> equals should be dispatched to b or c if o is an

> instance of b or c respectively.

> If an instance of b is compared to an instance of c

> this should be acclompished by a method implemented

> just in class a.

This is inconsistent with what you said above.

Are you saying something like this?

If it's not an instanceof a, return false.

Else if b is being compared to c, or c to b, then just use a's method

Else if comparing b to b, call b's method,

Else if comparing c to c call c's method.

?

If so, I gotta ask: Why? It seems twisted for no good reason.

You could do something like that. Implement equals(Object) only in a. Have it do the initial tests (It will have to use instanceof or getClass()). Then it can dispatch to a specific method.

If b & c implement equals(Object), then Java's polymorphism dictates that their implementations (not a's) will be the ones called.

Also note that equals always has an instanceof or getClass test.

Also note that you have to be careful of transitivity and symmetry requirements for equals. The general rule is, either the exact class has to match, in which case you compare this.getClass() to that.getClass(), or else instances of different subclasses can be equal, in which case you use intanceof ParentClass, and either only the parent class implements equals, or the child classes implement the same algorithm.

>

> I am aware that everything can be arranged by using

> the instanceof operator. However I am looking for a

> polymorphic solution where the usage of the instanceof

> operator is minimized.

>

> any hints are welcome

jverd at 2007-7-7 11:27:57 > top of Java-index,Other Topics,Patterns & OO Design...
# 4

Thomas,

Josh Bloch's book is my favorite Java book. He points out that int the case of inheritence one has to be careful in overwriting equals.

However the problems J.B. points out are not an issue in my design. Following your suggestion I carefully went through the chapter in the book an found an

idea for the simple polymorphic solution I was looking for:

class a{

boolean equals(Object o){

if(o instanceof a)

return doSomething(this,(a) o);

return false;

}

...

class b extends a{

boolean equals(Object o){

if(o instanceof b)

return doSomething(this,(b) o);

return super.equals(o);

}

...

}

class c extends a{

boolean equals(Object o){

if(o instanceof c)

return doSomething(this,(c) o);

return super.equals(o);

}

...

}

}

My intention was to avoid the simple instanceof style was inspired by another popular book. From Effective C++, by Scott Meyers :

"Anytime you find yourself writing code of the form "if the object is of type T1, then do something, but if it's of type T2, then do something else," slap yourself.

Of course one can't avoid at least one occurrence of instanceof while overriding equals. But one occurence should be sufficient.

Thanks for your advice an for all other repsonses

Lothar

lotharp at 2007-7-7 11:27:57 > top of Java-index,Other Topics,Patterns & OO Design...
# 5

> > class a{

> boolean equals(Object o){

> if(o instanceof a)

> return doSomething(this,(a) o);

> return false;

> }

> ...

>

> class b extends a{

> boolean equals(Object o){

> if(o instanceof b)

> return doSomething(this,(b) o);

> return super.equals(o);

> }

> ...

> }

>

>

> class c extends a{

> boolean equals(Object o){

> if(o instanceof c)

> return doSomething(this,(c) o);

> return super.equals(o);

> }

> ...

> }

> }

>

>

>

You've broken symmetry here. a.equals(b) may return true, but b.equals(a) never can.

Once you use instanceof, then no subclass should override equals, or, if it does, it must both 1) use the same instanceof as the superclass uses and 2) use the same logic (even if it's not implemented exactly the same way).

jverd at 2007-7-7 11:27:57 > top of Java-index,Other Topics,Patterns & OO Design...
# 6
Side note: Why pass this to doSomething?
jverd at 2007-7-7 11:27:57 > top of Java-index,Other Topics,Patterns & OO Design...
# 7

> Josh Bloch's book is my favorite Java book. He points

> out that int the case of inheritence one has to be

> careful in overwriting equals.

> However the problems J.B. points out are not an issue

> in my design. Following your suggestion I carefully

> went through the chapter in the book an found an

> idea for the simple polymorphic solution I was looking

> for:

That code breaks the equals() contract.

the equals contract says that if a == b then b must == a and if a == b and b == c then c must == a.

The classes you have just posted do not fufill the latter.

Consider:

class A

{

boolean x;

A(boolean x)

{

this.x = x;

}

public boolean equals(Object o)

{

if (o instanceof A) {

return x == ((A) o).x;

} else {

return false;

}

}

}

class B

{

boolean y;

A(boolean x, boolean y)

{

super(x);

this.y = y;

}

public boolean equals(Object o)

{

if (o instanceof B) {

B b = (B) o;

return x == b.x && y == b.y;

} else {

return super.equals(o);

}

}

}

Then:

A a = new B(0,0);

A b = new A(0);

A c = new B(0,1);

System.out.println(a == b);

System.out.println(b == c);

System.out.println(c == a);

The results:

true

true

false

dubwai at 2007-7-7 11:27:57 > top of Java-index,Other Topics,Patterns & OO Design...
# 8

That was stupid:

A a = new B(0,0);

A b = new A(0);

A c = new B(0,1);

System.out.println(a.equals(b));

System.out.println(b.equals(c));

System.out.println(c.equals(a));

The results:

true

true

false

dubwai at 2007-7-7 11:27:57 > top of Java-index,Other Topics,Patterns & OO Design...
# 9

> The classes you have just posted do not fufill the

> latter.

Nor the former, as far as I can tell.

> > System.out.println(a == b);

> System.out.println(b == c);

> System.out.println(c == a);

>

>

Presumably those == are actually .equals()?

jverd at 2007-7-7 11:27:57 > top of Java-index,Other Topics,Patterns & OO Design...
# 10
> You've broken symmetry here. a.equals(b) may return> true, but b.equals(a) never can.No the symmetry is fine. b.equals(a) calls super.equals() i.e. the a version when o is not a b.
dubwai at 2007-7-7 11:27:57 > top of Java-index,Other Topics,Patterns & OO Design...
# 11
You're right. However omitted some crucial code (sorry for this): in the complete design instances of class a solely do not exist. This menas that a.equals(b) and b.equals(a) both are dispatched to equals(a,a) and symmetry should be ok.
lotharp at 2007-7-7 11:27:58 > top of Java-index,Other Topics,Patterns & OO Design...
# 12

> > You've broken symmetry here. a.equals(b) may return

> > true, but b.equals(a) never can.

>

> No the symmetry is fine. b.equals(a) calls

> super.equals() i.e. the a version when o is not a b.

Oops. Missed the super.equals(). You're right.

jverd at 2007-7-7 11:27:58 > top of Java-index,Other Topics,Patterns & OO Design...
# 13

> You're right. However omitted some crucial code (sorry

> for this): in the complete design instances of class

> a solely do not exist. This menas that a.equals(b)

> and b.equals(a) both are dispatched to equals(a,a)

> and symmetry should be ok.

Yeah, I was mistaken about symmetry. Check out what dubwai says about transitivity though.

jverd at 2007-7-7 11:27:58 > top of Java-index,Other Topics,Patterns & OO Design...
# 14

> You're right. However omitted some crucial code (sorry

> for this): in the complete design instances of class

> a solely do not exist. This menas that a.equals(b)

> and b.equals(a) both are dispatched to equals(a,a)

> and symmetry should be ok.

Transitivity is broken. You cannot make this design scheme transitive. This can be OK if you make it clear in the docs. For example, SortedSet breaks the equals contract.

dubwai at 2007-7-7 11:27:58 > top of Java-index,Other Topics,Patterns & OO Design...
# 15

> Transitivity is broken. You cannot make this design

> scheme transitive. This can be OK if you make it

> clear in the docs. For example, SortedSet breaks the

> equals contract.

Make sure you have a good reason though. Complicating the design, creating nonstandard equals() methods that break the contract, shouldn't be done without a good reason to do so.

Why do you want b.equals(c) and c.equals(b) to use a's equals and b.equals(b), c.equals(c) to use their respective subclass' equals?

jverda at 2007-7-20 0:56:04 > top of Java-index,Other Topics,Patterns & OO Design...
# 16

> Why do you want b.equals(c) and c.equals(b) to use a's

> equals and b.equals(b), c.equals(c) to use their

> respective subclass' equals?

My guess is that it's an attempt to solve the symmetry problem with overriding equals. I tried the same thing when I was still a little green.

dubwaia at 2007-7-20 0:56:04 > top of Java-index,Other Topics,Patterns & OO Design...
# 17
right - transitivity is broken if instances of a exist. as i pointed our earlier instance of a definitly do not exist. the constructor of a is private! i don't see any violations against the equlas contract under these circumstances. do you?
lotharpa at 2007-7-20 0:56:04 > top of Java-index,Other Topics,Patterns & OO Design...
# 18

> right - transitivity is broken if instances of a

> exist. as i pointed our earlier instance of a

> definitly do not exist. the constructor of a is

> private! i don't see any violations against the

> equlas contract under these circumstances. do you?

Not off the top of my head. Nonetheless, it seems like an overly complex solution for no good reason. If you really have a need to compare two objects in this hierarchy based on their "least common denominator," then this may be a reasonable solution. But it would take some convincing before I'd think that's really necessary.

jverda at 2007-7-20 0:56:04 > top of Java-index,Other Topics,Patterns & OO Design...
# 19

> right - transitivity is broken if instances of a

> exist. as i pointed our earlier instance of a

> definitly do not exist. the constructor of a is

> private!

Well, you can extend it if the contructor is private. You could make it abstract.

> i don't see any violations against the

> equlas contract under these circumstances. do you?

Same problem exists between b and c. The only reason I can see for putting the verison of equals in a, is so you can have more than one class extend a directly. If you do that, you run into the same issue.

dubwaia at 2007-7-20 0:56:04 > top of Java-index,Other Topics,Patterns & OO Design...
# 20
I need to wake up.Maybe I'll try Jos' excuse and blame the flu. Yeah, that's it. I have the flu. (*cough, cough*)
jverda at 2007-7-20 0:56:04 > top of Java-index,Other Topics,Patterns & OO Design...
# 21
> Well, you can extend it if the contructor is private.That's can't.
dubwaia at 2007-7-20 0:56:04 > top of Java-index,Other Topics,Patterns & OO Design...
# 22
> > Well, you can extend it if the contructor is> private.> > That's can't.Oh, you've got the flu too?
jverda at 2007-7-20 0:56:04 > top of Java-index,Other Topics,Patterns & OO Design...
# 23
> Oh, you've got the flu too?Monkey Pox.
dubwaia at 2007-7-20 0:56:04 > top of Java-index,Other Topics,Patterns & OO Design...
# 24

> > right - transitivity is broken if instances of a

> > exist. as i pointed our earlier instance of a

> > definitly do not exist. the constructor of a is

> > private!

>

> Well, you can't extend it if the contructor is private.

Well, you can ...public class PrivateExtension {

private PrivateExtension() {

}

public static class Johnny extends PrivateExtension {

}

}

class Oops extends PrivateExtension.Johnny {

//

// is it broken ? hmm.

//

}

And we can note interesting behaviour here also ... simply by extending the class once,

we can then extend it again. I.e. the private-ness of the constructor isn't transferred because the

public constructor is automatically inserted into the second class. Interesting to me, at least.

Perhaps the automatic constructor should take the priviledge of it's parent ?

Hmm.

silk.ma at 2007-7-20 0:56:04 > top of Java-index,Other Topics,Patterns & OO Design...
# 25

> Perhaps the automatic constructor should take the

> priviledge of it's parent ?

I hadn't noticed this issue before. Thanks for pointing it out. I had to check some code I wrote where I was doing this (I marked the nested classes as final.) It's a little off-topic so lets just say that for unless there are inner or nested classes involved, you cannot extend a class that only has a private constructor.

dubwaia at 2007-7-20 0:56:04 > top of Java-index,Other Topics,Patterns & OO Design...
# 26

> so lets just say that

> for unless there are inner or nested classes involved,

> you cannot extend a class that only has a private

> constructor.

Well sure, but it's a bit like saying "except for when you can, you can't ..." :)

But do you think there would be any problems with the auto constructor

taking priviledges from the parent instead of being public.

Of course, it would mess up some current compilations, but it seems to make

more oo-sense, doesn't it? I.e. my parent's constructor is X so I would also like

to be that.

Although java, imo, is a bit strange with constructors and inheritence anyway, so

maybe it fits in :)

silk.ma at 2007-7-20 0:56:04 > top of Java-index,Other Topics,Patterns & OO Design...
# 27

> Well sure, but it's a bit like saying "except for when

> you can, you can't ..." :)

It's the exception that proves the rule.

> But do you think there would be any problems with the

> auto constructor

> taking priviledges from the parent instead of being

> public.

>

> Of course, it would mess up some current compilations,

> but it seems to make

> more oo-sense, doesn't it? I.e. my parent's

> constructor is X so I would also like

> to be that.

>

> Although java, imo, is a bit strange with constructors

> and inheritence anyway, so

> maybe it fits in :)

I think the default constructor was a nice thought that was supposed to make life easier but it's one of those things that causes confusion.

dubwaia at 2007-7-20 0:56:04 > top of Java-index,Other Topics,Patterns & OO Design...