Using Kotlin Extensions in Groovy
Dive into two different ways of using extension methods with Kotlin and Groovy, which is particularly useful if you want to use Spock for testing.
Join the DZone community and get the full member experience.
Join For FreeKotlin and Groovy have mechanisms for extending existing classes without using inheritance or decorators. In both languages, the mechanisms are called extension methods. Their source code looks different, but generated bytecode is quite similar. Thanks to that, Groovy is able to use Kotlin extensions just like its own.
Why would I want to use such extensions in Groovy? The main reason is that I want to test my extensions using the best testing framework available for the JVM — Spock Framework.
The code is available here.
Extensions in Kotlin
There are many types of extensions in Kotlin. I decided to focus only on extension functions and properties.
As an example, I extend the java.lang.String
class. First, I create an extension function, skipFirst
, which skips the first N
characters:
fun String.skipFirst(n: Int) = if (length > n) this.substring(n) else ""
Next, I create an extension property answer
, which is the Answer to the Ultimate Question of Life, the Universe, and Everything:
val String.answer
get() = 42
Both extensions are declared in the package com.github.alien11689.extensions
, in the file StringExtensions
. However, the generated class in the target directory is named StringExtensionsKt
, and this is the name that must be used when accessing it from other languages. Specific class names can be forced by the annotation @file:JvmName
.
Using Kotlin Extensions in Groovy
There are two ways to use extensions in Groovy (that are supported by good IDEs). First, you can declare the scope where the extensions are available by the use
method:
def "should use extension method"() {
expect:
use(StringExtensionsKt) {
input.skipFirst(n) == expected
}
where:
input | n | expected
"abcd" | 3 | "d"
"abcd" | 6 | ""
"" | 3 | ""
}
def "should use extension property"() {
expect:
use(StringExtensionsKt) {
"abcd".answer == 42
}
}
This is acceptable, but it is not very convenient. The second and much better way is to use an extension module definition. The extension module is defined in the file org.codehaus.groovy.runtime.ExtensionModule
in the directory src/main/resources/META-INF/services/
. The same directory is monitored by ServiceLoader, but the file format is completely different:
moduleName=string-extension-module
moduleVersion=1.0.0
extensionClasses=com.github.alien11689.extensions.StringExtensionsKt
The tests look much better now:
def "should use extension method"() {
expect:
input.skipFirst(n) == expected
where:
input | n | expected
"abcd" | 3 | "d"
"abcd" | 6 | ""
"" | 3 | ""
}
def "should use extension property"() {
expect:
"abcd".answer == 42
}
Published at DZone with permission of Dominik Przybysz. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments