How can you create flexible enums with different behaviors and associated values?

Written by e.rajasekar | Published 2018/04/27
Tech Story Tags: java | kotlin | programming | enum | computer-science

TLDRvia the TL;DR App

Power of Kotlin’s sealed classes

Enums are great to group objects with similar behavior. They are also efficient because only one instance of them will get created. However, it’s hard to implement enums for classes that has slightly different behaviors. Let me illustrate with an example.

Example: Stats Calculator

Let’s say we want write a Stats Calculator to compute mathematical statistics for list of values for eg. SUM, COUNT, AVG, QUANTILES . Let’s first define an interface.

Now we can easily represent different statistics as Enums like this:

We can use it like this:

Easy enough, but it becomes hard once want to support QUANTILE stat which needs to store percentile to compute in enum instance. For eg. QUANTILE(90).calculate(values) should compute 90th percentile of values.

Problems with implementing enums with different behavior

  • Since Java enums has to have same fields, We have to add percentile field to all enums although its irrelevant for AVG, SUM, COUNT.
  • We don’t know the percentile values ahead of time to statically create instances of Quantile. So they can’t be enum anymore.
  • Once we convert them to regular classes, we will lose it’s default singleton behavior.

We basically want simple statistics AVG, SUM, COUNT as singletons, but QUANTILE as dynamically created class for a given percentile value. We have to write lot of boiler plate code to support this difference in behavior, he is one way to do it.

That’s lot of work.

Kotlin’s Sealed classes to Rescue

Kotlin’s has powerful sealed classes to easily solve use cases like this.

Let’s first understand what a sealed class is:

Sealed classes are used for representing restricted class hierarchies. They are, in a sense, an extension of enum classes.

A sealed class can have subclasses, but all of them must be declared in the same file. A subclass of a sealed class can have multiple instances which can contain state. You can declare the subclasses inside the sealed class or outside but they always have to be declared in the same file.

A sealed class is abstract by itself, it cannot be instantiated directly and can have abstract members.

Sealed classes are not allowed to have non-private constructors (their constructors are private by default).

Now, we know about Sealed classes, let’s see how we can use sealed classes to implement StatsCalculator

Let’s create a sealed Stats class and sub classes for each Stats like this.

Kotlin supports object declaration to create Singletons in single line. Language itself supports defining exactly what we want:

  • The simple stats AVG, COUNT, SUM are made as singleton using object declaration.
  • The QUANTILE is defined as regular class to store percentile value.
  • All of these classes grouped together in sealed base class Stats.

Now, the calculate method can be implemented clear and concise way.

The code is not only concise, the beauty of when expression helps us avoid potentials errors at runtime.

Since when expression is used to directly return value, if we add a new stats say MAX extending Stats but forget to update calculate method, compiler will throw error.

when’ expression must be exhaustive, add necessary ‘Max’ branch or ‘else’ branch instead.

This is possible with sealed classes because all subclasses are declared in same file, so compiler will know all possible values.

When are Sealed classes useful?

The idea of Sealed classes isn’t new. The sealed classes allows us to easily work with Algebraic data types. The similar features are available in other languages like

So we can use Kotlin’s sealed classes to solve those problems that requires Algebraic data types.

If you don’t have luxury to use Kotlin, you can check out Spotify’s data enums to do in plain old Java.

Update on 12/03/2018:

The code examples are updated to use Doculet.


Published by HackerNoon on 2018/04/27