$30 off During Our Annual Pro Sale. View Details »

Kotlin Extension Functions

Kotlin Extension Functions

Christoph Leiter

April 18, 2017
Tweet

More Decks by Christoph Leiter

Other Decks in Technology

Transcript

  1. Agenda 1 Basics 2 General Extension Functions 3 Handy Extension

    Functions in stdlib 4 Defining your own Extension Functions 5 Summary 2
  2. What are EF? EFs allow to add new functionality to

    a class without modifying it Better alternative to creating *Util classes They are statically resolved, there’s no patching of the original classes Works with final classes, e.g. String Kotlin also has extension properties which are similar 4
  3. Declaring an EF Extension function: fun SomeType. extensionFunction (x: String

    ): Int { ... } Inside the fun you can reference the instance with this Even works with nullable types (this will be null) Extension property: val SomeType.empty: Boolean get() = size == 0 5
  4. Interoperability You can call EF from Java as well but

    you need to call the static method directly If your EFs are in a file called foo.kt Kotlin will generate the class FooKt with static methods First argument is the receiver object (this) 6
  5. Interoperability You can call EF from Java as well but

    you need to call the static method directly If your EFs are in a file called foo.kt Kotlin will generate the class FooKt with static methods First argument is the receiver object (this) Example extensions.kt: fun Int.toBigInteger () = BigInteger.valueOf(this.toLong ()) Java: ExtensionsKt.toBigInteger (10); 6
  6. General Extension Functions There are several handy extension functions defined

    in stdlib (Standard.kt) which work on Any: run let apply also takeIf takeUnless to 8
  7. let Calls the specified function block with this value as

    its argument and returns its result. public inline fun <T, R> T.let (block: (T) -> R): R = block(this) 9
  8. let Calls the specified function block with this value as

    its argument and returns its result. public inline fun <T, R> T.let (block: (T) -> R): R = block(this) Example: class C { var d: D? = null fun f() { d?.let { it.g() } } } Use to capture variable if smart cast not possible due to mutability. 9
  9. run Calls the specified function block with this value as

    its receiver and returns its result. public inline fun <T, R> T.run (block: T.() -> R): R = block () 10
  10. run Calls the specified function block with this value as

    its receiver and returns its result. public inline fun <T, R> T.run (block: T.() -> R): R = block () Example: val x = y.run() { a = 1 // refers to y.a b = 2 // refers to y.b compute () // refers to y.compute () } Use if you want to access multiple members of an instance without repeating yourself but you want to return something different. Might also be useful to execute multiple methods of an instance with a null check by using ?.run(..). 10
  11. apply Calls the specified function block with this value as

    its receiver and returns this value. public inline fun <T> T.apply (block: T.() -> Unit ): T { block (); return this } 11
  12. apply Calls the specified function block with this value as

    its receiver and returns this value. public inline fun <T> T.apply (block: T.() -> Unit ): T { block (); return this } Example: val bean = MyBean (). apply { a = 1 b = 2 c = true } Use to instantiate instances and set some values. 11
  13. also Calls the specified function block with this value as

    its argument and returns this value. public inline fun <T> T.also (block: (T) -> Unit ): T { block(this ); return this } 12
  14. also Calls the specified function block with this value as

    its argument and returns this value. public inline fun <T> T.also (block: (T) -> Unit ): T { block(this ); return this } Example: fun MyBean.copy () = MyBean (). also { it.a = a // it refers to new instance it.b = b // implicit this refers to outer } 12
  15. to Creates a tuple of type Pair from this and

    that public infix fun <A, B> A.to(that: B): Pair <A, B> = Pair(this , that) Useful for creating map entries with less verbosity: val m = mapOf("foo" to 23, "bar" to 42) 13
  16. takeIf Returns this value if it satisfies the given predicate

    or null, if it doesn’t. public inline fun <T> T.takeIf (predicate: (T) -> Boolean ): T? = if (predicate(this )) this else null 14
  17. takeIf Returns this value if it satisfies the given predicate

    or null, if it doesn’t. public inline fun <T> T.takeIf (predicate: (T) -> Boolean ): T? = if (predicate(this )) this else null Example: val x = foo.takeIf { it.p() } ?: return true Like filter for a single value. 14
  18. takeUnless Returns this value if it does not satisfy the

    given predicate or null, if it does. public inline fun <T> T.takeUnless (predicate: (T) -> Boolean ): T? = if (! predicate(this )) this else null Same as a negated takeIf. 15
  19. Array flatten: Returns a single list of all elements from

    all arrays in the given array. fun <T> Array <out Array <out T>>. flatten (): List <T> 17
  20. Array flatten: Returns a single list of all elements from

    all arrays in the given array. fun <T> Array <out Array <out T>>. flatten (): List <T> unzip: Returns a pair of lists, where first list is built from the first values of each pair from this array, second list is built from the second values of each pair from this array. fun <T, R> Array <out Pair <T, R>>. unzip (): Pair <List <T>, List <R>> 17
  21. Iterable flatten: Returns a single list of all elements from

    all collections in the given collection. fun <T> Iterable <Iterable <T>>. flatten (): List <T> 18
  22. Iterable flatten: Returns a single list of all elements from

    all collections in the given collection. fun <T> Iterable <Iterable <T>>. flatten (): List <T> unzip: Returns a pair of lists, where first list is built from the first values of each pair from this collection, second list is built from the second values of each pair from this collection. fun <T, R> Iterable <Pair <T, R>>. unzip (): Pair <List <T>, List <R>> { 18
  23. List orEmpty: Returns this List if it’s not null and

    the empty list otherwise. inline fun <T> List <T>?. orEmpty (): List <T> = this ?: emptyList () 19
  24. List orEmpty: Returns this List if it’s not null and

    the empty list otherwise. inline fun <T> List <T>?. orEmpty (): List <T> = this ?: emptyList () binarySearch: Searches this list or its range for the provided element using the binary search algorithm. fun <T: Comparable <T>> List <T?>. binarySearch ( element: T?, fromIndex: Int = 0, toIndex: Int = size ): Int 19
  25. Map getOrElse: Returns the value for the given key, or

    the result of the defaultValue function if there was no entry for the given key. inline fun <K, V> Map <K, V>. getOrElse( key: K, defaultValue: () -> V ): V = get(key) ?: defaultValue () 20
  26. Map getOrElse: Returns the value for the given key, or

    the result of the defaultValue function if there was no entry for the given key. inline fun <K, V> Map <K, V>. getOrElse( key: K, defaultValue: () -> V ): V = get(key) ?: defaultValue () Alternative to using ?: val v = map.getOrElse(k) { myDefaultValue } val w = map[k] ?: myDefaultValue 20
  27. Map II getOrPut: Returns the value for the given key.

    If the key is not found in the map, calls the defaultValue function, puts its result into the map under the given key and returns it. inline fun <K, V> MutableMap <K, V>. getOrPut( key: K, defaultValue: () -> V ): V 21
  28. Map II getOrPut: Returns the value for the given key.

    If the key is not found in the map, calls the defaultValue function, puts its result into the map under the given key and returns it. inline fun <K, V> MutableMap <K, V>. getOrPut( key: K, defaultValue: () -> V ): V Easy way to get a value out of a cache and if it doesn’t exist compute it and put it in the cache val v = cache.getOrPut(key) { computeValue(key) } 21
  29. Map III Methods to filter Maps based on Entrys, keys

    or values: inline fun <K, V> Map <out K, V>. filter( predicate: (Map.Entry <K, V>) -> Boolean ): Map <K, V> inline fun <K, V> Map <out K, V>. filterKeys( predicate: (K) -> Boolean ): Map <K, V> { inline fun <K, V> Map <out K, V>. filterValues ( predicate: (V) -> Boolean ): Map <K, V> 22
  30. Comparator thenBy: Creates a comparator comparing values after the primary

    comparator defined them equal. It uses the function to transform value to a Comparable instance for comparison. inline fun <T> Comparator <T>. thenBy( crossinline selector: (T) -> Comparable <*>? ): Comparator <T> { 23
  31. Comparator thenBy: Creates a comparator comparing values after the primary

    comparator defined them equal. It uses the function to transform value to a Comparable instance for comparison. inline fun <T> Comparator <T>. thenBy( crossinline selector: (T) -> Comparable <*>? ): Comparator <T> { Allows chaining of multiple comparators list.sortedWith( compareBy <User > { it.shoeSize }. thenBy { it.age } ) 23
  32. Lock withLock: Executes the given action under this lock and

    returns its value. inline fun <T> Lock.withLock( action: () -> T ): T 24
  33. Lock withLock: Executes the given action under this lock and

    returns its value. inline fun <T> Lock.withLock( action: () -> T ): T Allows easier lock usage: lock.withLock () { doSomething () } Also has read and write EFs for ReentrantReadWriteLock 24
  34. Closeable use: Executes the given block function on this resource

    and then closes it down correctly whether an exception is thrown or not. inline fun <T : Closeable?, R> T.use( block: (T) -> R ): R 25
  35. Closeable use: Executes the given block function on this resource

    and then closes it down correctly whether an exception is thrown or not. inline fun <T : Closeable?, R> T.use( block: (T) -> R ): R Works like try-with-resources in Java but is not a language feature stream.use { readFrom(it) } 25
  36. Use cases There are many good use cases to use

    EFs in your code: Add methods to classes which you don’t control Add methods to classes depending on the context toDTO in REST layer toJPA in storage layer Make 3rd party libraries more Kotlin like 27
  37. DTO Converter class UsersController ( private val userRepository : UserRepository

    ) { fun getUser(id: Long) = userRepository .find(id)?. toDTO () } private fun User.toDTO (): UserDTO = UserDTO (). also { it.name = "$firstname $lastname" it.age = calculateAge (birthday) } 28
  38. SQL like IN fun Any?. isIn(vararg objs: Any ?): Boolean

    = objs.any { it == this } Allows compact syntax for comparing a value to multiple possibilities. Avoid repetition of the value if it’s a complex expression. if (foo.bar (). buz (). isIn (23, 42, 404)) { doSomething () } 29
  39. MonetaryAmount JSR-354 defines a new MonetaryAmount. We can extend it

    to feel more like a native type with operator EFs: operator fun MonetaryAmount .plus( other: MonetaryAmount ): MonetaryAmount = this.add(other) operator fun MonetaryAmount .times( other: Number ): MonetaryAmount = this.multiply(other) 30
  40. MonetaryAmount JSR-354 defines a new MonetaryAmount. We can extend it

    to feel more like a native type with operator EFs: operator fun MonetaryAmount .plus( other: MonetaryAmount ): MonetaryAmount = this.add(other) operator fun MonetaryAmount .times( other: Number ): MonetaryAmount = this.multiply(other) Allows use of arithmetic symbols: val a = Money.of(30, "EUR") val b = Money.of(50, "EUR") val c = a + b * 2 30
  41. MonetaryAmount II You could even define something like val Number.EUR:

    MonetaryAmount get() = Money.of(this , "EUR") 31
  42. MonetaryAmount II You could even define something like val Number.EUR:

    MonetaryAmount get() = Money.of(this , "EUR") Then you could write assertThat(calculation ()). isEqualTo (30. EUR) I wouldn’t recommend this for production code but for unit tests it might be handy. 31
  43. Formatting for Remote Systems When communicating with remote systems it’s

    handy to have functions for data types to convert from/to remote types. fun LocalDate.format () = format( DateTimeFormatter .ISO_DATE) fun MonetaryAmount .convert (): BigDecimal = number.numberValue(BigDecimal :: class.java) .multiply(BigDecimal.valueOf (100)) fun XMLGregorianCalendar .toLocalDate () = toGregorianCalendar () . toZonedDateTime () .toLocalDate () Make sure to mark them private or internal to avoid polluting the namespace. 32
  44. Redisson Lock inline fun <T> RedissonClient .lock( lockName: String ,

    waitTime: Duration , leaseTime: Duration , action: (RLock) -> T ): LockResult <T> { val lock = getLock(lockName) val success = lock.tryLock( waitTime.toMillis (), leaseTime.toMillis (), TimeUnit.MILLISECONDS) // check - try action - finally unlock } 33
  45. Redisson Lock II Easy and consistent usage of locks, no

    need to repeat the try-finally block all the time Looks more like a language feature Allows usage of Duration instead of milliseconds as Long You can set default values for waitTime and leaseTime redisson.lock("my -lock") { doSomethingInLock () } 34
  46. EFs for Lambdas You can even define EFs on lambdas:

    fun (() -> Any). time (): Long { val start = System. currentTimeMillis () this () return System. currentTimeMillis () - start } val t = { computeSomething () }. time () 35
  47. EFs for Lambdas You can even define EFs on lambdas:

    fun (() -> Any). time (): Long { val start = System. currentTimeMillis () this () return System. currentTimeMillis () - start } val t = { computeSomething () }. time () Haven’t found a good use case for that but it’s good to know nonetheless. :) 35
  48. Summary EFs are syntactic sugar but really helpful Help to

    make your code more concise Can be used to “extend” the language (e.g. use is a language feature in Java) Even though you can define the same EFs multiple times it’s best not to pollute the namespace for common types 37