Want this course?
Creating Java classes with gen-class
Course: JVM Fundamentals for Clojure
Clojure comes with a set of tools for creating Java classes (compiled to
.class files) using pure Clojure. It's called
gen-class. You can add
:gen-class directives to your namespace declarations.
Hello. Today we're gonna be looking at Gen Class , which is a way, it's a set of tools lets say for creating java classes but totally in closure. It is not really a replacement for using Java directly, but in a lot of cases, it is exactly what you need and can give you a way to generate just exactly what you need without having to write any Java. You probably still want Java for more complicated stuff, it's not a replacement for Java, it is simply a set of tools for turning your closure code into something that can be used from Java. So I'm gonna go over the options that come with the Gen Class and there's some common options and then there's some uncommon options, and then at the end we'll go through a few different cases like use cases for when you would actually use a Gen Class.
Okay, so this is the Gen Class that goes in the NS declaration. It starts with Gen Class and then has all the options listed. Now you can just do Gen Class with no options and it will compile something to a class file and that's great, it will use all the defaults, 'cause most of the options have defaults, but sometimes you want to set stuff up. Okay so the first option is name, so Gen Class will make a class file in you know your class path, and sometimes by default it will use your name space name. So this class would be called number, of course it's gonna translate the hyphen to underscore, so number underscore cruncher dot core is the name of the class with the lower case C.
Now if you're gonna use this that's fine, that's a fine class name in Java, but there's a social convention in the Java world that classes are supposed to start with a capital letter. So if you want this to be consumed by someone outside of the closure world, you're probably going to want to use a name and give it a name, and notice I'm using this other convention of using your domain name as kinda like a prefix so that you don't have name collisions.
Okay so this is the fully qualified package name, and that the, that's where it's gonna get put, that's where the class file's gonna get compiled to. Okay lets look at some other options. Right here I have implements iterable, that's a vector with iterable in it. So I can put multiple interfaces, just like in Java you can inherit from multiple, implement multiple interfaces with one class, you can put a whole list of stuff in here. And notice I don't have to say Java dot lang dot iterable, I can just do iterable. All the other packages you need to have it completely specified. Now I can also extend so I can say extends and then put a single class because in Java class inheritance is, you know there's only one parent, so I could put something like, well I'm gonna say, what class could it be, Java dot util dot list, let's just say that I want to implement list.
Now that's a, let's array list, just to make sure it's a class. So I can extend this class from Java dot util and you'll notice I do have to specify it explicitly. Then there's, I'm gonna go through init and state in a little bit, they're a little more complicated and they go together, but there's methods, so obviously a Java class can define new methods not just the ones that come from the super class, or from the interfaces, so this one has a method called average and it takes a number, it's got one argument, it just takes a number and it returns void, so it doesn't return anything. And we notice this is called average, it doesn't call this function, it actually calls this one, because this one has the hyphen, the prefix, and we'll see how you can change the prefix, but the default is just a hyphen.
Okay now notice I do implement, here I'm gonna remove this extends, cause I think that's pretty clear, but if you implement or extend from something, you're gonna have these methods from the superclasses or the super interfaces that you'll want to override or implement, but you don't have to specify them here in the methods, okay they're just implicitly there. So if I go down here we see iterator which is a method on iterable. So you would call X dot iterator and it would give you an iterator.
So I have to, I mean I want to specify what this does, so I put it here and it's gonna take the this, just like average took this, and that's the same as in Java, you just have to be explicit, that it's always the first parameter. Okay and it will figure out the argument types and everything, it'll make that for you in the Java class, and it's just a closure function, but closure can based on this name, because it's the prefix and then the name of the method, it can figure out that that's the one you want to call.
Okay you can set the prefix however you want, the default is hyphen, okay, but you can make it something like numbers hyphen, but then you'd have to go down here and use numbers, and put numbers here, right, numbers. So you might want to do that if there is a confusion or something, but the default is fine most of the time. So I'm just gonna change it back to the default, okay, now this class as it is now, as we've seen all the stuff that we've seen, it doesn't have any initialization. Often with a class you need to pass it arguments and they need to, and it has to set up itself, it's initial state, okay so what we need to do is tell it how to do that, and closures Gen Class has stuff just for that, okay now it's a very functional and odd way of doing it, you don't just define the constructor and then have instructions in it. It's an interesting way of doing it, it's kinda complicated, so I'm gonna try to explain it.
The first way is that you use init, okay so we have init specified here, that this is the name of the method, now of course it's gonna add the prefix, so we're gonna go down here, and we see that init returns a vector of two things. What it has to, it's a pair, it's returning the arguments for the constructor, right and this one, for the superclass constructor sorry, the arguments for the superclass constructor.
The superclass for this is object because we don't specify any of them, using the extends, so since we don't say extends obviously it's gonna extend from object, that's just how Java works. So the arguments to the object constructor are nothing, it's empty. And then the state of this object, and I'm using a map that contains an atom so I can change the state, cause it's immutable, a map is immutable, and a channel, okay so it's an atom and a channel, I named them so I can access them later. Now where does it put the state, that's what this is, so this is defining the state option, it's defining a public accessor on this class. So a field on the class, or on the object. And you can see that I use it right here.
Alright so when I'm calling average, the average method, because it starts with a prefix, it's gonna get the value of state from this, it destructures it and then it calls the average function which is actually over here. Okay so this state is all the state I need because it's a map, it's got everything I need in it, okay, so you only get one of these, you only get one field. So this init is what initializes so this is the initial value here.
This init is going to be called when the constructor is called, and its going to let you specify arguments to the super constructor so that super constructor is going to get called with whatever I put in here, so I could put in one, two, three, and those are the three arguments to the super constructor. Some classes, some when you extend the class, you have to call one of the super constructors and this specifies which one, based on how many arguments it has, the types of the arguments, et cetera. Okay that is just for initialization, you can give it constructors, you can say "I'm going to define new constructors for this class."
Okay so the constructors option is it's actually really simple, but it's hard to explain in words, you're mapping the types of this constructor to the types of the super constructor. So if I wanna make a constructor on this number cruncher class, that takes one argument, lets say a number, but I wanted to call the super constructor with two arguments, I can do this. Okay so this is a map, and this is the key, and this is the value, maybe we should put it like that.
Okay now if they call the zero argument constructor, I actually also want to call the two argument super constructor. But if they call this constructor with a string, the two strings, I wanna call the super constructor with one string. You see you can do any combination here, and it's kind of very flexible and it's kinda not how you tend to think of it when you're programming in Java but it's separated out the choice of super constructor from the initialization, you can think of it that way, because this init is still gonna get called, and notice now we have a constructor with one argument and our init takes zero arguments, so we're actually going to have to change that, and you can change it like this, you can just say, I'll use arrest, and ill call it args.
Okay you could also do like this, so the zero argument one, now look what I'm saying here is the zero argument one has to return two, to call that super constructor, so I need to put one, two like that, two numbers, and the one argument constructor has to return two numbers so this is the one argument constructor, and it's gonna return the same, and then the two argument constructor, A B, has to return what? The two argument constructor returns one string, not numbers, so I'll just say hello.
Okay, so you notice that we now had to modify our initializer to deal with the different arguments, the numbers of arguments. Okay so this will override this constructors will override the default constructors. Alright so sometimes you want to have something run after the super constructor's run. Okay so this init is called obviously before because it sets up the super constructor arguments, it also sets up the state, but you want something to run after so I can do an option called post init, and we'll just call it post init two, and this will run after, and sometimes you need to do that because you're relying on something from the super constructor being set up by calling a constructor and then you wanna like modify it or know what it is. And so it's gonna take a this, and then some args, so A B C.
This one, the return value is totally ignored because it's just called for its side effects, oh sorry, and these args, I'm sorry, I'm just being very vague, these args are the same args that get passed to your constructor. Alright so in this case we might wanna do just args like that, 'cause if we wanna ignore how many come back, because it will be the same as this, right it will be zero, one, or two.
Okay so then you can do whatever you want, you know, you can say print this, I don't know that's not, to string this will print that, alright so it's called for its effect and you just wanna see what it looks like as a string. I don't suggest doing that, this is just an example of what you might wanna do. Alright, now I think we've gone through all of the regular options, the common ones.
Okay that let you map constructor et cetera. There's a couple of uncommon options that I'll just go over quickly. So you can actually define that you can specify that the name space that actually implements this class is a different one. So this is some other name space, notice the init and the post init, and the average are all defined in the current name space, the one that we're in here. They're all down here. You can say actually look in this name space. If you want to separate them out for some reason.
Okay but I don't want to. Also I'm gonna get rid of the constructors because they're kinda confusing, and post init we don't need. Okay there's another uncommon option, which you can say I want to load the name space, okay so the default is true, you probably don't wanna turn that off, I don't know why you wouldn't want to turn it on, so what happens is when this class is statically loaded, the name space will get run so that all these functions are defined. And you might wanna do that yourself for some reason. But you usually don't, you just wanna let it load it. So I'll remove that.
There's a thing called exposes which lets you expose, like normally in Java if you extend a class there are some protected fields on that class that normally you would get to access, but because these things are running in the closure space, they're not actually in Java, you don't get access to those protected things, and these are like protected fields, right? So you could actually define getters and setters for those things, so lets say there's a protected field called list, so I'd say list, I actually want a getter called get list, say get list set list, alright and then that will let me, this defines like getters and setters on it. A getter called get list and a setter called set list. And these are public so now you can use them anywhere.
Again you know this is very practical and a pragmatic way of getting at that data that is otherwise not possible from closure, it'll just set up these things for you. Now the thing is you can also do, and there's some protected methods, so that was for fields, you can do exposes methods, and you have the same thing. So let's say you have a method called calculate length, and you just wanna get access to that, well you can make a new one called calculate length, Right? And it will make a new method.
Actually you should be careful because this might get confusing because this is a method, so it has to follow the Java conventions for method names and field names. The first thing we're gonna go over for an example of using Gen Class is to have a main method, and in Java on the JVM, the main method on a class is what tells it that it can run that class itself so running it on the command line or as part of an uber jar deployment, you need a main somewhere, so we want to set that up and you need to use Gen Class because it generates a class file. So we're gonna use Gen Class , and really it's simple, all you need is to define a main method, and it's gonna get a list of the arguments as strings, so whatever command line arguments I mean, are gonna be passed directly to main, so we can do an at args, and so we can accept any number of arguments including zero, and then you know we do stuff.
Okay and this is all you need to set up the Gen Class , you will probably want to go in to your project and first of all AOT compile it, and ahead of time compile it so number cruncher dot main, and then also specify that the main for this is in number cruncher dot main. This is going to tell lein again what to run when you type lein run, or what to set up as the main class in the executable jar you create. And there's other lessons on how to set up an executable jar and stuff, but this is a lein again thing as a configuration for setting stuff up. But this one says yes we're gonna compile that. And we can actually see the compile, let's go to our terminal, so we see that there's no target, cause I cleaned it, and we do lein compile.
Uh oh, I have an exception. Field, oh right, because I changed it, remember I kind of messed up. Yeah, there's no list and there's no calculate, okay, let's go back to the terminal, and we'll try again. There we go, so now we see we have a target, and I'm gonna do tree on target. Woah, that generated a ton of stuff. 1740 files, well obviously that is not gonna be easy to tree, so let's go to classes, alright so here's, we're exploring what this Gen Class does. I have com and I have lisp cast, and in there we have the number cruncher class. Which is the same name that we set up here.
So even though it was compiling number cruncher core we set the name to number cruncher. That's good. Okay so let's get out of this one and let's go to number cruncher and see what's in there. So it still has a core, and notice we talked about this before, it's generating a class for every function we have an average, and these are the ones with the prefix that hyphen, it got changed to an underscore. So all of these functions have their own class. But notice it also has this loading and this init.
Okay these are called by, this is the initializer for that whole, like what happens when the thing gets loaded. Right it sets up all the vars, it does you know any initialization that, and it runs any code that's at the top level of your CLJ. Okay and notice also that it makes that main class, because when we go to main, we notice we have not set the name of the class that gets generated, so it's the default, and the default is just the name space name.
Alright now we're going to look at how to create a custom exception in closure, you know when you're looking at a Java library, a typical Java library, they often define new exceptions that are custom to the kind of errors that, you know, whatever library you're using is gonna throw, and if you want to be able to do that Instead of just always throwing a run time exception, you want to define a new type of exception, you can do that with Gen Class, so let's make a new name space called exception, and we're going to Gen Class it, Gen Class.
Now the first thing is this class has to extend Java dot lang dot exception, cause it's an exception, and we probably wanna give it a name like com dot lispcast dot silly mistake exception okay? And that's really all you have to do. The reason is all the constructors and everything from exception are going to be there. They're all gonna work. So that default one where it takes a message, that one is there. Also the one that takes another throwable that you call the cause, that one is there too.
So this is all you need to define a custom exception type. Of course you'll wanna put it in your project, and make sure it gets ALT compiled number cruncher dot exception, and we can test this out by going to the shell. The final example is how to wrap a closure API in a custom Java class so that it's callable from Java, and we already saw that there's actually a whole lesson on it. The lesson is called "Creating a closure library for use in Java."