Java Callbacks Ruby Style
Java (pre version 8, anyway) doesn't have proper anonymous functions1 for things like callbacks. Instead, the Java Way is to create an anonymous subclass implementing the behaviour you care about in an overridden method, then passing that instance to whatever needs it. So you see a lot of stuff like this:
addListener(new ChangeListener() {
public void changed (InputEvent event, Actor actor) {
do_a_thing(event, actor);
}
});
ChangeListener
is an abstract class in LibGDX implementing a bunch
of callback hooks; this snippet passes addListener
an anonymous
subclass that overrides one of these callbacks and does what I want it
to.
The Ruby Way, on the other hand, is to use blocks. This gives you the same power but requires less code. The equivalent (hypothetical) Ruby framework's interface might look something like:
addKeyDown {|event, keycode| do_a_thing(event, keycode)}
So this becomes jarring when you wish to call Java APIs from JRuby, as I did when writing a game in JRuby.
Initially, I just bit the bullet and wrote (non-anonymous) subclasses to hold the callbacks. Soon though, the number of callbacks got larger than my personal threshold (two) and I decided to try something else.
My first attempt was to create anonymous Ruby subclasses, which is fine but not really compact:
addListener(Class.new(ChangeListener) {
def changed(event, actor)
do_a_thing(event, actor)
end
}.new())
Another downside--and this is a bit more subtle--is that local
variables defined in the method calling addListener
are not visible
to the new class. For example, this:
def listenForThing(someEvent)
addListener(Class.new(ChangeListener) {
def changed(event, actor)
do_a_thing(event, actor) if someEvent == event
end
}.new())
end
won't work; changed
can't see someEvent
.
There had to be a better way--this is Ruby, after all! And there was.
Ruby provides the method define_method
, which takes a block and name
and turns it into a method which is then added to the class. I added
a helper function to do the work:
def newListener(klass, name, &block)
c = Class.new(klass)
c.send(:define_method, name, &block)
return c.new
end
and used it like this:
addListener(newListener(ChangeListener, :changed) {|event, actor|
do_a_thing(event, actor)
})
This is almost Ruby-like in it's compactness, but there's one subtle quirk.
The block has redefined self
to be the owner of the new method (the
sub-instance of ChangeListener
created by newListener
), so the
method do_a_thing
the block is trying to call should actually belong
to that, not the class which called newListener
in the first
place.
This isn't what most people expect when they see a block in Ruby, and so could lead to all kinds of unpleasant surprises. It also optimizes for the less-common case and is kind of unwieldly to work around:
outerSelf = self
addListener(newListener(ChangeListener, :changed) {|event, actor|
outerSelf.do_a_thing(event, actor)
})
Fortunately, this is easy to fix. Instead of turning the block into the method body, I just define the new method as a simple one-liner that evaluates the block:
def newListener(klass, name, &block)
c = Class.new(klass)
c.send(:define_method, name) { | *args| block.call(*args) }
return c.new
end
This way, the block keeps its old self
value and everything works as
expected:
addListener(newListener(ChangeListener, :changed) {|event, actor|
outerSelf.do_a_thing(event, actor)
})
And now, we have Java callbacks that are almost as easy as their Ruby equivalents.
-
And you could argue that Java 8 doesn't have them either. ↩
# Posted 2016-03-05 22:49:09 UTC; last changed 2016-03-13 18:57:21 UTC