Simplifying class matching with java 8

I’m knee deep in akka these days and its a great queueing framework, but unfortunately I’m stuck using java and not able to use scala (business decisions, not mine!) so pattern matching on incoming untyped events can be kind of nasty.

And while that technically works, I really hate it because it promotes a monolothic function doing too much work. It also encourages less disciplined devs to put logic into the if block. While this is fine for a few checks, what happens when you need to dispatch 10, or 20 different types? It’s not uncommon in actor based systems to have lots of small message types.

Also, because akka gives you your object as a type erased Object you can’t use normal dispatching mechanisms like overloaded functions. And to complicate things even more, you can’t really use a visitor pattern without conflating business logic into your data access objects.

In reality, all I want is that my receive function should act as dispatcher, doing the right type checking on your object and executing a function that does the specific work. Now I can write small functions for each particular message type and keep that logic encapsulated and composable.

Thankfully with java 8 and lambadas we can create some simple combinator style executors that let us do stuff like this:

Performance

Reddit seemed to be obsessed about the perf costs with this implementation. I spun up JMH and gave it a whirl to compare the cost of this vs if statements.

You do pay a small performance penalty for this higher level abstraction but I think its a small price to pay. Almost any higher abstraction pays a penalty of some sort. In JMH benchmarking methods that used an if tree took a pretty constant 20-50 nanoseconds to complete, and ones using a matcher took about 2-4 times longer (around 90 nanoseconds for a matcher of 4 cases).

Caching an instance of your dispatcher cut the perf time in half, and when you build out lots of match statements it makes a more noticable difference (20 match statements recreated each time was 500 nanoseconds and cached it was 150 nanoseconds). Downside to caching is that you can’t close over anything and create adhoc functions inline, but if you use it as a pure dispatcher then caching is fine.

Or simply define consumer as a lamba invoking reflected method.
(So that you implicitly define a unique proxy with minimum overhead)
(But prefers MethodHandler over Method because of underlying jvm optimisation)

You could cache unbound configuration by reflected class,
and return configuration bound to visitor instance.