Dynamic Class Loading |
COMP 310 Java Resources Eclipse Resources |
Dynamic class loading enables classes to be loaded into the Java execution system at run-time. This allows new features and capabilities to be added to an application while it is running.
Dynamic class loading utilizes a feature of Java called "reflection", which enables one to treat variable types as objects, thus allowing them to be manipulated at run-time. In reflection, there are classes that represent classes. The instances of those classes are thus objects that represent types. There are also classes that represent methods and constructors. Below, an object representing the desired class is instantiated (an object of type Class) then an object representing the constructor is then retrieved from that Class object. That constructor object can then be used to create a new instance of the class associated with that constructor. This "programming the program of a program" which reflection enables is called "meta-programming".
Notes:
The following code returns an instance of a subclass of ABall by dynamically loading the given class name and then calling its constructor. (Change the return type and cast statement to suit the desired return type for your system!):
/** * The following method returns an instance of an ABall, given a fully qualified class name (package.classname) of * a subclass of ABall. * The method assumes that there is only one constructor for the supplied class that has the same *number* of * input parameters as specified in the args array and that it conforms to a specific * signature, i.e. specific order and types of input parameters in the args array. * @param className A string that is the fully qualified class name of the desired object * @return An instance of the supplied class. */ private ABall loadBall(String className) { // YOUR CODE MAY HAVE MORE/DIFFERENT INPUT PARAMETERS! try { Object[] args = new Object[]{startLoc, startRadius, startVel, startColor, theCanvas}; // YOUR CONSTRUCTOR MAY BE DIFFERENT!! The supplied values here may be fields, input parameters, random values, etc. java.lang.reflect.Constructor<?> cs[] = Class.forName(className).getConstructors(); // get all the constructors java.lang.reflect.Constructor<?> c = null; for(int i=0;i < cs.length; i++) { // find the first constructor with the right number of input parameters if(args.length == (cs[i]).getParameterTypes().length) { c = cs[i]; break; } } return (ABall) c.newInstance(args); // Call the constructor. Will throw a null ptr exception if no constructor with the right number of input parameters was found. } catch(Exception ex) { System.err.println("Class "+className+" failed to load. \nException = \n"+ ex); ex.printStackTrace(); // print the stack trace to help in debugging. return null; // Is this really a useful thing to return here? Is there something better that could be returned? } }
The above process does the following:
The try-catch block is necessary because there is a possibility that the requested class does not exist or that the constructor call is incorrect.
It turns out that if one simply mis-capitalizes a class name, i.e the letters in the name are correct, but the capitalization is incorrect, Java's dynamic class loader will throw a NoClassDefFoundError error, which is an Error, not an Exception. Exception is not a sub-class of Error or vice versa in Java. There are two ways to deal with this issue:
Note: Technically, the above code will work even if the classes have multiple constructors. The restriction is that there can only be one constructor with the desired number of input parameter.
The above code can definitely be improved in a number of areas:
ABall
.
These are all doable but unfortunately, the code becomes somewhat involved due to two bugs and one annoyance in Java:
Class.getConstructor(Class<?>[] typeParameters)
--
This method would seem to be perfect for finding the constructor we want,
except that the implementation of this method requires that the supplied
array of type parameters exactly match the formal input types of a
constructor. This means that if one supplies a subclass of the
formal type parameter for a constructor, this method will not find
the constructor for which an input argument set of those type parameters
would actually work. This forces us to manually search for
the appropriate constructor.Integer.TYPE.isAssignableFrom(Integer.class)
returns
false
even though int x = new Integer(42)
is a perfectly
valid statement -- Integer.TYPE
is a Class
object
that represents the type of an int
parameter (similar fields
exist for all the other primitive wrapper classes, e.g.
Character
, Double
, Byte
, etc.).
aClass.isAssignableFrom(anotherClass)
is supposed to return
true
if anotherClass
can be assigned to a variable
of type aClass
. Due to Java's
auto-boxing/unboxing of primitives, this is clearly true for all primitives
and their wrapper classes but isAssignableFrom
returns an
incorrect result in these cases. To get around this, one must
substitute the wrapper class whenever the primitive class is encountered.Integer.TYPE
as a Class<?>
variable, it
is impossible to deduce that its wrapper class is Class<Integer>
-- While one can use Class.isPrimitive()
to figure out if the
type is a primitive, Java not supplying any methods to enable one to figure
out what the corresponding wrapper class is. This is most likely
a consequence of Java's type erasure wherein the generic typing used by
Class
used to represent the primitive is lost at run-time. This is needed to
get around the bug #2 above. Thus, one must manually build
a lookup table (dictionary) that maps every primitive type to its
corresponding wrapper class. This unfortunately still leaves out the problem of "narrowing" when choosing the appropriate constructor to use for a set of input parameters. Narrowing where the Java compiler automatically and preferentially selects the constructor with the most narrowly defined (i.e. lowest subclass) types that are valid for the given input parameters. It can be a non-trivial process to determine the most narrowed constructor to use and is fraught with potential logical dilemmas when there are more than one equally viable choices.
It is however quite possible to write an improved version of the dynamic class loader that fulfills all of the above listed deficiencies though without the narrowing capability. Please see the course staff if you would like to learn more about how to do this.
© 2020 by Stephen Wong