Dynamic Class Loading

COMP 310    Java Resources  Eclipse Resources

Note: The provided Ballworld package includes utilies to perform dynamic class loading, so in general it is NOT necessary to implement the code below!

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:

  1. Class.forName(className) : Load the given class, which is specified using its "fully qualifed name", i.e. the name includes its packages, e.g. "package1.subpackage.MyClass".   The object returned by this call is an object that represents the class specified by the String classname.
  2. .getConstructors() : get an array of all the constructors of the class.   Since the actual type of the class is unknown to the rest of system at compile time, a constructor for an unknown type is designated as type "java.lang.reflect.Constructor<?>" .
  3. new Object[]{startLoc, startRadius, startVel, startColor, theCanvas} : an array of input parameter values for the constructor is created.  These values are whatever is appropriate for the soon-to-be created ABall instance and will depend on the specifics of your system.   The code above just has dummy variables inserted for illustration purposes--you will probably want to pass them as input parameters to loadBall().
  4. (ABall) c.newInstance(args)  : The constructor is called to create an instance using the array of input parameter values.    The resultant Object that is returned is then typecast to its superclass, ABall, which is always possible, if className is a subclass of ABall.

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.

Making a better dynamic class loader

The above code can definitely be improved in a number of areas:

These are all doable but unfortunately, the code becomes somewhat involved due to two bugs and one annoyance in Java:

  1. 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.
  2. 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.
  3. Given 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