Java Specialists' Java Training Europehome of the java specialists' newsletter

The Java Specialists' Newsletter
Issue 0182001-05-03 Category: Language Java version:

GitHub Subscribe Free RSS Feed

Class names don't identify a class

by Dr. Heinz M. Kabutz

Welcome to the 18th issue of The Java(tm) Specialists' Newsletter, now sent to 38 countries on all continents of our globe. Please remember to forward the newsletter to others who might be interested.

NEW: We have revised our "Advanced Topics" course, covering Reflection, Java NIO, Data Structures, Memory Management and several other useful topics for Java experts to master. 2 days of extreme fun and learning. Extreme Java - Advanced Topics.

Class names don't identify a class

This week I want to introduce the concepts of having Class objects in the VM with the exact same name, but being completely different. I had the opportunity to question Dr. Jung, who has been using this for a while now, and at long last, my small brain has made "click" and I understand (I think). In two weeks time, he is going to write the newsletter (he wrote the piece on dynamic proxies) and he will demonstrate how you can build up a sibling hierarchy of class loaders which you can use to automatically redeploy classes and find dependencies, etc. I didn't quite follow all of his explanations, so I'm looking forward to read what he has to say about this topic. In the meantime, to prepare us all, I've written a simple example to demonstrate how it works.

In JDK 1.2, SUN added a new approach to class loading which allows you to identify classes not only by the class name, but also by the context in which it was loaded. We can set the ClassLoader for a Thread, which we can then use to load classes. By having a different classloader, you are effectively constructing a new instance of Class, which is then completely different from other instances of Class. This was, I think, also possible in JDK 1.1, but it is much easier to make an instance of a ClassLoader in JDK 1.2 as there is now a URLClassLoader which you can point to a directory where it will load the classes from.

For example, say we have two directories, a1 and a2. In each of these directories we have a class A:

//: a1/A.java
public class A {
  public String toString() { return "This is the first class"; }
}
//: a2/A.java
public class A {
  public String toString() { return "This is the second class"; }
}

As you will agree, the two classes are completely different. They can have different method definitions, data members or access control. The normal way of using these two classes is to choose at compile/run time which one you wish to use. For example, we may have a class NormalTest below:

//: NormalTest.java
public class NormalTest {
  public static void main(String[] args) {
    System.out.println(new A());
  }
}

To compile this class, we have to specify the directory where A resides, either a1 or a2. Since the signature is the same, we can compile with one class and run with the other class if we want.

javac -classpath .;a1 NormalTest.java
java -classpath .;a2 NormalTest

would result in "This is the second class" being displayed on the console.

What happens if we want to have instances of both A classes in use at the same time? Normally we cannot do that, but if we use ClassLoaders we can.

//: Loader.java
import java.net.*;
public class Loader {
  public static void main(String[] args) throws Exception {
    ClassLoader a1 = new URLClassLoader(
	new URL[] {new URL("file:a1/")}, null);
    ClassLoader a2 = new URLClassLoader(
	new URL[] {new URL("file:a2/")}, null);
    Class c1 = a1.loadClass("A");
    Class c2 = a2.loadClass("A");
    System.out.println("c1.toString(): " + c1);
    System.out.println("c2.toString(): " + c2);
    System.out.println("c1.equals(c2): " + c1.equals(c2));
    System.out.println("c1.newInstance(): " + c1.newInstance());
    System.out.println("c2.newInstance(): " + c2.newInstance());
  }
}

The two classes, both called "A", are loaded with different ClassLoader objects, and are thus, as far as the VM is concerned, different classes altogether. We can print the names of the classes, compare the two classes (even though their names are the same, the classes are not equal, you should therefore never compare just the class names if you want to compare classes, rather use the Class.equals() method), and make instances of them by calling the newInstance() method.

The output if we run Loader is:

c1.toString(): class A
c2.toString(): class A
c1.equals(c2): false
c1.newInstance(): This is the first class
c2.newInstance(): This is the second class

We can also let these two "A" classes have a common superclass. For example, say we have a superclass called Parent.java located in the root directory:

//: Parent.java
public class Parent {
  public String toString() {
    return "Thanks for caring... but what do you want??? ";
  }
}

And our A.java classes are now written as:

//: a1/A.java
public class A extends Parent {
  public String toString() {
    return super.toString() + "This is the first class";
  }
}
//: a2/A.java
public class A extends Parent {
  public String toString() {
    return super.toString() + "This is the second class";
  }
}

We then need to have a common parent ClassLoader which we use to load the Parent.class file. We have to specify the location to start looking for Parent.class, and the locations are searched in a hierarchical fasion. Note that the compile-time Parent class is loaded with a different ClassLoader to the one loaded with the URLClassLoader called "parent" so they refer to a different class altogether, which means we cannot type-cast an instance of "A" to a Parent. Also, if we load the class "Parent" without using the classloader, we will see that it is not equal to the superclass of "c1".

//: Loader.java
import java.net.*;
public class Loader {
  public static void main(String[] args) throws Exception {
    ClassLoader parent = new URLClassLoader(
	new URL[] {new URL("file:./")}, null);
    ClassLoader a1 = new URLClassLoader(
	new URL[] {new URL("file:a1/")}, parent);
    ClassLoader a2 = new URLClassLoader(
	new URL[] {new URL("file:a2/")}, parent);
    Class c1 = a1.loadClass("A");
    Class c2 = a2.loadClass("A");
    System.out.println(c1.newInstance());
    System.out.println(c2.newInstance());
    System.out.println(
	c1.getSuperclass().equals(c2.getSuperclass()));
    System.out.println(
	Class.forName("Parent").equals(c1.getSuperclass()));
    try {
	Parent p = (Parent)c1.newInstance();
    } catch(ClassCastException ex) {
	ex.printStackTrace();
    }
    System.out.println("We expected to get ClassCastException");
  }
}
Thanks for caring... but what do you want??? This is the first class
Thanks for caring... but what do you want??? This is the second class
true
false
java.lang.ClassCastException: A
        at Loader.main(Loader.java:18)
We expected to get ClassCastException

Note that the super classes of both "A" classes are equal.

Where is this all this useful? It is very useful when you have an application server into which you want to deploy business objects written by different people. It is entirely feasible that you have two developers with different versions of classes deploying their applications onto the same server. You don't necessarily want to start a new VM for each deployment, and so with ClassLoaders it is possible to have lots of classes with the same name running in the same memory space but not conflicting with one another. They would also share the common JDK classes with one another, so we would not have to have a java.util.ArrayList class loaded for each of the ClassLoaders.

Until next week, and please remember to forward this newsletter in its entirety to as many Java users as you know who would be interested.

Heinz

Language Articles Related Java Course

Extreme Java - Concurrency and Performance for Java 8
Extreme Java - Advanced Topics for Java 8
Design Patterns
In-House Courses

© 2010-2016 Heinz Kabutz - All Rights Reserved Sitemap
Oracle and Java are registered trademarks of Oracle and/or its affiliates. Other names may be trademarks of their respective owners. JavaSpecialists.eu is not connected to Oracle, Inc. and is not sponsored by Oracle, Inc.