java.lang.Class is probably one of Java’s most famous classes, and whether you write applications or frameworks, you have probably encountered it many times, either superficially (getClass()) or in more depth (reflection, class loaders, etc…).
java.lang.Class is also a very special class in the Java ecosystem, and as such, it is limited in some very specific ways. One of them is that developers can’t create a new Class object themselves (the class has no public constructor), only the JVM can create Class objects. When you are writing Java code and you want to materialize some of these objects, your options are fairly limited:
- Create a class loader and use the defineClass() method. Class loaders are complex and require a lot of intimate knowledge of the JVM to get right, and defineClass() also requires you to supply the bytecode for the class you want to create.
- Code generation. This is the more traditional approach. It’s easier to achieve in some ways (all you need to do is generate the source of the class you need) and more complicated in others (you need to infect your build with the knowledge of these generated classes and teach it how to bundle them with your application).
- You can use dynamic proxies. Dynamic proxies don’t exactly materialize new classes, so they shouldn’t really be part of this list, but I thought I’d mention them since they can be a pretty powerful solution to the overall problem of generating new types in Java programs (note that this approach is less typed than the other two).
Despite their shortcomings, all these approaches have been used with great success by various Java frameworks, and these techniques played a great role in making Java such a strong platform that remains able to solve most of the engineering problems that arise these days.
Having said that, what can we do to improve on these approaches?
Imagine a new type called IType. This is actually an interface, so any class can implement it. Let’s now assume that everywhere in the JVM where a Class is expected, we can use an IType instead (obviously, we’ll make Class implement IType). IType is the new representation of a class inside the JVM, and any object that implements it can be passed instead of a Class.
Obviously, an IType needs to be able to perform all the operations that Class can, among which:
- Return information about its name, package, parent type(s), nested types, etc…
- Respond to standard introspection operations, such as returning the list of methods that it exposes.
The methods returned by the IType need to be abstracted as well, so instead of returning java.lang.reflect.Method, we return a new type called IMethod. We place the same requirements on IMethod that exist on Method: introspection must work on these objects and, more importantly, we need to implement invoke(). If the object we are returning is an instance of Method, implementing invoke() is trivial (just delegate it to the underlying Method object).
But what happens if, instead of returning standard Java Class and Method objects, we decide to return something different?
Actually, what could these other objects be?
As it turns out, anything. They could represent files: .xml obviously, but how about .xsd or .properties? Or how about sources of another language? Maybe a .groovy, .bash or .scala? Thinking further outside the box, how about concepts that are not files, such as database tables? Or virtual files, or files present on a remote file system? How about the endpoint of a network connection (and we don’t care what’s feeding this endpoint)? We could also decide to represent the results of a REST or AJAX or JSON API.
This new type system doesn’t care what the underlying concept actually is, all it needs is a set of ITypes (and a few other details that I’m glossing over), and it will expose these types to the JVM as if they were genuine classes. Whether they are backed by Java bytecode or some other logic is irrelevant.
Here is a quick example of what such a type might look like.
Let’s suppose we want to make Java property files first class citizens in this new type system (it’s a silly example since a simple hash table is good enough, but this is just for illustration). The idea is that whenever we encounter a file called “foo.properties”, we’ll create a class called FooProperties and each key/value found in this file will be represented as a getter. For example, loading this file in this new type system:
# File: host.properties host: 127.0.1.0 port: 1245
will allow you to write the following code:
HostProperties fp = PropertyTypeLoader.load("host.properties"); System.out.println("Host:" + fp.getHost() + " port:" + fp.getPort());
The implementation of the corresponding IType and IMethod interfaces is pretty straightforward in this case, but this should give you an idea of the flexibility of such a type system.
What I have just described is basically Gosu’s Open Type System. Gosu is not just “yet another JVM language”, it also enables the kind of type openness that allows you to materialize pretty much anything as a real Gosu type. Once you have described your type, it becomes a part of your Gosu program and as such, benefits from the same privileges as any other type (e.g. it can be used by IDE’s for completion, browsing, refactoring, etc…).
Having said that, let’s now put things in perspective:
- Java’s metaclass design is intentional: it was conceived this way for security reasons, the idea being that types materializing in your JVM process need to go through a very specific pipeline so it can be validated and checked against malignant attacks. I am guessing that this system is extremely paranoid because Java was initially driven by applets, but I wonder if such concerns are still valid today.
- While elegant, Gosu’s open type system is basically just saving you from a code generation phase.
I’m guessing that not everyone will think that the benefits from such a type system outweigh the drawbacks, but I’d love to hear some feedback on whether you think that future languages should give access to such functionality or if it’s just a nice theoretical toy whose practicaly is doubtful.
Here is some additional material on Gosu’s Open type System: