Saturday, June 20, 2009

isAssignableFrom and instanceof

I had occasion recently to use Class.isAssignableFrom again.

I don't use this API routinely, so when I do use it I always seem to have to stop and think about it for a second, to make sure I know what I'm doing. Class.isAssignableFrom is closely related to instanceof, but they do slightly different things, and both are extremely powerful.

I think that one of the reasons that I get a bit confused with isAssignableFrom is that it sort-of reads backwards from instanceof. That is, suppose you have the following:


interface IFace { }

class A implements IFace { }

class B extends A { }

class CNum extends java.math.BigInteger implements IFace
{
public CNum(String s) {super(s);}
}


And then suppose that you create some instances of these classes:


A a = new A();
B b = new B();
CNum c = new CNum("1");
IFace i = new CNum("2");


Then the following uses of isAssignableFrom and instanceof are somewhat illuminating:


if ( Object.class.isAssignableFrom(B.class) ) { ... }

if ( b instanceof Object ) { ... }


These are both true, because Object is assignable from everything. Object is the root type of all Java classes, so Object.class is always assignable from every other Java class.

Looking at the two statements above, do you see why I say that one reads backwards from the other? With instanceof, you tend to see the subclass on the left, while with isAssignableFrom, you tend to see the subclass on the right. Or, at least, I think that's why I sometimes stumble over isAssignableFrom, because instanceof seems to read naturally to me, but I always have to stop a second and think about isAssignableFrom.

Any instance of B is an instance of Iface, because B is a subclass of A, and A implements IFace, so the following is always true:

if (b instanceof IFace) { ... }
and
if (b instanceof A) { ... }


But a particular instance of Iface might in actuality be a A, a B, or a CNum, so the following is true because we initialized the variable i to be a CNum instance:


if (i instanceof BigInteger) { ... }


And for that very same reason, this statement is false:

if ( i.getClass().isAssignableFrom(b.getClass())) { ... }


A class is assignable from itself:


if ( c.getClass().isAssignableFrom(CNum.class)) { ... }


An interface is assignable from an implementing class:


if ( IFace.class.isAssignableFrom(b.getClass())) { ... }


It's a wonderful API.

2 comments:

  1. You say they do slighly different things...

    Are there differences apart from the right-to-left order and operating on classes instead of instances?

    ReplyDelete
  2. I would find this a lot easier to read if the text were grey and the code black.

    ReplyDelete