Wednesday, July 4, 2007

ClassCastException

When do we get a ClassCastException?

The java documentation of ClassCastException states:-

Thrown to indicate that the code has attempted to cast an object to a subclass of which it is not an instance.
For example, the following code generates a ClassCastException:

Object x = new Integer(0);
System.out.println((String)x);


The statement seems to be a very simple, but not when different class loaders are involved. Another scenario when this exception occurs is when the class accesses an object of the same class loaded by another class loader hierarchy.

I was determined to write a simple code to simulate the exception.

So I wrote two classes to be loaded by different classloaders.

package classldr;

public class Tree {

private String name = "sash";

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

//------ different file ----------//
package classldr;

public class Person{

public void doIT(Object t)
{
//try to create the typecast exception here!!
System.out.println("Tree planted : " + ((Tree)t).getName());
}
}



I would try to load these classes using different classloaders and simulate the error.

So I created instances of URLClassLoader and did the same. I had to make sure that the class creating the class loaders could not access these classes, as it was the parent of the URLClassLoader and would have actually loaded the class due to delegation model followed by the classloaders.

package classldr;

import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

public class TestClassLoader {

public static void main(String[] args) throws Exception {

/*
* Class loader 1 to create instance of Tree class. I have put the
* Person and Tree classes in C:\classldr\ directory.
*/
ClassLoader cl1 = new URLClassLoader(new URL[] { new URL(
"file:C:\\") });
Class c = cl1.loadClass("classldr.Tree");
Object treeNewInstance = c.newInstance();

/*
* Class loader 2 to create instance of Person and Tree class.
*/
ClassLoader cl2 = new URLClassLoader(new URL[] { new URL(
"file:C:\\") });
Class treeClass = cl2.loadClass("classldr.Tree");
Class personTreeClass = cl2.loadClass("classldr.Person");
Object personTreeObj = personTreeClass.newInstance();
Object treeClassObj = treeClass.newInstance();
Method method = personTreeClass.getMethod("doIT",new Class[]{Object.class} );


//pass the object of tree loaded by cl 1 to
//person object loaded by cl 1
method.invoke(personTreeObj,new Object[]{ treeClassObj});

//pass the object of tree loaded by cl 1 to
//person object loaded by cl 2
//should throw a ClassCastException
method.invoke(personTreeObj,new Object[]{ treeNewInstance});
}
}



After I spent a lot of time on this I realized it could be demonstrated in a much simpler way. I decided that my URLClassLoader could be a child of the Bootstrap class loader, not necessarily the system class loader.

package classldr;

import java.net.URL;
import java.net.URLClassLoader;

public class TestClassLoader2 {

public static void main(String[] args) throws Exception {

//get current class location
URL resource = TestClassLoader2.class.getClassLoader().getResource(".");

//create a new classloader to load this class,
//have the parent of the classloader as null
ClassLoader cl1 = new URLClassLoader(new URL[] { resource }, null);
Class c = cl1.loadClass("classldr.TestClassLoader");

//this throws ClassCastException
TestClassLoader2 treeNewInstance = (TestClassLoader2) c.newInstance();
}
}


This code was good enough to simulate the exception.

Finally I successfully generated the exception and was feeling very stupid about my first method. :(

2 comments:

Wohi Rashid said...

This is good stuff. I am also confused about Hierarchy of class loader. I am confused about this statement
"Classes loaded by Bootstrap class loader have no visibility into classes loaded by its descendants (ie Extensions and Systems class loaders).
The classes loaded by system class loader have visibility into classes loaded by its parents (ie Extensions and Bootstrap class loaders)."

sash said...

This means that the parent classloaders don't have access to the classes loaded by its descendants, but the descendants have the access to the classes loaded by the parents. If classloader A has two decsendents B and C (siblings) and if B loads a class 'classB' then C should not be able to access 'classB'. Now both B and C can access all classes loaded by classloader A.

Now if A is allowed the access to 'classB' since C has access to all the classes loaded by A, C will also have access to 'classB' and the java sandbox will fail thus this is not allowed.