Slide 1

Slide 1 text

Extension Functions Christoph Leiter Kotlin Vienna, April 18th 2017

Slide 2

Slide 2 text

Agenda 1 Basics 2 General Extension Functions 3 Handy Extension Functions in stdlib 4 Defining your own Extension Functions 5 Summary 2

Slide 3

Slide 3 text

Basics

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

General Extension Functions

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

let Calls the specified function block with this value as its argument and returns its result. public inline fun 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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

run Calls the specified function block with this value as its receiver and returns its result. public inline fun 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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

apply Calls the specified function block with this value as its receiver and returns this value. public inline fun 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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

also Calls the specified function block with this value as its argument and returns this value. public inline fun 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

Slide 18

Slide 18 text

to Creates a tuple of type Pair from this and that public infix fun A.to(that: B): Pair = Pair(this , that) Useful for creating map entries with less verbosity: val m = mapOf("foo" to 23, "bar" to 42) 13

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

takeIf Returns this value if it satisfies the given predicate or null, if it doesn’t. public inline fun 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

Slide 21

Slide 21 text

takeUnless Returns this value if it does not satisfy the given predicate or null, if it does. public inline fun T.takeUnless (predicate: (T) -> Boolean ): T? = if (! predicate(this )) this else null Same as a negated takeIf. 15

Slide 22

Slide 22 text

Handy Extension Functions in stdlib

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

Array flatten: Returns a single list of all elements from all arrays in the given array. fun Array >. flatten (): List 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 Array >. unzip (): Pair , List > 17

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

Iterable flatten: Returns a single list of all elements from all collections in the given collection. fun Iterable >. flatten (): List 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 Iterable >. unzip (): Pair , List > { 18

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

List orEmpty: Returns this List if it’s not null and the empty list otherwise. inline fun List ?. orEmpty (): List = this ?: emptyList () binarySearch: Searches this list or its range for the provided element using the binary search algorithm. fun > List . binarySearch ( element: T?, fromIndex: Int = 0, toIndex: Int = size ): Int 19

Slide 29

Slide 29 text

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 Map . getOrElse( key: K, defaultValue: () -> V ): V = get(key) ?: defaultValue () 20

Slide 30

Slide 30 text

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 Map . getOrElse( key: K, defaultValue: () -> V ): V = get(key) ?: defaultValue () Alternative to using ?: val v = map.getOrElse(k) { myDefaultValue } val w = map[k] ?: myDefaultValue 20

Slide 31

Slide 31 text

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 MutableMap . getOrPut( key: K, defaultValue: () -> V ): V 21

Slide 32

Slide 32 text

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 MutableMap . 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

Slide 33

Slide 33 text

Map III Methods to filter Maps based on Entrys, keys or values: inline fun Map . filter( predicate: (Map.Entry ) -> Boolean ): Map inline fun Map . filterKeys( predicate: (K) -> Boolean ): Map { inline fun Map . filterValues ( predicate: (V) -> Boolean ): Map 22

Slide 34

Slide 34 text

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 Comparator . thenBy( crossinline selector: (T) -> Comparable <*>? ): Comparator { 23

Slide 35

Slide 35 text

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 Comparator . thenBy( crossinline selector: (T) -> Comparable <*>? ): Comparator { Allows chaining of multiple comparators list.sortedWith( compareBy { it.shoeSize }. thenBy { it.age } ) 23

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

Lock withLock: Executes the given action under this lock and returns its value. inline fun Lock.withLock( action: () -> T ): T Allows easier lock usage: lock.withLock () { doSomething () } Also has read and write EFs for ReentrantReadWriteLock 24

Slide 38

Slide 38 text

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.use( block: (T) -> R ): R 25

Slide 39

Slide 39 text

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.use( block: (T) -> R ): R Works like try-with-resources in Java but is not a language feature stream.use { readFrom(it) } 25

Slide 40

Slide 40 text

Defining your own Extension Functions

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

SQL like IN fun Any?. isIn(vararg objs: Any ?): Boolean = objs.any { it == this } 29

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

Redisson Lock inline fun RedissonClient .lock( lockName: String , waitTime: Duration , leaseTime: Duration , action: (RLock) -> T ): LockResult { val lock = getLock(lockName) val success = lock.tryLock( waitTime.toMillis (), leaseTime.toMillis (), TimeUnit.MILLISECONDS) // check - try action - finally unlock } 33

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

Summary

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

End Thanks for your attention Questions? Kotlin Vienna Meetup https://www.meetup.com/Kotlin-Vienna/ 38