You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: exercise-1.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,7 +8,7 @@ This project uses maven for building the application, for gradle projects the sa
8
8
9
9
Prepare the maven pom.xml for Kotlin.
10
10
11
-
**Exercise**: add the `<kotlin.version>` and `<kotlin.compiler.incremental>true</kotlin.compiler.incremental>`maven property to the pom.xml. We will use this property to define the version of Kotlin dependencies used in this project.
11
+
**Exercise**: add the `<kotlin.version>` and `<kotlin.compiler.incremental>true</kotlin.compiler.incremental>`properties to the maven pom.xml. We will use this property to define the version of Kotlin dependencies used in this project.
12
12
13
13
```xml
14
14
<properties>
@@ -34,7 +34,7 @@ By adding `<kotlin.compiler.incremental>true</kotlin.compiler.incremental>` we i
34
34
</dependency>
35
35
```
36
36
37
-
You project is now ready to use the Kotlin standard library and reflection utilities which are used by frameworks like Jackson.
37
+
You project is now ready to use the Kotlin standard library and reflection utilities which are used by frameworks like Spring Boot and Jackson.
Copy file name to clipboardExpand all lines: exercise-2.md
+32-31Lines changed: 32 additions & 31 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -16,7 +16,7 @@ Open `OrderItem.java`.
16
16
17
17
**Exercise**: convert OrderItem.java to Kotlin using IntelliJ _menu > Code > Convert Java File to Kotlin File_.
18
18
19
-
The outcome of the conversion is far from optimal, because of the Java class being a bit more complex. We can do better!
19
+
The outcome of the conversion is far from optimal, because of the Java class being a (little) bit more complex. We can do better!
20
20
21
21
Let get rid of the `equals()`, `hashCode()` and `toString()` boiler-plate. Kotlin has a feature for that: [data classes](https://kotlinlang.org/docs/reference/data-classes.html). Lets do this step by step.
22
22
@@ -26,21 +26,24 @@ With data classes we get the `equals()`, `hashCode()` and `toString()` functions
26
26
27
27
**Exercise**: remove the `equals()`, `hashCode()` and `toString()` functions.
28
28
29
-
In the converted code we ended up with a [constructor](https://kotlinlang.org/docs/reference/classes.html#constructors)on class level called the primary constructor, and next to that the secondary constructor.
29
+
In the converted code we ended up with a primary [constructor](https://kotlinlang.org/docs/reference/classes.html#constructors) and next to that a secondary constructor.
30
30
31
-
In many situations we can get rid of the duplicate constructors by merging the [constructors](https://kotlinlang.org/docs/reference/classes.html#constructors) into a single primary constructor.
31
+
In many situations we can get rid of the generated duplicate constructors by merging the [constructors](https://kotlinlang.org/docs/reference/classes.html#constructors) into a single primary constructor by providing default values for some of the constructor arguments.
32
32
33
-
Notice that the `productId`argument in the two constructors are different, one which is the non-nullable `val productId: String` and the other which is nullable `val productId: String?`. Since productId should be a mandatory field, we will use the null safe version in the next exercise.
33
+
Notice that the `productId`and `price` arguments in the two constructors are of the nullable type String.
34
34
35
-
**Exercise**: merge the two constructors into primary constructor but *don't remove* the `@JsonCreator` and `@JsonProperty` annotations. Hint, the constructor keyword on class level is mandatory when you want to use annotations on the primary constructor.
35
+
**Exercise**: merge the two constructors into primary constructor but *don't remove* the `@JsonCreator` and `@JsonProperty` annotations. Hint, you can also use the constructor keyword on class level when you want to use annotations on the primary constructor just yet. Make all arguments non-nullable and don't provide a default value.
36
36
37
37
<details>
38
38
<summary>Suggested solution:</summary>
39
39
40
40
```kotlin
41
-
data classOrderItem @JsonCreator constructor(@JsonProperty("productId") valproductId:String,
42
-
@JsonProperty("quantity") valquantity:Int,
43
-
valprice:BigDecimal) {
41
+
data classOrderItem @JsonCreator constructor(
42
+
@JsonProperty("productId") valproductId:String,
43
+
@JsonProperty("quantity") valquantity:Int,
44
+
valprice:BigDecimal
45
+
) {
46
+
44
47
val totalPrice:BigDecimal
45
48
get() = price.multiply(BigDecimal(quantity))
46
49
}
@@ -72,9 +75,9 @@ for JSON property price due to missing (therefore NULL) value for creator parame
In the POST body we send only two fields `{"productId":"1","quantity":2}` for an OrderItem, these fields are directly mapped onto the OrderItem class. This used to work when there were two constructors, but after the merge we ended up with a single constructor which requires 3 non-nullable (mandatory) parameters.
78
+
In the POST body we send only two fields `{"productId":"1","quantity":2}` for an OrderItem, these fields are directly mapped onto the OrderItem class. This used to work when there were two constructors, but after the merge we ended up with a single constructor which requires 3 non-nullable (mandatory) arguments.
76
79
77
-
How can we fix this with Kotlin? First lets try to make the price field (optional) nullable and add the ? after the BigDecimal type. You will probable notice that the totalPrice calculation is now also giving you a hard time. Price can now be nullable therefore we need to add null safety checks using the ? operator in the totalPrice calculation, see the snippet below:
80
+
How can we fix this with Kotlin? First lets try to make the price field nullable and add the question mark to the BigDecimal type. The totalPrice calculation uses the price property and should handle the value being null by adding the null-safe operator (?.) to the totalPrice calculation, like in the snippet below:
78
81
79
82
```kotlin
80
83
data classOrderItem @JsonCreator constructor(@JsonProperty("productId") valproductId:String,
@@ -85,7 +88,8 @@ data class OrderItem @JsonCreator constructor(@JsonProperty("productId") val pro
85
88
}
86
89
```
87
90
88
-
A better approach would be to avoid having to deal with null values all together so that way we don't have to worry about potential NPEs. We can do this by providing a default value for the price argument. This way we dont have to explicitly provide a value, but it will never be null. In the Java version price was assigned the value of BigDecimal.ZERO, use that here as well.
91
+
A much better approach would be to avoid having to deal with null values all together and not having to worry about potential NPEs.
92
+
We can do this by providing a default value for the price argument. This way we can construct the OrderItem without explicitly providing a value for price, but it will never be null. In the Java version price was assigned the value of BigDecimal.ZERO, use that here as well.
89
93
90
94
**Exercise**: assign a default value, BigDecimal.ZERO to the price argument.
91
95
@@ -97,43 +101,41 @@ data class OrderItem @JsonCreator constructor(@JsonProperty("productId") val pro
97
101
@JsonProperty("quantity") valquantity:Int,
98
102
valprice:BigDecimal = BigDecimal.ZERO) {
99
103
val totalPrice:BigDecimal
100
-
get() = price.multiply(BigDecimal(quantity)) // evaluated every time we access the totalPrice property or call getTotalPrice() from Java.
104
+
get() = price.multiply(BigDecimal(quantity))
101
105
}
102
106
```
103
107
</details>
104
108
<br>
105
109
106
-
In the code snippet above the constructor arguments are val, immutable, which means after assignment the value cannot be changed. The `val totalPrice` is calculated based in these immutable properties, because of this we can simplify the code and write the totalPrice calculation as an expression. Would it not be nice if we could write it like:
110
+
In the code snippet above the constructor arguments are all immutable now, which means that after initialization their values cannot be changed.
107
111
108
-
```kotlin
109
-
val totalPrice:BigDecimal= price * quantity
110
-
```
112
+
The `val totalPrice` which is called a property in Kotlin, calculates the totalPrice based on two immutable properties (price and quantity). Since both properties are `vals` (immutable) we can get rid of the get() method on the property. Getters on properties are useful when the expression needs to be evaluated every time the property is accessed.
111
113
112
-
Or even more concise like this:
114
+
But the solutin is not yet the idiomatic Kotlin way of writing code, because it would be even nicer if we could write it like:
113
115
114
116
```kotlin
115
-
val totalPrice = price * quantity
117
+
val totalPrice:BigDecimal= price * quantity
116
118
```
117
119
118
-
Lets do this step by step but the application wont work until the end of this exercise!
120
+
Lets do this step by step (complete all the exercises before restarting the application).
119
121
120
-
**Exercise:** change the totalPrice calculation to the snippet below:
122
+
**Exercise:**instead of multiply use the times operator and change the totalPrice calculation to the snippet below:
121
123
122
124
```kotlin
123
125
val totalPrice:BigDecimal= price *BigDecimal(quantity)
124
126
```
125
127
126
-
This is not yet how we want to write the expression because we still have to wrap the quantity in a BigDecimal in order to be able to do calculations with BigDecimal. Notice that in Java this way of doing calculations on BigDecimals would not be possible!
128
+
We still need to wrap the quantity property in a BigDecimal in order to be able to do calculations with BigDecimal instances.
127
129
128
-
The Kotlin stdlib includes [overloaded operators](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/java.math.-big-decimal/index.html) for Java types like java.math.BigDecimal, this allows use the * (times) operator on BigDecimal.
130
+
Note that performing these kinds of calculations with BigDecimals is not possible in Java since it does not support operator overloading! In Kotlin this is possible, but limited to a few predefined operators only. The Kotlin stdlib includes [overloaded operators](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/java.math.-big-decimal/index.html) for Java types like java.math.BigDecimal, this allows use the * (times) operator on BigDecimal.
129
131
130
-
Have a look at the signature for times operator on `java.math.BigDecimal`.
132
+
Now have a look at the signature for times operator on `java.math.BigDecimal` in IntelliJ (Cmd+Click).
Note that `price * BigDecimal(quantity)` under the hood is exactly the same as `price.times(BigDecimal(quantity))`, this is just syntactic sugar.
138
+
Note that `price * BigDecimal(quantity)` under the hood is identical to `price.times(BigDecimal(quantity))`, consider it to be syntactic sugar.
137
139
138
140
What we want is being able to invoke the times function with a Int argument so that we do not need to wrap our Int in a BigDecimal. We can implement our own overloaded operator, similar to the one in the Kotlin stdlib.
139
141
@@ -148,27 +150,26 @@ operator fun BigDecimal.times(quantity: Int) = this.times(BigDecimal(quantity))
148
150
</details>
149
151
<br>
150
152
151
-
You can group these custom extensions like overloaded operators in a Kotlin files, so that they are easily recognizable and can be shared and reused in other parts of the code.
153
+
You can add the overloaded operator to the Kotlin file from which you are using it, but you can also group extensions and overloaded operators in a separate Kotlin file so that they are easily recognizable and can be shared and reused in other parts of the code.
152
154
153
155
### Polishing the code
154
156
155
-
This application communicates over HTTP using JSON, as you have seen in Swagger or in the curl command in the README.md. The JSON (de)serialization in this application is handled by the Jackson library, the spring-boot-starter-web (Spring Boot 2!) dependency pulls in the Jackson dependencies for us.
157
+
This application communicates via JSON over HTTP, as you have seen in Swagger or in the curl command in the README.md. The JSON (de)serialization in this application is handled by the Jackson library, the spring-boot-starter-web (Spring Boot 2!) dependency pulls in the Jackson dependencies for us.
156
158
157
159
In the BasketController the JSON data is mapped from the POST data directly on the OrderItem class. As you can see, in the OrderItem class we are instructing the Jackson library, with the `@JsonCreator` and `@JsonProperty`, how to map the JSON data to our Java (or Kotlin) class.
158
160
159
-
Reason for having the `@JsonProperty` annotation is that when compiling Java code, the parameter names of the constructor parameters are lost, so Jackson does not know how to map the json properties to the OrderItem class. In Kotlin, constructor parameter names are preserved when compiling code. We can therefore get rid of the `@JsonProperty` annotations.
161
+
Reason for having the `@JsonProperty` annotation is that when compiling Java code, the parameter names in the constructor are lost, Jackson does not know how to map the json properties to the OrderItem class. In Kotlin, parameter names are preserved when compiling the code and stores as meta data. We can therefore get rid of the `@JsonProperty` annotations. As a bonus feature, the Jackson library also allows us to omit the `@JsonCreator` annotation when using Kotlin.
160
162
161
-
As a bonus feature, the Jackson library also allows us to omit the `@JsonCreator` annotation when using Kotlin, these features are provided by the `jackson-kotlin-module`. This dependency needs to be explicitly added to the project pom.xml!
163
+
This functionality is provided by the `jackson-kotlin-module`. This dependency needs to be added explicitly to the maven pom.xml!
162
164
163
-
When we don't have any annotations on the class definition we can omit the constructor keyword as well.
165
+
Once we have no `@JsonCreator`annotations on the `OrderItem`class we can omit the Kotlin constructor keyword as well.
164
166
165
-
**Exercise**: add the `jackson-module-kotlin` to the maven `pom.xml` (note that `${jackson.version}`is defined by Spring Boot)
167
+
**Exercise**: add the `jackson-module-kotlin` to the maven `pom.xml` (note that the version of this module is [managed by Spring Boot](https://docs.spring.io/spring-boot/docs/current/reference/html/appendix-dependency-versions.html)).
0 commit comments