Beliebte Suchanfragen
|
//

Kotlin goodies that make a developer's life easier

19.9.2018 | 8 minutes of reading time

In this blog post, I want to show how some of Kotlin's feature make it easier to write concise code. First, these are functions from the Kotlin standard library. Second, these are custom written functions that use Kotlin language features, extension functions, or lambda arguments.

I won't go into details of what extensions functions are or focus on syntax details. For this, I refer to the excellent Kotlin documentation .

Functions from the Kotlin library

In this first part of this blog post, I'd like to introduce some functions and functionality that the standard Kotlin library offers. This is just a small selection which I found useful and I strongly encourage you to check the source code and documentation by yourself.

Argument checking

When writing code that is not using any validation framework, you sometimes have to check the validity of function arguments. You can do it in the classical way with:

fun someFunction(answer: Int) {
  if(answer != 42) {
    throw IllegalArgumentException("the answer must be 42!")
  }
  // function code
}

But the better way is to use require from the standard library, which does exactly the same:

fun someFunction(answer: Int) {
  require(answer == 42) {" the answer must be 42!"}
  // function code
}

Returning multiple values from a function

Sometimes you need to return two values from a function. One way to do this is by creating a new class to hold this value. An easier way is to use Kotlin's Pair class, the to infix function and a destructuring variable declaration:

fun someFunction() : Pair<Int, String> {
  return 42 to "the answer"
}

fun main(args: Array<String>) {
  val (i: Int, s: String) = someFunction()
  log.info("$s is $i") // logs "the answer is 42"
}

<h3Time measuring

You probably often use some code to measure the execution time of some code like this:


val start = System.currentTimeMillis()
// do something    
val end = System.currentTimeMillis()   

log.info("duration: ${end - start} msecs")

Kotlin offers two inline functions, measureTimeMillis and measureNanoTime, which can be used in the following way:


val duration = measureTimeMillis {
  // do something
}

log.info("duration: $duration msecs")

File IO

Have you ever tried to delete a directory in your program that is not empty? There is no function in the JVM classes that will allow you to do this without implementing a recursive descent into the directory and delete contained files and directories first.
Kotlin has an extension function for that:

File("/path/to/some/directory").deleteRecursively()

If you have the name of a file and just need it without the extension:


val f = File("image.jpg")
val name = f.nameWithoutExtension // sets name to "image"

Read a file line by line and call a lambda to process each line:


File("filename").useLines { line ->
  println(line)
}

There are many more utility functions in the kotlin.io package, please check the documentation.

Concurrent programming

Reentrant read write locks

Did you ever need to have a resource that can be accessed for writing by one thread, but should be able to be read by multiple threads? Java has the ReentrantReadWriteLock class for this, but using it is not easy as you have to take care of interruptions or exceptions when aquiring and releasing the locks.

Kotlin has you covered with some helper functions (extension functions on the ReentrantReadWriteLock class) where you just pass in the function you want to be executed within the lock:

val sharedResource = mutableMapOf<String, String>()
val lock = ReentrantReadWriteLock()

fun readSomeData(key: String) {
  return lock.read {
    sharedResource[key]
  }
}

fun writeSomeData(key: String, value: String) {
  lock.write {
    sharedResource[key] = value
  }
}

Threads and ThreadLocals

When using a ThreadLocal, the normal code to initialize a ThreadLocal object looks something like this:

class ClassWithThreadState {
  private val state = object : ThreadLocal<String>() {
    override fun initialValue(): String = "initialValue"
  }

  fun someFunction() {
    var currentState = state.get()
    log.info { "currentState: $currentState" }
    state.set("newState")
  }
}

Kotlin adds a getOrSet extension function which makes it easier to set up the inital value:

class ClassWithThreadState {
  private val state = ThreadLocal<String>()

  fun someFunction() {
    val currentState = state.getOrSet { "initalValue" }
    log.info { "currentState: $currentState" }
    state.set("newState")
  }
}

To run some code in a thread, you have to wrap it in a Runnable, which is then passed into a Thread, which is then started:

fun main(args: Array<String>) {
  val classWithThreadState = ClassWithThreadState()
  Thread(Runnable { classWithThreadState.someFunction() }).start()
}

The Kotlin library has a thread function that makes it easier:

fun main(args: Array<String>) {
  val classWithThreadState = ClassWithThreadState()
  // imediately start the thread with the runnable
  thread { classWithThreadState.someFunction() }
    
  // or first create it and start it later:
  val t = thread(start = false) { classWithThreadState.someFunction() }
  t.start()
}

Apart from the start parameter, the thread function has other parameters which allow you to set the priority, the name or the daemon state of the thread.

Writing custom functions and extensions

In the second part of this blog, I now want to show how you can easily write functions to make the code cleaner and more precise. These functions also enable you to remove boilerplate code.

The principles I use for these functions are the same that are used throughout the Kotlin library: extension functions and functions taking a lambda as last argument, which is then written after the parenthesis. For more information about these techniques, you might want to read my blog post “ Wie schreibt man eine Kotlin-DSL“ .

Slf4j

Are you using slf4j and are tired of writing the same boilerplate code for creating a logger in every class like this?

class SomeClass {
  fun doSomething() {
    log.info("doing something")
  }
    
  companion object {
    // always have to repeat the name of the class here:
    private val log: Logger = LoggerFactory.getLogger(SomeClass::class.java)
  }
}

With a little extension function on Any, you can easily create an slf4j logger for any class, even if you put the logger in the companion object:

fun Any.logger(): Logger {
  val clazz = if (this::class.isCompanion) this::class.java.enclosingClass else this::class.java
  return LoggerFactory.getLogger(clazz)
}

When using this function, you just can skip passing in the class into the call to create the logger:


package de.codecentric.kotlingoodies
import logger

class SomeClass {
  fun doSomething() {
    log.info("doing something")
  }

  companion object {
    // creates a logger with the name "de.codecentric.kotlingoodies.SomeClass"
    private val log = logger()
  }
}

Are you checking logging levels of a logger to prevent, for example, string templates from being evaluated with code like this?

fun someFunction() {
  val someObject = Any() // or something else, doesn't matter here

  try {
    someObject.doSomething()
    if(log.isDebugEnabled) {
      log.debug("doing something with $someObject")
    }
  } catch (e: Exception) {
    if(log.isWarnEnabled) {
      log.warn("could not do something with $someObject" e)
    }
  }
}

With just a handful of extension functions for the Logger class you can reduce your code (just showing for debug and info level):

inline fun Logger.info(msg: (() -> String)) {if (isInfoEnabled) info(msg())}
inline fun Logger.info(t: Throwable, msg: (() -> String)) {if (isInfoEnabled) info(msg(), t)}
inline fun Logger.debug(msg: (() -> String)) {if (isDebugEnabled) debug(msg())}
inline fun Logger.debug(t: Throwable, msg: (() -> String)) {if (isDebugEnabled) debug(msg(), t)}

// use it like this: 
fun someFunction() {
  val someObject = Any() // or something else, doesn't matter here

  try {
    someObject.doSomething()
    log.debug { "doing something with $someObject" }
  } catch (e: Exception) {
    log.warn(e) { "could not do something with $someObject" }
  }
}

This makes your code more readable and prevents you from the trap of changing the logged level for example from info to debug and forgetting to change from isInfoEnabled to isDebugEnabled as well.

Writing functions to execute boilerplate code around something

Imagine that in your code you have lots of places where you for example start a transaction, do something, commit the transaction on success or roll back the transaction in an error case.

To handle this more elegantly, you can define a function – it has to be inline and reified to keep the result type. In this example it is called transactional, that does the boilerplate part and calls the code that is to be executed (R is the return type of the code to be executed):

inline fun <reified R> transactional(f: () -> R): R {
    // code to begin transaction
  return try {
    f()
    // code to commit transaction
  } catch(e: Exception){
    // code to rollback transaction
    throw e
  }
}

Then, to use it, you just pass a lambda to this function:

data class Record(val name: String)
fun main(args: Array<String>) {

  val records = transactional { 
    // this code is run within a transaction and is probably more complex in a real world scenario
    listOf(Record("John"), Record("James"))
  }

  records.forEach { println(it) }
}

Removing diacritics from strings

Just recently, I needed a function to remove diacritics (for example accents) from words because we were building URL paths with this words. This can be done by writing a function with the following code:

fun noDiacritics (s: String) : String {
  val normalized = Normalizer.normalize(this, Normalizer.Form.NFD)
  val stripped = normalized.replace("\\p{M}".toRegex(), "")
  return stripped
}

<pThe better variant is to write this as an extension function for the String class which makes it possible to use it on String variables and even on String constants:


inline fun String?.noDiacritics() = this?.let{Normalizer.normalize(this, Normalizer.Form.NFD)
  .replace("\\p{M}".toRegex(), "")}

fun main(args: Array<String>) {
  println("Porsche Coupé".noDiacritics())
  // output is: Porsche Coupe
}

Summary

In this post, I showed how small functions – either from the Kotlin standard library or self-written functions – can reduce the code you have to write. The examples also showed how they can make your code easier to read, understand, and maintain.

Kotlin features such as extension functions, inline, and infix functions help you create such helper functions yourself.

For further reference, I can only recommend the source code of the different Kotlin library functions, they are excellent material to study and learn from.

|

share post

//

More articles in this subject area

Discover exciting further topics and let the codecentric world inspire you.

//

Gemeinsam bessere Projekte umsetzen.

Wir helfen deinem Unternehmen.

Du stehst vor einer großen IT-Herausforderung? Wir sorgen für eine maßgeschneiderte Unterstützung. Informiere dich jetzt.

Hilf uns, noch besser zu werden.

Wir sind immer auf der Suche nach neuen Talenten. Auch für dich ist die passende Stelle dabei.