Kotlin and Java EE Part Three - Making it Idiomatic
Converting Java EE application to Kotlin started with the battle with the framework, where we successfully outmaneuvered all the obstacles presented by sometimes archaic standards. In the process, the code was enriched with modern, Kotlin-specific constructs making it concise and safer.
If you did not read the previous two parts of the series, you can find them here:
- Kotlin and Java EE: Part One - From Java to Kotlin
- Kotlin and Java EE: Part Two - Having Fun with Plugins
After briefly revisiting already made changes, I will add some final touches.
What We Already Did
Many constructs from the previous two parts are already idiomatic Kotlin. Let’s take a look at set definition:
private final Set<Class<?>> classes =
new HashSet<>(Arrays.asList(KittenRestService.class));
As Java does not support a simple construction of Set and some other collections from a list of objects, we have to go to Arrays
class to create List
(!), and then we convert it to Set. In Kotlin, it becomes:
private val classes = setOf(KittenRestService::class.java)
We also converted Java Beans to Kotlin data classes making them much shorter in the process. We got rid of all getters and setters, and got equals()
, hashCode()
and toString()
for free.
@Entity
data class KittenEntity private constructor(
@Id
var id: Int?,
override var name: String,
override var cuteness: Int // set Int.MAX_VALUE for Nermal
) : Kitten {
constructor(name: String, cuteness: Int) : this(null, name, cuteness)
}
Thanks to compiler plugins, we could fake immutable objects without a parameterless constructor:
@Path("kitten")
class KittenRestService
@Inject constructor(private val kittenBusinessService: KittenBusinessService) {
lateinit
keyword made handling of values initialized by framework a bit easier, as we could avoid unnecessary null checks:
@Stateless
class KittenBusinessService {
@PersistenceContext
private lateinit var entityManager: EntityManager
...
Let’s see what else can be improved.
Null or Optional?
This is a pretty tough question. Kotlin has excellent support for nullable values, which helps a lot when you are using third party libraries. The question is what to use when you have the opportunity to choose one over the other? Here is our original Optional producer and consumer pair:
fun find(id: Int): Optional<KittenEntity> =
Optional.ofNullable(entityManager.find(KittenEntity::class.java, id))
fun find(id: Int): KittenRest =
kittenBusinessService
.find(id)
.map { kittenEntity -> KittenRest(kittenEntity.name, kittenEntity.cuteness) }
.orElseThrow { NotFoundException("ID $id not found") }
Idiomatic Kotlin solution would be to use nulls, so it becomes:
fun find(id: Int): KittenEntity? =
entityManager.find(KittenEntity::class.java, id)
fun find(id: Int) =
kittenBusinessService
.find(id)
?.let { KittenRest(it.name, it.cuteness) }
?: throw NotFoundException("ID $id not found")
Nullable value can appear in each step of the call chain, so you have to use the question mark for all calls. That solves the nullability issue, but it is not pretty.
However, if the return type is Optional
and the result becomes Optional.empty
, all future monadic calls on that object will be simply skipped and the result will be Optional.empty
. To me, this looks like a cleaner solution, and it is also a safer one if you plan to call Kotlin code from Java. For Java interop, prefer Optionals over nulls.
Operators!
find
, add
and delete
are perfectly valid names for methods, but wouldn’t it be good to use operators instead?
Method | Operator |
---|---|
service.find(id) |
service[id] |
service.add(kittenEntity) |
service += kittenEntity |
I find it not to be just shorter, but also more readable as code is not a big pile of method calls any more. Be careful to use only well-known and well-understood operators, otherwise, you will get a big mess like some Scala libraries, and then you will need operator periodic table. In case of data repository, the MutableMap-like interface works nicely. Note that I used “plus assign” (+=
) operator for persisting an entity, as original collection contains what it already has plus additional item.
Here is how to declare them:
operator fun plusAssign(kitten: KittenEntity) =
entityManager.persist(kitten)
operator fun get(id: Int): KittenEntity? =
entityManager.find(KittenEntity::class.java, id)
You may want to leave the original methods and make operators wrapper around them, as original methods can return values, while some operators cannot. Other good candidates for operators are “remove” and “contains” methods, because they can be represented with “minus assign” (-=
) and Kotlin’s “in
” operator. I will leave the rest to your imagination.
Conclusion
The purpose of writing in an idiomatic way is to have more readable and safer code, and I hope that presented example succeeded in that intention. This series shows just a couple of ways how to improve code over Java version leaving some areas untouched. Features worth exploring are: extension functions, and if
, when
and try/catch
as functions. Just explore, find out what works for you, and have fun!
Complete code can be found here
Comments
Post a Comment