Decoding Kotlin - Your guide to solving the mysterious in Kotlin.pptx

JooEsperancinha 1,231 views 31 slides Apr 24, 2024
Slide 1
Slide 1 of 31
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15
Slide 16
16
Slide 17
17
Slide 18
18
Slide 19
19
Slide 20
20
Slide 21
21
Slide 22
22
Slide 23
23
Slide 24
24
Slide 25
25
Slide 26
26
Slide 27
27
Slide 28
28
Slide 29
29
Slide 30
30
Slide 31
31

About This Presentation

These are the slides of the presentation I gave at the JetBrains HQ by the RAI in Amsterdam named "Decoding Kotlin - Your Guide to Solving the Mysterious in Kotlin". I want to express a big thank you to Xebia and JetBrains for the opportunity. I gave this presentation on the 24th of April ...


Slide Content

Decoding Kotlin - Your guide to solving the mysterious in Kotlin By João Esperancinha 2024/04/24

Nullability Working with the Spring Framework Reflection to force nulls Inline and cross-inline The Java overview Tail recursive => Tail Cal Optimization (TCO) What is it Why? How it makes us work recursively and not use mutable Data classes Why things work and why things don't work How to fix the ones that don't How to work with use-site targets . What does a `delegate` do? and other use-site targets. Topics for today

Nullability Kotlin promises a guarantee of null-safety. Although we can use nullable members in our classes, we really shouldn’t whenever possible. Whenever possible?

@Table (name = "CAR_PARTS" ) @Entity data class CarPart( @Id val id : Long , val name : String , val productionDate : Instant , val expiryDate : Instant , val barCode : Long , val cost : BigDecimal ) CREATE SEQUENCE car_parts_id_sequence START WITH 1 INCREMENT BY 1 NO MINVALUE NO MAXVALUE CACHE 1 ; CREATE TABLE CAR_PARTS ( id BIGINT NOT NULL DEFAULT nextval ( 'car_parts_id_sequence' :: regclass ) , name VARCHAR ( 100 ) , production_date timestamp, expiry_date timestamp, bar_code BIGINT, cost float ) ; CRUD Entity Example

CRUD Entity Example INSERT INTO CAR_PARTS ( name , production_date , expiry_date , bar_code , cost ) VALUES ( 'screw' , current_date , current_date , 12345 , 1.2 ) ; INSERT INTO CAR_PARTS ( name , production_date , expiry_date , bar_code , cost ) VALUES ( null, current_date , current_date , 12345 , 1.2 ) ; @Test fun `should mysteriously get a list with a car part with a name null` () { carPartDao .findAll() . filter { it . name == null } . shouldHaveSize ( 1 ) } Is this possible?

Reflection Example val carPartDto = CarPartDto( id = 123L , name = "name" , productionDate = Instant.now() , expiryDate = Instant.now() , cost = BigDecimal. TEN , barCode = 1234L ) println (carPartDto) val field: Field = CarPartDto:: class . java .getDeclaredField( "name" ) field. isAccessible = true field.set(carPartDto , null ) println (carPartDto) assert (carPartDto. name == null ) println (carPartDto. name == null ) data class CarPartDto( val id : Long , val name : String , val productionDate : Instant , val expiryDate : Instant , val barCode : Long , val cost : BigDecimal ) Is this possible?

Inline and crossinline. Inline and crossline can be used in combination with each other. Inline provides bytecode copies of the code per each call point and they can even help avoid type erasure. Crossinline improves readability and some safety, but nothing really functional. Why does this matter?

Crossinline as just a marker fun main () { callEngineCrossInline { println ( "Place key in ignition" ) println ( "Turn key or press push button ignition" ) println ( "Clutch to the floor" ) println ( "Set the first gear" ) }. run { println ( this ) } } inline fun callEngineCrossInline (startManually: () -> Unit) { run loop@ { println ( "This is the start of the loop." ) introduction { println ( "Get computer in the backseat" ) return @introduction } println ( "This is the end of the loop." ) } println ( "Engine started!" ) } fun introduction (intro: () -> Unit) { println (LocalDateTime.now()) intro() return } public final class IsolatedCarPartsExampleKt { public static final void main() { int $i$f$callEngineCrossInline = false; int var1 = false; String var2 = "This is the start of the loop." ; System.out.println(var2) ; introduction((Function0) IsolatedCarPartsExampleKt $callEngineCrossInline$1$1.INSTANCE) ; var2 = "This is the end of the loop." ; System.out.println(var2) ; String var4 = "Engine started!" ; System.out.println(var4) ; Unit var3 = Unit.INSTANCE ; int var5 = false; System.out.println(var3) ; } public static final void introduction(@NotNull Function0 intro) { Intrinsics.checkNotNullParameter(intro , "intro" ) ; LocalDateTime var1 = LocalDateTime.now() ; System.out.println(var1) ; intro.invoke() ; } public final void invoke() { String var1 = "Get computer in the backseat" ; System.out.println(var1) ; } Decompiled code

Crossinline as just a marker fun main () { callEngineCrossInline { println ( "Place key in ignition" ) println ( "Turn key or press pus button ignition" ) println ( "Clutch to the floor" ) println ( "Set the first gear" ) }. run { println ( this ) } } inline fun callEngineCrossInline ( crossinline startManually: () -> Unit) { run loop@ { println ( "This is the start of the loop." ) introduction { println ( "Get computer in the backseat" ) startManually() return @introduction } println ( "This is the end of the loop." ) } println ( "Engine started!" ) } fun introduction (intro: () -> Unit) { println (LocalDateTime.now()) intro() return } public final class IsolatedCarPartsExampleKt { public static final void main() { int $i$f$callEngineCrossInline = false; int var1 = false; String var2 = "This is the start of the loop." ; System.out.println(var2) ; introduction((Function0)( new IsolatedCarPartsExampleKt$main$$inlined$callEngineCrossInline$1())) ; var2 = "This is the end of the loop." ; System.out.println(var2) ; String var4 = "Engine started!" ; System.out.println(var4) ; Unit var3 = Unit.INSTANCE ; int var5 = false; System.out.println(var3) ; } public static final void introduction(@NotNull Function0 intro) { Intrinsics.checkNotNullParameter(intro , "intro" ) ; LocalDateTime var1 = LocalDateTime.now() ; System.out.println(var1) ; intro.invoke() ; } public final void invoke() { String var1 = "Get computer in the backseat" ; System.out.println(var1) ; int var2 = false; String var3 = "Place key in ignition" ; System.out.println(var3) ; var3 = "Turn key or press push button ignition" ; System.out.println(var3) ; var3 = "Clutch to the floor" ; System.out.println(var3) ; var3 = "Set the first gear" ; System.out.println(var3) ; } Decompiled code

Crossinline for safety object SpecialShopNonLocalReturn { inline fun goToStore (chooseItems: () -> Unit) { println ( "Walks in" ) chooseItems() } @JvmStatic fun main (args: Array<String> = emptyArray ()) { goToStore { println ( "Make purchase" ) return @main } println ( "Never walks out" ) } } object SpecialShopLocalReturn { inline fun goToStore ( crossinline block: () -> Unit) { println ( "Walks in" ) block() } @JvmStatic fun main (args: Array<String> = emptyArray ()) { goToStore { println ( "Make purchase" ) return @goToStore } println ( "Walks out" ) } } @JvmStatic public static final void main(@NotNull String[] args) { Intrinsics.checkNotNullParameter(args , "args" ) ; SpecialShopNonLocalReturn this_$iv = INSTANCE ; int $i$f$goToStore = false; String var3 = "Walks in" ; System.out.println(var3) ; int var4 = false; String var5 = "Make purchase" ; System.out.println(var5) ; } @JvmStatic public static final void main(@NotNull String[] args) { Intrinsics.checkNotNullParameter(args , "args" ) ; SpecialShopLocalReturn this_$iv = INSTANCE ; int $i$f$goToStore = false; String var3 = "Walks in" ; System.out.println(var3) ; int var4 = false; String var5 = "Make purchase" ; System.out.println(var5) ; String var6 = "Walks out" ; System.out.println(var6) ; } ? Decompiled code

Tail Call Optimization Since the late 50’s TCO was already a theory intentend to be applied to Tail Recursivity. It allows tail recursive functions to be transformed into iterative functions in the compiled code for better performance. What is the catch?

Tail Call Optimization sealed interface Part { val totalWeight : Double } sealed interface ComplexPart : Part{ val parts : List<Part> } data class CarPart( val name : String , val weight : Double) : Part { override val totalWeight : Double get () = weight } data class ComplexCarPart( val name : String , val weight : Double , override val parts : List<Part> ) : ComplexPart { override val totalWeight : Double get () = weight } data class Car( val name : String , override val parts : List<Part> ) : ComplexPart { override val totalWeight : Double get () = parts . sumOf { it. totalWeight } } tailrec fun totalWeight (parts: List<Part> , acc: Double = 0.0 ): Double { if (parts.isEmpty()) { return acc } val part = parts. first () val remainingParts = parts. drop ( 1 ) val currentWeight = acc + part. totalWeight return when (part) { is ComplexPart -> totalWeight (remainingParts + part. parts , currentWeight) else -> totalWeight (remainingParts , currentWeight) } } Why use this? All variables are immutable Tail recursive

Tail Call Optimization tailrec fun totalWeight (parts: List<Part> , acc: Double = 0.0 ): Double { if (parts.isEmpty()) { return acc } val part = parts. first () val remainingParts = parts. drop ( 1 ) val currentWeight = acc + part. totalWeight return when (part) { is ComplexPart -> totalWeight (remainingParts + part. parts , currentWeight) else -> totalWeight (remainingParts , currentWeight) } } public static final double totalWeight(@NotNull List parts , double acc) { while ( true ) { Intrinsics.checkNotNullParameter(parts , "parts" ) ; if (parts.isEmpty()) { return acc ; } Part part = (Part)CollectionsKt.first(parts) ; List remainingParts = CollectionsKt.drop((Iterable)parts , 1 ) ; double currentWeight = acc + part.getTotalWeight() ; if (part instanceof ComplexPart) { List var10000 = CollectionsKt.plus((Collection)remainingParts , (Iterable)((ComplexPart)part).getParts()) ; acc = currentWeight ; parts = var10000 ; } else { acc = currentWeight ; parts = remainingParts ; } } } V ariables are mutable and algorithm is iterative

Data classes and Frameworks Kotlin provides use-site targets that allow us to specify where particular annotations have to be applied . Sometimes we need them and sometimes we don’t Why?

Working with Data classes @Table (name = "CAR_PARTS" ) @Entity data class CarPart( @Id val id : Long , @Column @field:NotNull @field:Size (min= 3 , max= 20 ) val name : String , val productionDate : Instant , val expiryDate : Instant , val barCode : Long , @field:Min (value = 5 ) val cost : BigDecimal ) @Table (name = "CAR_PARTS" ) @Entity data class CarPart( @Id val id : Long , @Column @NotNull @Size (min= 3 , max= 20 ) val name : String , val productionDate : Instant , val expiryDate : Instant , val barCode : Long , @Min (value = 5 ) val cost : BigDecimal ) Doesn’t work Works! Why u se-site targets?

Working with Data classes https://kotlinlang.org/docs/annotations.html#annotation-use-site-targets If you don't specify a use-site target, the target is chosen according to the @Target annotation of the annotation being used. If there are multiple applicable targets, the first applicable target from the following list is used: param property field

Working with Data classes @Target ({ElementType. METHOD , ElementType. FIELD , ElementType. ANNOTATION_TYPE , ElementType. CONSTRUCTOR , ElementType. PARAMETER , ElementType. TYPE_USE }) @Retention (RetentionPolicy. RUNTIME ) @Repeatable (List. class ) @Documented @Constraint ( validatedBy = {} ) public @ interface Size { PARAMETER is selected

Working with Data classes public final class CarPart { @Id private final long id ; @Column @NotNull private final String name ; @NotNull private final Instant productionDate ; @NotNull private final Instant expiryDate ; private final long barCode ; @NotNull private final BigDecimal cost ; public final long getId() { return this .id ; } @NotNull public final String getName() { return this .name ; } @NotNull public final Instant getProductionDate() { return this .productionDate ; } @NotNull public final Instant getExpiryDate() { return this .expiryDate ; } public final long getBarCode() { return this .barCode ; } @NotNull public final BigDecimal getCost() { return this .cost ; } public CarPart( long id , @jakarta.validation.constraints.NotNull @Size(min = 3 , max = 20 ) @NotNull String name , @NotNull Instant productionDate , @NotNull Instant expiryDate , long barCode , @Min( 5L ) @NotNull BigDecimal cost) { Intrinsics.checkNotNullParameter(name , "name" ) ; Intrinsics.checkNotNullParameter(productionDate , "productionDate" ) ; Intrinsics.checkNotNullParameter(expiryDate , "expiryDate" ) ; Intrinsics.checkNotNullParameter(cost , "cost" ) ; Not where we want them to be, b ut where they are expected

@Table (name = "CAR_PARTS" ) @Entity data class CarPart( @Id val id : Long , @Column @field:NotNull @field:Size (min= 3 , max= 20 ) val name : String , val productionDate : Instant , val expiryDate : Instant , val barCode : Long , @field:Min (value = 5 ) val cost : BigDecimal ) Working with Data classes public final class CarPart { @Id private final long id ; @Column @NotNull @Size( min = 3 , max = 20 ) @org.jetbrains.annotations.NotNull private final String name ; @org.jetbrains.annotations.NotNull private final Instant productionDate ; @org.jetbrains.annotations.NotNull private final Instant expiryDate ; private final long barCode ; @Min( 5L ) @org.jetbrains.annotations.NotNull private final BigDecimal cost ; Since @field forces the target, these annotations get applied where they should

Delegates and other use-site targets Delegation is a great part of the Kotlin language and it is quite different than what we are used to seeing in Java But how can we use it?

Working with Delegates interface Horn { fun beep () } class CarHorn : Horn { override fun beep () { println ( "beep!" ) } } class WagonHorn : Horn { override fun beep () { println ( "bwooooooo!" ) } } annotation class DelegateToWagonHorn annotation class DelegateToCarHorn class SoundDelegate( private val initialHorn : Horn) { operator fun getValue (thisRef: Any? , property: KProperty<*>): Horn { return initialHorn } } class HornPack { @delegate:DelegateToWagonHorn val wagonHorn : Horn by SoundDelegate(CarHorn()) @delegate:DelegateToCarHorn val carHorn : Horn by SoundDelegate(WagonHorn()) } Where is this being applied to? Horn or SoundDelegate?

Working with Delegates public final class HornPack { // $FF: synthetic field static final KProperty[] $$delegatedProperties = new KProperty[]{Reflection.property1( new PropertyReference1Impl(HornPack. class, "wagonHorn" , "getWagonHorn()Lorg/jesperancinha/talks/carparts/Horn;" , )) , Reflection.property1( new PropertyReference1Impl(HornPack. class, "carHorn" , "getCarHorn()Lorg/jesperancinha/talks/carparts/Horn;" , ))} ; @DelegateToWagonHorn @NotNull private final SoundDelegate wagonHorn$delegate = new SoundDelegate((Horn)( new CarHorn())) ; @DelegateToCarHorn @NotNull private final SoundDelegate carHorn$delegate = new SoundDelegate((Horn)( new WagonHorn())) ; @NotNull public final Horn getWagonHorn() { return this .wagonHorn$delegate.getValue( this, $$delegatedProperties[ ]) ; } @NotNull public final Horn getCarHorn() { return this .carHorn$delegate.getValue( this, $$delegatedProperties[ 1 ]) ; } } SoundDelegate No Horn!

Working with Delegates class SanitizedName( var name : String?) { operator fun getValue (thisRef: Any? , property: KProperty<*>): String? = name operator fun setValue (thisRef: Any? , property: KProperty<*> , v: String?) { name = v?. trim () } } class PartNameDto { @get:NotBlank @get:Size (max = 12 ) var name : String? by SanitizedName( null ) override fun toString (): String { return name ?: "N/A" } } class ImpossiblePartNameDto { @delegate:NotBlank @delegate:Size (max = 12 ) var name : String? by SanitizedName( null ) override fun toString (): String { return name ?: "N/A" } } public final class PartNameDto { static final KProperty[] $$delegatedProperties = new KProperty[]{Reflection.mutableProperty1( new MutablePropertyReference1Impl(PartNameDto. class, "name" , "getName()Ljava/lang/String;" , ))} ; @Nullable private final SanitizedName name$delegate = new SanitizedName((String) null ) ; @NotBlank @Size( max = 12 ) @Nullable public final String getName() { return this .name$delegate.getValue( this, $$delegatedProperties[ ]) ; } … public final class ImpossiblePartNameDto { static final KProperty[] $$delegatedProperties = new KProperty[]{Reflection.mutableProperty1( new MutablePropertyReference1Impl(ImpossiblePartNameDto. class, "name" , "getName()Ljava/lang/String;" , ))} ; @NotBlank @Size( max = 12 ) @Nullable private final SanitizedName name$delegate = new SanitizedName((String) null ) ; @Nullable public final String getName() { return this .name$delegate.getValue( this, $$delegatedProperties[ ]) ; }

Working with Delegates @Service data class DelegationService( val id : UUID = UUID.randomUUID() ) { @delegate:LocalDateTimeValidatorConstraint @get: Past val currentDate : LocalDateTime by LocalDateTimeDelegate() } public class DelegationService { // $FF: synthetic field static final KProperty[] $$delegatedProperties = new KProperty[]{Reflection.property1( new PropertyReference1Impl(DelegationService. class, "currentDate" , "getCurrentDate()Ljava/time/LocalDateTime;" , ))} ; @LocalDateTimeValidatorConstraint @NotNull private final LocalDateTimeDelegate currentDate$delegate ; @NotNull private final UUID id ; @Past @NotNull public LocalDateTime getCurrentDate() { return this .currentDate$delegate.getValue( this, $$delegatedProperties[ ]) ; }

What ’s next? Better understanding of the Kotlin Language. Don’t fight the Spring Framework or anything else like Quarkus. They are not evil and they are not magic. Read the Kotlin documentation and only use Google as a last resort. Nothing is perfect and Kotlin also falls into that category and recognizing that, allow us to be better.

Thank you!

Questions?

Resources Null Safety : https://kotlinlang.org/docs/null-safety.html Inline : https://kotlinlang.org/docs/inline-functions.html Tail Call Optimization : https://kotlinlang.org/docs/functions.html#tail-recursive-functions Annotation use-site targets: https://kotlinlang.org/docs/annotations.html#annotation-use-site-targets Spring Validation via AOP : https://docs.spring.io/spring-framework/reference/web/webmvc/mvc-controller/ann-validation.html

Source code https://github.com/jesperancinha/kotlin-mysteries

About me Homepage - https://joaofilipesabinoesperancinha.nl LinkedIn - https://www.linkedin.com/in/joaoesperancinha/ YouTube - JESPROTECH https://www.youtube.com/channel/UCzS_JK7QsZ7ZH-zTc5kBX_g https://www.youtube.com/@jesprotech X - https://twitter.com/joaofse GitHub - https://github.com/jesperancinha Hackernoon - https://hackernoon.com/u/jesperancinha DevTO - https://dev.to/jofisaes Medium - https://medium.com/@jofisaes

See you Next time!