Thursday 17 October 2024

Create Class Instance given a String with its Name

Reflection has always fascinated me, from basic introspection to creating code at runtime, to more crazy ideas like modifying at runtime how the language behaves (how loops work, how inheritance works, how attribute lookup works). The other day, working with Python, I needed a not particularly unusual reflective feature, create instances of a class which name we have in a string. Think of a plugin system, you don't know at development time which plugins you'll have available, and at runtime you are provided with the name of a particular plugin class (a class that someone has made available in the modules search path) that has to be instantiated.

This is a pretty easy task. Classes live inside modules, so the first step is loading (if it's not already loaded) the corresponding module for that class. Once we have a reference to the module we can access to the class object using getattr() and invoke it (classes are callable) to create an instance.



# we have  dblib.oradb.py module, with an OracleConnector class in it
full_class_name = "dblib.oradb.OracleConnector"
parts = param["param_type"].split(".")
mod_name_parts, class_name = (parts[:-1], parts[-1])

# load module
mod = importlib.import_module(".".join(mod_name_parts))

# retrieve a class object
connector_cls = getattr(mod, class_name)

# invoke the "constructor" with the corresponding parameters
connector = connector_cls("geodata", "user1")

# obviously the module is now loaded in sys.modules
mod == getattr(sys.modules, "mod_name")
# True

If we know that the module for the class is already loaded and with the class added to the global namespace, we could obtain a reference to the class using eval(), but that's not a particularly good approach. I mean:


from dblib.oradb.OracleConnector import *

class_name = "OracleConnector"
connector = eval(class_name)


What about Kotlin (for the JVM) / Java? There's not an extra syntax or library in Kotlin for this, so it's just the same as in Java. While in Python modules are the "loading unit", in Java that "loading unit" is the class (classes are loaded from the file system by the Class Loader as needed). The equivalent to the 2 steps process in Python: load module and retrieve class object, is a single step in Java, get a Class object by calling Class.forName(classNameString). The next part is more complicated in Java than in Python. Python classes are callables, so we just have to invoke the class object to obtain the class instance. In Java we have to obtain the constructor function from the class, and the thing that I had almost forgotten is that due to overloading we can have multiple constructors (or multiple methods with the same name), so to retrieve the correct constructor we have to provide the types of the different parameters that we are going to use in the ensuing invokation. The main idea is this (simplest possible case, as we are using a parameterless constructor)



val classToLoad: Class = Class.forName(classNameString)
//simplest possible case, just get a parameterless constructor
val ob = classToLoad.getDeclaredConstructor().newInstance();


And now a more complex/verbose case using a constructor that expects parameters (taken from here)



class MyClass {
    public MyClass(Long l, String s, int i) {

    }
}

Class classToLoad = Class.forName(classNameString);

Class[] cArg = new Class[3]; //Our constructor has 3 arguments
cArg[0] = Long.class; //First argument is of *object* type Long
cArg[1] = String.class; //Second argument is of *object* type String
cArg[2] = int.class; //Third argument is of *primitive* type int

Long l = new Long(88);
String s = "text";
int i = 5;

classToLoad.getDeclaredConstructor(cArg).newInstance(l, s, i);


I think I had never thought until today about the fact that though Kotlin supports method overloading we use it less frequently than in Java, and that's just because optional parameters eliminates some of the cases where we would be using method overloading.

No comments:

Post a Comment