Showing posts with label kotlin. Show all posts
Showing posts with label kotlin. Show all posts

Saturday, November 16, 2019

Kotlin in Action Review and the Book Club Experience

If you are wanting to learn Kotlin, Kotlin in Action will give you a very solid foundation. While the book only covers Kotlin version 1.0 an as such is lacking many of the newer features of the language, the authors, Dmitry Jemerov and Svetlana Isakova, have such a breadth of understanding for the language that you'd be remiss not to read it. (This of course comes as no surprise, considering that they helped design and implement the language.)

I went through this book as part of a developer book club where I took a number of developers through the book along with me. As I went through this book I built out the following exercises to go along with some of the chapters to give the developers a chance to play around with the concepts that they were reading about. When we met to discuss the chapter, we would go through the exercises in a mob programming style, with me casting my laptop screen to a TV, and having the others in the group tell me what code to write to solve the problem, with me giving thoughts and suggestions here and there.

The developers in the book club were mostly all familiar with Java, so for chapters 3 through 6, the exercises that I built dealt with translating code from Java to Kotlin. These exercises went decently well:

Chapter 3: Exercises and Answers
Chapter 4: Exercises and Answers
Chapter 5: Exercises and Answers
Chapter 6: Exercises and Answers

For chapter 7 through 11, I changed strategies, and simply had a problem definition to solve without any code to translate. These went much better:

Chapter 7: Exercises and Answers
Chapter 8: Exercises and Answers
Chapter 9: Exercises and Answers
Chapter 10: Exercises and Answers
Chapter 11: Exercises and Answers

In addition to this, after finishing the book, I was asked present many of the concepts from the book in a series of trainings to a larger group of developers that were unfamiliar with the Kotlin language, along with some help from the other developers that were part of the book club. In addition to doing the training, I created the following outlines of the trainings so that attendees could have something to reference following the training:


All in all, I would say that this book club experience was very successful, with the participants getting a lot out of it and enjoying the process. I will look to reuse and build upon this experience as I help groups tackle technical books and topics moving forward.

Wednesday, October 30, 2019

Intro to Kotlin, Part 2 Outline

Let's start out with showing how classes work in Kotlin. Here's a basic User class:
class User(val username: String)

fun main() {
  val user = User("bob")
  println(user.username)
}
  • In one line we've declared a class with a property on that class
  • Note that public is the default visibility
  • Also note no new keyword when creating an instance
  • Since val was used to define the username, you can only get the value, and not change it
  • If you want to change it (e.g. have a setter), then use var
class User(var username: String)

fun main() {
  val user = User("bob")
  user.username = "robert"
  println(user.username)
}
  • Also, you can set default values for class properties
class User(var username: String = "unknown")

fun main() {
  val user = User()
  println(user.username)
}
  • You can also define multiple classes in the same file
class User(val username: String)
class Comment(val message: String, val author: User)

fun main() {
  val user = User("bob")
  val comment = Comment("Hi there!", user)
}
  • Classes like these that only contain data are called value objects, and are frequently known as pojos in Java
  • Typically with these kinds of classes, you should implement the toString, equals, and hashCode methods
  • In Kotlin, these methods can be auto implemented for you by declaring the class a data class
class User(val username: String)
class Comment(val message: String, val author: User)

fun main() {
  val user1 = User("bob")
  val comment1 = Comment("Hi there!", user1)

  val user2 = User("bob")
  val comment2 = Comment("Hi there!", user2)

  println(comment1)
  println(comment2)
  println(comment1 == comment2)
  println(comment1.hashCode())
  println(comment2.hashCode())
}
compared to
data class User(val username: String)
data class Comment(val message: String, val author: User)

fun main() {
  val user1 = User("bob")
  val comment1 = Comment("Hi there!", user1)

  val user2 = User("bob")
  val comment2 = Comment("Hi there!", user2)

  println(comment1)
  println(comment2)
  println(comment1 == comment2)
  println(comment1.hashCode())
  println(comment2.hashCode())
}
Let's look at something a little more complicated:
class User(val username: String)
class Comment(initialMessage: String, val author: User) {

  private val msgHist = mutableListOf(initialMessage)

  init {
    println("Logging initial message: $initialMessage")
  }

  var message: String
    get() {
      println("Message retrieved")
      return msgHist.last()
    }
    set(value: String) {
      println("Message edited: $value")
      msgHist.add(value)
    }

  fun compareHistory(startIndex: Int, endIndex: Int): List<String> {
    return listOf(msgHist[startIndex], msgHist[endIndex])
  }
}

fun main() {
  val comment = Comment("hi thre!", User("bob"))
  comment.message = "hi there!"
  comment.message = "Hi there!"
  println(comment.message)
  println(comment.compareHistory(0, 2))
}
  • We didn't want initialMessage to be a property, so ommitted the val/var, making it just a parameter
  • Parameters can be used in the init block or to initialize properties
  • Properties can have custom accessors
  • Here's an example of a function in a class
  • Also note that if you need more than the primary constructor, you can create secondary constructors like this:
class User(val username: String)
class Comment(messageHistory: List<String>, val author: User) {

  private val msgHist = messageHistory.toMutableList()

  init {
    println("Logging initial messages: $messageHistory")
  }

  constructor(initialMessage: String, author: User): this(listOf(initialMessage), author) {
    println("Secondary constructor used")
  }

  var message: String
    get() {
      println("Message retrieved")
      return msgHist.last()
    }
    set(value: String) {
      println("Message edited: $value")
      msgHist.add(value)
    }

  fun compareHistory(startIndex: Int, endIndex: Int): List<String> {
    return listOf(msgHist[startIndex], msgHist[endIndex])
  }
}

fun main() {
  val comment = Comment("hi thre!", User("bob"))
  comment.message = "hi there!"
  comment.message = "Hi there!"
  println(comment.message)
  println(comment.compareHistory(0, 2))
}
  • You are also not required to have a primary constructor
class User(val username: String)
class Comment {

  private val msgHist: MutableList<String>
  val author: User

  init {
    println("Logging init block usage")
  }

  constructor(initialMessage: String, author: User) {
    println("Logging initial message: $initialMessage")
    msgHist = mutableListOf(initialMessage)
    this.author = author
  }

  constructor(messageHistory: List<String>, author: User) {
    println("Logging initial messages: $messageHistory")
    msgHist = messageHistory.toMutableList()
    this.author = author
  }

  var message: String
    get() {
      println("Message retrieved")
      return msgHist.last()
    }
    set(value: String) {
      println("Message edited: $value")
      msgHist.add(value)
    }

  fun compareHistory(startIndex: Int, endIndex: Int): List<String> {
    return listOf(msgHist[startIndex], msgHist[endIndex])
  }
}

fun main() {
  val comment = Comment("hi thre!", User("bob"))
  comment.message = "hi there!"
  comment.message = "Hi there!"
  println(comment.message)
  println(comment.compareHistory(0, 2))
}
  • Also, you can have different visibilities for the get and set accessors
class User(val username: String)
class Comment {

  private val msgHist: MutableList<String>
  var author: User
    private set

  init {
    println("Logging init block usage")
  }

  constructor(initialMessage: String, author: User) {
    println("Logging initial message: $initialMessage")
    msgHist = mutableListOf(initialMessage)
    this.author = author
  }

  constructor(messageHistory: List<String>, author: User) {
    println("Logging initial messages: $messageHistory")
    msgHist = messageHistory.toMutableList()
    this.author = author
  }

  var message: String
    get() {
      println("Message retrieved")
      return msgHist.last()
    }
    set(value: String) {
      println("Message edited: $value")
      msgHist.add(value)
    }

  fun compareHistory(startIndex: Int, endIndex: Int): List<String> {
    return listOf(msgHist[startIndex], msgHist[endIndex])
  }

  fun anonymize() {
    author = User("anonymous")
  }
}

fun main() {
  val comment = Comment("hi thre!", User("bob"))
  comment.message = "hi there!"
  comment.message = "Hi there!"
  println(comment.message)
  println(comment.compareHistory(0, 2))
}
  • And you can access the backing field for a property with the field keyword
data class User(val username: String)
class Comment {

  private val msgHist: MutableList<String>
  var author: User
    private set(value: User) {
      println("User changed from $field to $value")
      field = value
    }

  init {
    println("Logging init block usage")
  }

  constructor(initialMessage: String, author: User) {
    println("Logging initial message: $initialMessage")
    msgHist = mutableListOf(initialMessage)
    this.author = author
  }

  constructor(messageHistory: List<String>, author: User) {
    println("Logging initial messages: $messageHistory")
    msgHist = messageHistory.toMutableList()
    this.author = author
  }

  var message: String
    get() {
      println("Message retrieved")
      return msgHist.last()
    }
    set(value: String) {
      println("Message edited: $value")
      msgHist.add(value)
    }

  fun compareHistory(startIndex: Int, endIndex: Int): List<String> {
    return listOf(msgHist[startIndex], msgHist[endIndex])
  }

  fun anonymize() {
    author = User("anonymous")
  }
}

fun main() {
  val comment = Comment("hi thre!", User("bob"))
  comment.message = "hi there!"
  comment.message = "Hi there!"
  println(comment.message)
  println(comment.compareHistory(0, 2))
  comment.anonymize()
}
  • You can also create infix functions
class Thread() {
  val comments = mutableListOf<Comment>()
}
data class User(val username: String)
class Comment {

  private val msgHist: MutableList<String>
  var author: User
    private set(value: User) {
      println("User changed from $field to $value")
      field = value
    }

  init {
    println("Logging init block usage")
  }

  constructor(initialMessage: String, author: User) {
    println("Logging initial message: $initialMessage")
    msgHist = mutableListOf(initialMessage)
    this.author = author
  }

  constructor(messageHistory: List<String>, author: User) {
    println("Logging initial messages: $messageHistory")
    msgHist = messageHistory.toMutableList()
    this.author = author
  }

  var message: String
    get() {
      println("Message retrieved")
      return msgHist.last()
    }
    set(value: String) {
      println("Message edited: $value")
      msgHist.add(value)
    }

  fun compareHistory(startIndex: Int, endIndex: Int): List<String> {
    return listOf(msgHist[startIndex], msgHist[endIndex])
  }

  fun anonymize() {
    author = User("anonymous")
  }

  infix fun on(thread: Thread) {
    thread.comments.add(this)
  }
}

fun main() {
  val thread = Thread()
  val comment = Comment("hi thre!", User("bob"))
  comment.message = "hi there!"
  comment.message = "Hi there!"
  println(comment.message)
  println(comment.compareHistory(0, 2))
  comment.anonymize()
  comment on thread
}
Visibility
  • Let's touch on visibility real quick
Modifier Class member Top-level declaration
public (default) Visible everywhere Visible everywhere
internal Visible in a module Visible in a module
protected Visible in subclass --
private Visible in a class Visible in a file

Enums
  • Enums are declared via enum class, other than that they're the same as in Java
enum class Colors {
  RED,
  GREEN,
  BLUE
}
Interfaces
  • Interfaces work very similarly to interfaces in Java
interface Clickable {
  fun click()
}

class Button : Clickable {
  override fun click() = println("Click!")
}

fun main() {
  Button().click()
}
  • Note the colon used to specify interface implementation
  • Also note the required override keyword
Inheritance
  • Unlike Java, Kotlin classes are final by default. Use the open keyword to make a class inheritable
  • Also, functions must be marked as open to be overridable
open class Animal {
  open fun speak() {
    println("...")
  }

  fun sleep() {
    println("zzz")
  }
}

class Dog : Animal() {
  override fun speak() {
    println("Bark")
  }
}

class Cat : Animal() {
  override fun speak() {
    println("Meow")
  }
}

fun main() {
  val animal = Animal()
  animal.speak()
  animal.sleep()

  val dog = Dog()
  dog.speak()
  dog.sleep()

  val cat = Cat()
  cat.speak()
  cat.sleep()
}
  • You can also use the final keyword for a final override
open class Animal {
  open fun speak() {
    println("...")
  }

  fun sleep() {
    println("zzz")
  }
}

open class Dog : Animal() {
  final override fun speak() {
    println("Bark")
  }
}

class Bulldog : Dog() {
  //Can't override speak()

  fun growl() {
    println("Grr")
  }
}

class Cat : Animal() {
  override fun speak() {
    println("Meow")
  }
}

fun main() {
  val animal = Animal()
  animal.speak()
  animal.sleep()

  val dog = Dog()
  dog.speak()
  dog.sleep()

  val bulldog = Bulldog()
  bulldog.speak()
  bulldog.sleep()
  bulldog.growl()

  val cat = Cat()
  cat.speak()
  cat.sleep()
}
  • You can also create abstract classes, where the base class can't be instantiated
abstract class Animal {
  open fun speak() {
    println("...")
  }

  fun sleep() {
    println("zzz")
  }
}

class Dog : Animal() {
  override fun speak() {
    println("Bark")
  }
}

class Cat : Animal() {
  override fun speak() {
    println("Meow")
  }
}

fun main() {
  val animal = Animal() //This won't compile

  val dog = Dog()
  dog.speak()
  dog.sleep()

  val cat = Cat()
  cat.speak()
  cat.sleep()
}
  • You can also create sealed classes, where subclasses are required to be defined in the same file
  • This limits subclasses to a pre defined set of classes
sealed class Animal {
  open fun speak() {
    println("...")
  }

  fun sleep() {
    println("zzz")
  }
}

class Dog : Animal() {
  override fun speak() {
    println("Bark")
  }
}

class Cat : Animal() {
  override fun speak() {
    println("Meow")
  }
}

fun main() {
  val dog = Dog()
  dog.speak()
  dog.sleep()

  val cat = Cat()
  cat.speak()
  cat.sleep()
}
Class delegation and the by keyword
  • Most experts encourage composition over inheritance, but many languages don't make composition easy
  • Kotlin addresses this
interface Door {
  fun enter()
}

class WoodDoor : Door {
  override fun enter() {
    println("Entered by door")
  }
}

interface Window {
  fun openWindow()
}

class ClearWindow : Window {
  override fun openWindow() {
    println("Opened the window")
  }
}

class House : Door by WoodDoor(), Window by ClearWindow()

class Shed(door: Door) : Door by door

class WindowDisplay(window: Window = ClearWindow()) : Window by window

fun main() {
  val house = House()
  house.enter()
  house.openWindow()

  val shed = Shed(WoodDoor())
  shed.enter()

  val windowDisplay = WindowDisplay()
  windowDisplay.openWindow()
}
Extension functions and properties
  • You can, in essence, extend classes that you don't have control over through extension functions
fun main() {
  val helloWorld = "Hello World"
  println(helloWorld.removeVowels())
  println(helloWorld.firstVowel)
}

val vowels = listOf('a', 'e', 'i', 'o', 'u')

fun String.removeVowels() =
  this.filter { it.toLowerCase() !in vowels }

val String.firstVowel
  get() = filter { it.toLowerCase() in vowels }.first()
  • You can also use this to give classes certain functionality only in the right circumstances
fun main() {
  val helloWorld = "Hello World"
  println(helloWorld.removeVowels())
  println(helloWorld.firstVowel)  //This won't work
  MySpecialClass(helloWorld) //Inside the class it will work
}

val vowels = listOf('a', 'e', 'i', 'o', 'u')

fun String.removeVowels() =
  this.filter { it.toLowerCase() !in vowels }

class MySpecialClass(str:String) {
    
    init {
      println(str.firstVowel)
    }
    
    private val String.firstVowel
      get() = filter { it.toLowerCase() in vowels }.first()
}
Lambdas
fun main() {
  val incrementer = { a:Int -> a + 1 }
  println(incrementer(1))
}
  • You define a lambda by surrounding it with curly braces
  • You define the parameters on the left side of the arrow
  • Lamdas can be multiline, and the result of the last statement is returned
fun main() {
  val incrementer = { a:Int ->
    println("Input: $a")
    a + 1
  }
  println(incrementer(1))
}
  • The Kotlin standard library has lots of predefined functions that use lambdas, such as map and filter
fun main() {
  val incrementer = { a:Int ->
    println("Input: $a")
    a + 1
  }
  println(listOf(1, 2, 3).map(incrementer))
}
  • You can of course pass the lambda into the method directly
fun main() {
  println(listOf(1, 2, 3).map({ a:Int ->
    println("Input: $a")
    a + 1
  }))
}
  • And since it is being passed in, you can infer the parameter type
fun main() {
  println(listOf(1, 2, 3).map({ a ->
    println("Input: $a")
    a + 1
  }))
}
  • And when there's only one parameter, Kotlin will provide a default parameter name that you can use, called it
fun main() {
  println(listOf(1, 2, 3).map({
    println("Input: $it")
    it + 1
  }))
}
  • When a lambda is the last parameter for a method, the lambda can be moved outside of the parentheses
fun main() {
  println(listOf(1, 2, 3).map() {
    println("Input: $it")
    it + 1
  })
}
  • And when a lambda is the only parameter for a method, you can remove the parentheses entirely
fun main() {
  println(listOf(1, 2, 3).map {
    println("Input: $it")
    it + 1
  })
}
  • Why does Kotlin support this? Because it allows you to create constructs that look like they're part of the language. For example:
fun main() {
  ifnot (false) {
    println("here")
  }
}

fun ifnot(conditional:Boolean, body:() -> Unit) {
  if (!conditional) body()
}
  • And here we also see how to create functions that take lambdas as parameters
  • body is a lambda that has no parameters, so it uses ()
  • It also returns nothing, so it specifies the return type Unit
  • To specify parameters and return types, we can do something like this:
fun main() {
  val result = 1.singleMap { it > 0 }
  println(result)
}

fun <T, R> T.singleMap(mapper:(T) -> R) =
  mapper(this)
  • Note that parenthesis around the parameter types is always required
  • We also get a small taste of generics here
  • We can also return a lambda from a function, like this:
fun main() {
  val add1 = curry(::add, 1)
  println(add1(2))
}

fun add(a:Int, b:Int):Int = a + b

fun <T, U, R> curry(function:(T, U) -> R, t:T): (U) -> R {
  return { u ->
    function(t, u)
  }
}
  • Also note that we can pass regularly defined functions as lambdas by prepending them with ::
  • You can also inline functions, and the compiler will inline the code with the lambda at compile time
fun main() {
  ifnot (false) {
    println("In ifnot")
  }
}

inline fun ifnot(conditional:Boolean, body:() -> Unit) {
  if (!conditional) body()
}
  • This allows for some speedups, since lambda objects don't need to be created at runtime
  • Note that an inlined function cannot call and pass its lambda to another function unless that function is also inlined
  • It also allows you do to some extra things in your lambda, such as using a return statement
fun main() {
  ifnot (false) {
    println("In ifnot")
    return
    println("After return") //It won't reach this code
  }
  println("After ifnot") //It won't reach this code, either
}

inline fun ifnot(conditional:Boolean, body:() -> Unit) {
  if (!conditional) body()
}
  • Note that if you wanted to just return from the lambda, you can do so as follows:
fun main() {
  ifnot (false) {
    println("In ifnot")
    return@ifnot
    println("After return") //It won't reach this code
  }
  println("After ifnot") //But it will reach this code
}

inline fun ifnot(conditional:Boolean, body:() -> Unit) {
  if (!conditional) body()
}
  • You can also specify a label, instead of using the method name:
fun main() {
  ifnot (false) marker@{
    println("In ifnot")
    return@marker
    println("After return") //It won't reach this code
  }
  println("After ifnot") //But it will reach this code
}

inline fun ifnot(conditional:Boolean, body:() -> Unit) {
  if (!conditional) body()
}
  • You can also create lambdas with receivers, which bind the this keyword to something specific in the lambda
  • You can think of them as extension functions as lambdas
  • Here's how the with statement in the standard library works (note that the standard library with statement has a little more to it)
fun main() {
  val result = with("Hello World") {
    substring(6)
  }
  println(result)
}

inline fun <T, R> with(receiver:T, block:T.() -> R): R {
  return receiver.block()
}

Tuesday, October 15, 2019

Intro to Kotlin, Part 1 Outline

So let's start where we always start, with a hello world application:
fun main() {
  println("Hello world!")
}
A couple of things to note here:
  • Just like in Java, the entry point into the code is with the main method
  • But unlike Java, the main method doesn't need to be in a class
  • It's also not necessary to specify the args parameter, though you can if you need it, like this:
fun main(args: Array<String>) {
  println("Hello world!")
}
Some other important points to note:
  • Functions are defined using the fun keyword
  • Parameters are defined by first specifying the name, followed by a colon and the type
Other important points for functions:
  • You specify the return type using a colon and a type at the end of the function
fun main() {
  println(greeting())
}

fun greeting(): String {
  return "Hello world!"
}
  • For functions that are a single line, you can also use expression bodies
fun main() {
  println(greeting())
}

fun greeting(): String =
  "Hello world!"
  • And for functions that do use an expression body, you can let it infer the return type
fun main() {
  println(greeting())
}

fun greeting() =
  "Hello world!"
  • You can also create parameters with default values
fun main() {
  println(greeting())
}

fun greeting(
  greeting: String = "Hello",
  name: String = "world"
) = "$greeting $name!"
  • And you can choose which parameters you want to provide via named arguments
fun main() {
  println(greeting(name = "Bob"))
}

fun greeting(
  greeting: String = "Hello",
  name: String = "world"
) = "$greeting $name!"
  • You can have a vararg parameter using the vararg keyword
fun main() {
  println(greeting("Hi", "Bob", "Sue"))
}

fun greeting(
  greeting: String = "Hello",
  vararg names: String = arrayOf("world")
) = "$greeting ${names.joinToString(" and ")}!"
  • Also if you want to pass an array into the vararg parameter, you need to explicitly use the spread operator
fun main() {
  val names = arrayOf("Bob", "Sue")
  println(greeting("Hi", *names))
}

fun greeting(
  greeting: String = "Hello",
  vararg names: String = arrayOf("world")
) = "$greeting ${names.joinToString(" and ")}!"
So with that, let's move on to variables:
  • The difference between val and var is that val can only be assigned to once, whereas var allows reassignment
fun main() {
  val myVal = "Test 1"
  myVal = "Test 2" //This line breaks
  
  var myVar = "Test 1"
  myVar = "Test 2" //This works fine
}
  • Kotlin is a statically typed language, but it is able to infer the type if it's being immediately assigned a value
  • If the value isn't immediately specified, then a type must be specified at declaration
fun main() {
  val myVal: String
  myVal = "Test 1"
}
  • Also note that the type cannot be changed
fun main() {
  var myVar = "Test 1"
  myVar = 2 //This is not allowed
}
  • All types in Kotlin come in nullable and non null variants
  • Non null types cannot be null
fun main() {
  val myVal: String = null // This is not allowed
}
  • You make a type nullable by adding a ?
fun main() {
  val myVal: String? = null // This works fine
}
  • You can't do anything with a nullable type until you've null checked it
fun main() {
  val greeting: String? = "Hello world!"
  greeting.substring(1, 3) // This doesn't compile
  if (greeting != null) {
    greeting.substring(1, 3) //This works fine
  }
}
  • There are operators that help to make null checks easier, such as the safe call operator
fun main() {
  val greeting: String? = "Hello world!"
  greeting?.substring(1, 3)
}
  • And the elvis operator
fun main() {
  val greeting: String? = "Hello world!"
  (greeting ?: "Some default").substring(1, 3)
}
Strings
  • You can insert a variable into a string template using the $ followed by the variable name
fun main() {
  println(greeting(name = "Bob"))
}

fun greeting(
  greeting: String = "Hello",
  name: String = "world"
) = "$greeting $name!"
  • And you can insert logic using the $ by sticking it inside {}
fun main() {
  println(greeting("Hi", "Bob", "Sue"))
}

fun greeting(
  greeting: String = "Hello",
  vararg names: String = arrayOf("world")
) = "$greeting ${names.joinToString(" and ")}!"
  • We can also create multi-line strings
fun main() {
  val insert = "Some insert"
  val str1 = """
    |I'm
    |  a multi-line
    |    string
    |      with indentation and and and insert: $insert
  """.trimMargin()
  println(str1)
}
If statements
  • The major difference between if statements in Java and Kotlin is that in Kotlin an if statement can return a value
fun main() {
  val result = if (1 > 2) {
    "Not happening"
  } else {
    "Here's the result"
  }
  println(result)
}
When statements
  • When statements are essentially a much more powerful switch statement
fun main() {
  val myVal: Any = "My Test"
  when (myVal) {
    is String -> println(myVal.substring(3))
    is Int -> println(myVal + 5)
    else -> println("Unknown")
  }

  val myOtherVal = "Test 2"
  val result = when (myOtherVal) {
    "Test 1" -> "Result 1"
    "Test 2" -> "Result 2"
    else -> "Other"
  }
  println(result)

  val result2 = when {
    myOtherVal == myVal -> "Equal"
    myOtherVal.startsWith("Test") -> "Test"
    else -> "Other"
  }
  println(result2)
}
While loops
  • While and do-while loops function the same way as they do in Java
For each loops
  • For loops don't exist in Kotlin, you instead do everything with for each loops
fun main() {
  for (i in 1..5) {
    print("$i ")
  }
  println()

  for (i in 1 until 5) {
    print("$i ")
  }
  println()

  for (i in 5 downTo 1) {
    print("$i ")
  }
  println()

  for (i in 1..5 step 2) {
    print("$i ")
  }
  println()
  println()

  val list = listOf("One", "Two", "Three")
  for (i in 0 until list.size) {
    println("$i: ${list[i]}")
  }
  println()

  for (i in list.indices) {
    println("$i: ${list[i]}")
  }
  println()

  for (item in list) {
    println(item)
  }
  println()
}
Object destructuring
  • You can use object destructuring to directly access the properties on a object
fun main() {
  val map = mapOf("key1" to "value1", "key2" to "value2")

  for (pair in map) {
    println("${pair.key} ${pair.value}")
  }
  println()

  for ((key, value) in map) {
    println("$key, $value")
  }
  println()
}
  • You can also use the underscore to omit properties that you don't care about
fun main() {
  val map = mapOf("key1" to "value1", "key2" to "value2")

  for ((key, _) in map) {
    println(key)
  }
  println()
}
Equality
  • The == in Kotlin is equivalent to the .equals call in Java
  • If you need Java's == functionality, in Kotlin you use ===
Try, catch, finally
  • This works similar to how it works in Java, but you can return a value from it
fun main() {
  val myVal = "Hello world"
  val result = try {
    myVal.toInt()
  } catch (e: Exception) {
    0
  }
  println(result)
}

Thursday, September 5, 2019

Dependency Injection Sans Reflection in Kotlin

A few weeks back we implemented a very basic dependency injection container in Kotlin using reflection (see here). But here's something cool about Kotlin: it's powerful and flexible enough to allow for a pretty solid dependency injection experience without even pulling out reflection or annotation processing. Check this out:
fun main() {
  val dep4 = Injector.dep4
  println(dep4)
}
​
object Injector {
  val dep4 by lazy { Dep4() }
  val dep1 by lazy { Dep1() }
  val dep3 by lazy { Dep3() }
  val dep2 by lazy { Dep2() }
}
​
class Dep1
data class Dep2(
  val dep1: Dep1 = Injector.dep1)
data class Dep3(
  val dep1: Dep1 = Injector.dep1,
  val dep2: Dep2 = Injector.dep2)
data class Dep4(
  val dep3: Dep3 = Injector.dep3)
And we could take this one step further, and allow for mocks to be injected for integration tests:
// Here's the implementation
​
fun main() = run()
// Running this main method will print this to the console:
// Dep4(dep3=Dep3(dep1=Dep1@610694f1, dep2=Dep2(dep1=Dep1@610694f1)))
​
fun run(injectorOverride: Injector? = null) {
  injectorOverride?.let {
    injector = it
  }
  val dep4 = inject().dep4
  println(dep4)
}
​
open class Injector {
  open val dep4 by lazy { Dep4() }
  open val dep1 by lazy { Dep1() }
  open val dep3 by lazy { Dep3() }
  open val dep2 by lazy { Dep2() }
}
​
private lateinit var injector: Injector
fun inject(): Injector {
  if (!::injector.isInitialized) {
    injector = Injector()
  }
  return injector
}
​
class Dep1
data class Dep2(
  val dep1: Dep1 = inject().dep1)
data class Dep3(
  val dep1: Dep1 = inject().dep1,
  val dep2: Dep2 = inject().dep2)
data class Dep4(
  val dep3: Dep3 = inject().dep3)
​
// Here's some hypothetical test code
​
import io.mockk.mockk
​
fun main() = run(TestInjector())
// Running this main method will print this to the console:
// Dep4(dep3=Dep3(dep1=Dep1@72c28d64, dep2=Dep2(#1)))
​
class TestInjector: Injector() {
  override val dep2 by lazy { mockk() }
}
This could of course be further improved upon, but it shows that in not all that many lines of code, we've got a pretty solid dependency injection setup.

Thursday, August 29, 2019

Kotlin in Action: Answers to Chapter 11 Exercises

Here's the answers to the chapter 11 exercises.

Exercise: Family Tree

fun man(description: Man.() -> Unit): Man =
    Man().apply(description)

fun woman(description: Woman.() -> Unit): Woman =
    Woman().apply(description)

class Man: Person() {
    var wife: Woman? = null

    fun wife(description: Woman.() -> Unit): Woman =
        woman(description).also { woman ->
            wife = woman
            woman.husband = this
        }

    override fun toString(): String =
        toString("Man", "wife", wife?.name)
}

class Woman: Person() {
    var husband: Man? = null

    fun husband(description: Man.() -> Unit): Man =
        man(description).also { man ->
            husband = man
            man.wife = this
        }

    override fun toString(): String =
        toString("Woman", "husband", husband?.name)
}

sealed class Person() {
    val name = Name()
    var father:Man? = null
    var mother:Woman? = null
    val children = mutableListOf<Person>()

    fun name(description: Name.() -> Unit): Name =
        name.apply(description)

    fun father(description: Man.() -> Unit): Man =
        man(description).also { man ->
            father = man
            man.children.add(this)
        }

    fun mother(description: Woman.() -> Unit): Woman =
        woman(description).also { woman ->
            mother = woman
            woman.children.add(this)
        }

    fun son(description: Man.() -> Unit): Man =
        man(description).also { man ->
            children.add(man)
            man.addParent(this)
        }

    fun daughter(description: Woman.() -> Unit): Woman =
        woman(description).also { woman ->
            children.add(woman)
            woman.addParent(this)
        }

    protected fun addParent(parent: Person) {
        when (parent) {
            is Man -> {
                father = parent
                parent.wife?.let { mother ->
                    this.mother = mother
                    mother.children.add(this)
                }
            }
            is Woman -> {
                mother = parent
                parent.husband?.let { father ->
                    this.father = father
                    father.children.add(this)
                }
            }
        }
    }

    protected fun toString(
        typeName: String,
        spouseTypeName: String,
        spouseName: Name?
    ): String =
        "$typeName(" +
            "name=$name, " +
            "father=${father?.name}, " +
            "mother=${mother?.name}, " +
            "$spouseTypeName=$spouseName, " +
            "children=${children.map {it.name}}" +
        ")"
}

data class Name(var first:String="",
                var middle:String="",
                var last:String="")

Wednesday, August 28, 2019

Kotlin in Action: Chapter 11 Exercises

Chapter 11 is about DSLs, and in addition to going over multiple example DSLs, it also gives us two more tools to use to assist in creating them: lambdas with receivers and the invoke convention. This is a topic that has a lot of depth to it, and we won't be able to cover all possibilities and options in a simple exercise, but with this exercise I hope to cover something with enough complexity to show the power and potential, while also keeping it simple enough to not be overwhelming.

Exercise: Family Tree

With this exercise we'll cover creating a simple family tree DSL. Note that for the sake of simplicity we'll stick with a very basic definition of a family where a father and mother have kids together, even though the definition of family can be much more complex in real life. So in this DSL, we can start by specifying a man or woman, and then in that person's definition a name can be specified, as well as father, mother, son, and daughter. For a man a wife can be specified, and for a woman a husband can be specified. Behind the scenes the DSL should hook up all the connections so that when, say, a father is specified, then the current person is added as a child of the person specified as the father, in addition to the father being added to the current person. So ultimately we should be able to specify code like the following with the DSL:
val john = man {
  name {
    first = "John"
    last = "Smith"
  }
  father {
    name {
      first = "Bob"
      middle = "Bobby"
      last = "Smith"
    }
  }
  mother {
    name {
      first = "Jane"
      last = "Doe"
    }
  }
  wife {
    name {
      first = "Martha"
      middle = "Molly"
      last = "May"
    }
  }
  son {
    name { first = "Joey"; last = "Smith" }
  }
  daughter {
    name { first = "Susie"; middle = "Que"; last = "Smith" }
  }
}

val sally = woman {
  name { first = "Sally" }
  husband {
    mother {
      father {
        name { first = "Richard" }
      }
    }
  }
}
As usual, answers to the exercise will be shared in a follow up post. And here's the answers.

Wednesday, August 21, 2019

Kotlin in Action: Answers to Chapter 10 Exercises

Here's the answers to the chapter 10 exercise.

Exercise: Basic Dependency Injection

So here's the implementation of the build method:
val objMap = mutableMapOf<KClass<*>, Any>()

inline fun <reified T: Any> build() =
  build(T::class)

@Suppress("UNCHECKED_CAST")
fun <T: Any> build(klass: KClass<T>): T {
  if (objMap.containsKey(klass)) {
    return objMap[klass] as T
  } else {
    with(klass.constructors.first()) {
      val paramObjs = parameters
        .map { build(it.type.classifier as KClass<*>) }
        .toTypedArray()
      val obj = call(*paramObjs)
      objMap[klass] = obj
      return obj
    }
  }
}
For the bonus exercise of adding a @Singleton annotation, we'd want to define the annotation like this:
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class Singleton
And then rework the build method like this:
@Suppress("UNCHECKED_CAST")
fun <T: Any> build(klass: KClass<T>): T {
  if (objMap.containsKey(klass)) {
    return objMap[klass] as T
  } else {
    with(klass.constructors.first()) {
      val paramObjs = parameters
        .map { build(it.type.classifier as KClass<*>) }
        .toTypedArray()
      val obj = call(*paramObjs)
      if (klass.findAnnotation<Singleton>() != null) {
        objMap[klass] = obj
      }
      return obj
    }
  }
}
With this code in place, if you run our example with Nodes 1 through 4, you'll notice that the Node1 ids are different when it doesn't have the @Singleton annotation, but they are the same if you annotate the Node1 class with @Singleton.

Tuesday, August 20, 2019

Kotlin in Action: Chapter 10 Exercises

Chapter 10 covers annotations and reflection. To get a better grasp on this, we'll implement a very basic dependency injection framework using reflection.

Basic Dependency Injection

We'll implement a very bare bones dependency injection framework. With it you will be able to supply a class to a build method, and it will instantiate that class, as well as its dependencies.We'll limit what scenarios it will work with to simplify the problem. All classes are treated as singletons, and only a single instance of each class will be created. If a class requests a dependency that has already been instantiated, then it will be reused. We're only requiring this to work with Kotlin classes, so Java classes don't need to be considered. It'll also be assumed that the classes will have a single constructor which defines the class dependencies. We also won't handle dependencies where there could be multiple different instances of the class (so no String, Number, or other such dependencies). We also won't worry about detecting circular dependencies.

So as such, if we have classes that look like this:
class Node1
data class Node2(val node1: Node1)
data class Node3(val node1: Node1, val node2: Node2)
data class Node4(val node3: Node3)
And ran this code:
fun main() {
  val node4 = build<Node4>()
  println(node4)
}
We should get something like the following printed to the console (note that the Node1 Identifiers need to match):
Node4(node3=Node3(node1=Node1@18d87d80, node2=Node2(node1=Node1@18d87d80)))
Also note that since kotlin reflection is in a different jar, you'll need to add the following to your build.gradle file:
implementation "org.jetbrains.kotlin:kotlin-reflect:1.3.41"
As a bonus activity, after we've completed this we can modify it and add a Singleton annotation, and make it so that the class instance reference is only kept and reused if the class is annotated as Singleton, otherwise it will create a new instance each time it is needed. This will allow us to play with defining and referencing annotations.

As usual, the answers will be posted in a followup post. And here's the answers.

Thursday, August 15, 2019

Kotlin in Action: Answers to Chapter 9 Exercises

Here's the answers to the chapter 9 exercises.

Exercise 1: Covariance and Contravariance

So similar to how Java uses the keywords extends and super to define covariance and contravariance, Kotlin uses the keywords out and in, respectively. In a way, this makes more sense, since covariance is usually applied when producing a type, and contravariance is applied when consuming a type. In Java, the acronym PECS for "Producer Extends Consumer Super" is usually used to remember this and keep things straight, whereas is Kotlin the association is intuitive. The out keyword is used when producing, and the in keyword is used when consuming. So in Kotlin, we can create the addDogs method like this:
fun addDogs(list: MutableList<in Dog>,
            toAdd: List<out Dog>) {
  list.addAll(toAdd)
}
Though note that with this method, many IDEs, like Intellij, will give you a warning on the out keyword that says something like this: "Projection is redundant: the corresponding type parameter of List has the same variance." This is because Kotlin allows for declaration site variance, and since in Kotlin the List type is immutable (which in turn means that it has no methods that consume its type, only methods that produce), the List type is already marked as covariant. If you look at List's definition, you'll see something like this:
public interface List<out E> : Collection<E> {
  ...
}
So since the List type already declared the variance, we don't need to, which allows up to remove the out keyword and gives us this:
fun addDogs(list: MutableList<in Dog>,
            toAdd: List<Dog>) {
  list.addAll(toAdd)
}
Do note that if we had declared the toAdd parameter as a MutableList, then it would have required the out keyword. This is because it has both producer methods (like the get method) and consumer methods (like the add method). As such, it is neither covariant nor contravariant, and is instead invariant, which means that only the specific type can be used (if a list of dogs is requested, neither a list of animals, nor a list of poodles, would be acceptable; only a list of dogs).

But of course, just because a type is invariant, it doesn't mean that it can't be used in a covariant or contravariant way, it just means that it requires use site variance, which is why the in keyword is required on the MutableList in our addDogs function.

Now as a final bonus, let's convert the addDogs function to be an extension function. It can be done like this:
fun MutableList<in Dog>.addDogs(toAdd: List<Dog>) {
  addAll(toAdd)
}
And it can be used like this:
animals.addDogs(dogs)
dogs.addDogs(dogs)
animals.addDogs(chihuahuas)
dogs.addDogs(chihuahuas)
animals.addDogs(poodles)
dogs.addDogs(poodles)

Exercise 2: Reified Types

Well the explanations around the last example were a little long winded, but the example with reified types should be pretty straightforward. In short, reified types can be used in ways that a normal type can't, but reified types can only be used in inline functions. So to create our cast method, we'd want to do something like this:
inline fun <reified T> Observable<*>.cast() =
  cast(T::class.java)

Kotlin in Action: Chapter 9 Exercises

Chapter 9 covers generics, including topics such as invariance, covariance, contravariance, inlining, and reified types. We'll go over two different exercises to help us better understand some of these points in Kotlin.

Exercise 1: Covariance and Contravariance

To better understand covariance and contravariance, we'll start by creating an easy to understand hierarch of types:
open class Animal
open class Dog: Animal()
class Chihuahua: Dog()
class Poodle: Dog()
open class Cat: Animal()
class Persian: Cat()
class Birman: Cat()
So in this structure, the top level is Animal. A Dog is an Animal, and a Cat is an Animal. A Chihuahua is a Dog (which in turn is an Animal), and same goes for a Poodle. Persian and Birman are both Cats.

With this type hierarchy, we can create lists that can contain different categories of animals:
val animals = mutableListOf<Animal>()
val dogs = mutableListOf<Dog>()
val cats = mutableListOf<Cat>()
val chihuahuas = mutableListOf<Chihuahua>()
val poodles = mutableListOf<Poodle>()
val persians = mutableListOf<Persian>()
val birmans = mutableListOf<Birman>()
Now what we want to do is create a function called addDogs that will take in two lists. The first list should be any list that any kind of dog can be added to (either a list of animals or a list of dogs would fit the bill). The second list should be a list that is guaranteed to only have dogs in it (in this case, either a list of dogs, a list of chihuahuas, or a list of poodles would work). This function will take all the dogs in the second list and add them to the first list. Such a function would look like this in Java:
public void addDogs(List<? super Dog> list,
                    List<? extends Dog> toAdd) {
    list.addAll(toAdd);
}
This would in turn allow for the following legal uses:
addDogs(animals, dogs)
addDogs(dogs, otherDogs)
addDogs(animals, chihuahuas)
addDogs(dogs, chihuahuas)
addDogs(animals, poodles)
addDogs(dogs, poodles)
While the following scenarios won't compile:
addDogs(chihuahuas, dogs) //chihuahuas can't accept any kind of dog
addDogs(dogs, animals) //animals might contain cats
addDogs(cats, persians) //while cats can hold persians, neither fullfill the contract,
                        //cats can't accept dogs, and persians doesn't contain dogs
So can we write the addDogs method in Kotlin? As a bonus activity, we can also explore converting it into an extension function.

Exercise 2: Reified Types

For this exercise, we'll use the RxJava2 library, so you'll want to add the following to your gradle file:
compile group: 'io.reactivex.rxjava2', name: 'rxjava', version: '2.2.11'
We'll also make use of the same animal classes from the above example, but we'll add a bark method to the Dog class, for illustrative purposes:
open class Dog: Animal() {
  fun bark() {
    println("bark")
  }
}
Now RxJava has a cast operator so that you can cast an object from one type to another in its chain. For instance, if you have an animal that you know is a Poodle and you need to cast it to Dog so that you can all the bark function on it:
val animal = Poodle() as Animal
Observable.just(animal)
  .cast(Dog::class.java)
  .subscribe{ dog -> dog.bark() }
Now the fact that the cast method takes in a Java class parameter makes it kind of long and ugly. Let's see if we can write an extension function that would allow us to do this instead:
val animal = Poodle() as Animal
Observable.just(animal)
  .cast<Dog>()
  .subscribe{ dog -> dog.bark() }
This will require us to make use of reified types.

As always, the answers will be given in a follow up post. And here's the answers.

Wednesday, August 7, 2019

Kotlin in Action: Answers to Chapter 8 Exercises

Here's the answer to the chapter 8 exercise.

Exercise: Filter Out

So first off, we want to be able to do this:
listOf(1, 2, 3, 4, 5)
  .filterOut { it == 2 }
Where the result would be a list of [1, 3, 4, 5] where 2 was filtered out, because it matched the predicate in the filterOut method. To do this, we can do the following:
public inline fun  Iterable.filterOut(predicate: (T) -> Boolean) =
  filter { !predicate(it) }
Note that we we were able to mark this function as inline, so in essense, when the code compiles, it ends up being equivalent to if you had written:
listOf(1, 2, 3, 4, 5)
  .filter { !(it == 2) }
Though that's actually a lie, because the filter method itself is inline, and as such when the code compiles, it's more like this:
val source = listOf(1, 2, 3, 4, 5)
val destination = ArrayList<Int>()
for (element in source) if (!(element == 2)) destination.add(element)
So you get the speed and efficiency benefits as if you had actually written the code like this, while still getting the niceness how how the code was written above.

But we're not done with this example just yet, because as we learned in chapter 5, you can also do the same operations with a sequence. A sequence will simply try to optimize all the operations being performed to a collection, and our solution wouldn't be complete without having a filter out method for Sequence. So we ultimately want to be able to do the following:
listOf(1, 2, 3, 4, 5)
  .asSequence()
  .filterOut { it == 2 }
  .toList()
To do this, we'll want to write the following code:
public fun  Sequence.filterOut(predicate: (T) -> Boolean) =
  filter { !predicate(it) }
It's nearly identical to the filter out method that we wrote for the Iterable, but you'll notice that we didn't use inline here. That's because we can't inline this function, because the filter function on the Sequence is not inlined. If you try to put inline on this function, you will get a compile time error. The reason why the filter function is not inline on the sequence is so that the sequence has the flexibility to optimize the order in which different calls are made in order to minimize the amount of work that needs to be done when processing large data sets.

Now if you really had your heart set on inlining this function, you could do the following:
public inline fun  Sequence.filterOut(noinline predicate: (T) -> Boolean) =
  filter { !predicate(it) }
Which essentially tells the compiler that the lambda being passed down into the filter method shouldn't be inlined. Now while this can be useful in a number of situations, in this particular case it really isn't all that useful, because it won't be able to eliminate any of the previously mentioned overhead. After compilation, it's essentially as if you had written the following code:
listOf(1, 2, 3, 4, 5)
  .asSequence()
  .filter { !(it == 2) }
  .toList()
And it can't optimize any further past that because the filter function is not inlined.

And so with that, the final solution is this:
public inline fun  Iterable.filterOut(predicate: (T) -> Boolean) =
  filter { !predicate(it) }

public fun  Sequence.filterOut(predicate: (T) -> Boolean) =
  filter { !predicate(it) }

Kotlin in Action: Chapter 8 Exercises

Chapter 8 covers higher order functions, which is just a fancy way of saying functions that can take a function as a parameter or return a function (or both). it also covers other points such as inline functions, non local returns, and many other such points.

Exercise: Filter Out

So for a simple exercise to test our knowledge out, let's create a method that takes in a lambda. We'll create a filterOut method, which works similarly to the filter method, but instead of keeping anything that matches the predicate, it will remove anything that matches the predicate, and keep everything else.

As usual, an answer will be given in a follow up post. And the answer can be found here.

Thursday, August 1, 2019

Kotlin in Action: Answers to Chapter 7 Exercises

Here's the answer to the chapter 7 exercise.

Exercise: Operator Overloads for Json Manipulation

Immutable

operator fun JsonObject.plus(other: JsonObject): JsonObject =
  copy().apply {
    other.forEach { (key, value) -> put(key, value) }
  }

operator fun JsonObject.plus(pair: Pair<String, *>): JsonObject =
  copy().put(pair.first, pair.second)

operator fun JsonObject.minus(key: String): JsonObject =
  copy().apply { remove(key) }

operator fun JsonObject.minus(keys: Collection<String>): JsonObject =
  copy().apply {
    keys.forEach { remove(it) }
  }

operator fun JsonArray.plus(other: JsonArray): JsonArray =
  copy().addAll(other)

operator fun JsonArray.plus(item: Any?): JsonArray =
  copy().add(item)

operator fun JsonArray.minus(other: JsonArray): JsonArray =
  copy().apply {
    other.forEach { remove(it) }
  }

operator fun JsonArray.minus(item: Any?): JsonArray =
  copy().apply { remove(item) }

operator fun JsonArray.minus(index: Int): JsonArray =
  copy().apply { remove(index) }

Mutable

operator fun JsonObject.plusAssign(other: JsonObject) =
  other.forEach { (key, value) -> put(key, value) }

operator fun JsonObject.plusAssign(pair: Pair<String, *>) {
  put(pair.first, pair.second)
}

operator fun JsonObject.minusAssign(key: String) {
  remove(key)
}

operator fun JsonObject.minusAssign(keys: Collection<String>) =
  keys.forEach { remove(it) }

operator fun JsonArray.plusAssign(other: JsonArray) {
  addAll(other)
}

operator fun JsonArray.plusAssign(item: Any?) {
  add(item)
}

operator fun JsonArray.minusAssign(other: JsonArray) {
  other.forEach { remove(it) }
}

operator fun JsonArray.minusAssign(item: Any?) {
  remove(item)
}

operator fun JsonArray.minusAssign(index: Int) {
  remove(index)
}

Wednesday, July 31, 2019

Kotlin in Action: Chapter 7 Exercises

Starting with chapter 7 the Kotlin in Action book starts moving on to more advanced topics that may or may not have direct correlations with the Java language, so for the rest of the chapters in the book I will present a problem to solve instead of code to convert. So without further ado, here's the exercise for chapter 7:

Exercise: Operator Overloads for Json Manipulation

This will be yet another problem involving json (What can I say? I'm a web developer and it's kind of relevant to web development.), but to mix things up, we'll work with the Vertx library's JsonObject and JsonArray this time. So you'll want to add these two dependencies to your build.gradle file:
compile group: 'io.vertx', name: 'vertx-core', version: '3.8.0'
compile group: 'io.vertx', name: 'vertx-lang-kotlin', version: '3.8.0'
From here there are two paths that can be explored: one where the operator overloads that we write treat the JsonObject or JsonArray as immutable, and the other where the operator overloads that we write treat the JsonObject or JsonArray as mutable. Note that these two approaches don't play well together, and should be explored separately.

Immutable

In the immutable scenario, we'll want to overload the plus and minus operators for both JsonObject and JsonArray. In all cases the JsonObjects and/or JsonArrays won't be modified, but a new one will be created and returned instead. When adding two JsonObjects together, the fields from both JsonObjects will be added to the new JsonObject. When there are overlapping fields, the fields from the latter JsonObject will take precedence. When a JsonObject and a Pair are added together, then the Pair is added as a field on the JsonObject, or replaces an existing field if there's overlap. Subtracting a String from a JsonObject will subtract the field with that key from the JsonObject. Subtracting a Collection of Strings from a JsonObject will remove all fields that have a corresponding key.

Adding two JsonArrays should return a JsonArray with all the values from both JsonArrays, with the values from the former JsonArray being first and the values from the latter JsonArray being last. Adding any object to the JsonArray will append that object to the end of the JsonArray. Subtracting any object from a JsonArray will remove the firstinstance of that object from the JsonArray. Subtracting one JsonArray from another will remove all of the values in the second JsonArray from the first JsonArray. Something to explore would be whether or not it would be meaningful to implement the operator such that subtracting an int from a JsonArray will remove the element at that index.

Mutable

This should follow the same general rules as defined by the immutable problem above except that it will modify the first JsonObject or JsonArray. This will implement the += and -= operators directly, and not implement the + or - operators.

As usual, a solution will be posted in a followup update. And here's the answers.

Wednesday, July 24, 2019

Kotlin in Action: Answers to Chapter 6 Exercises

And here's the answer to the chapter 6 exercise.

Exercise 1: Promo Emails

import java.lang.Exception
import java.lang.RuntimeException

fun main() {
  val jsmith432 = User(
    "jsmith432",
    "John",
    "Smith",
    "john.smith@yahoo.com"
  )
  val jdoe = User(
    username = "jdoe",
    emailAddress = "jdoe@gmail.com"
  )
  val unknown = User(emailAddress = "abc123@hotmail.com")
  val bob = User(
    username = "bob",
    firstName = "Bob"
  )
  val users = listOf(jsmith432, jdoe, unknown, bob)
  val usersWithNull = listOf(jsmith432, null, jdoe)

  try {
    sendSalesPromotion(null)
  } catch (e: Exception) {
    println("${e.message}\n")
  }

  sendSalesPromotion(jsmith432)
  sendSalesPromotion(jdoe)
  sendSalesPromotion(unknown)
  sendSalesPromotion(bob)

  try {
    sendSalesPromotions(null)
  } catch (e: Exception) {
    println("${e.message}\n")
  }

  try {
    sendSalesPromotions(usersWithNull)
  } catch (e: Exception) {
    println("${e.message}\n")
  }

  sendSalesPromotions(users)
}

fun sendSalesPromotions(users: List<User?>?) {
  users ?: nullParameterException("users")
  users.forEach { it ?: nullParameterException("user") }
  users.forEach(::sendSalesPromotion)
}

fun sendSalesPromotion(user: User?) {
  user ?: nullParameterException("user")
  user.emailAddress?.let {
    val subject = "Blowout XYZ Widget Sale!"
    val message =
      """Dear ${user.firstName ?: user.username ?: "Valued Customer"}
        |
        |We're having a massive sale on XYZ Widgets!
        |95% Off! Get yours today!""".trimMargin()
    sendEmail(Email(it, subject, message))
  }
}

fun nullParameterException(paramName: String): Nothing {
  throw RuntimeException("Param $paramName is null")
}

fun sendEmail(email: Email) {
  println("""Email sent:
            |  emailAddress: ${email.emailAddress}
            |  subject: ${email.subject}
            |  message: ${email.message}
            |""".trimMargin()
  )
}

data class User(
  val username: String? = null,
  val firstName: String? = null,
  val lastName: String? = null,
  val emailAddress: String? = null
)

data class Email(
  val emailAddress: String,
  val subject: String,
  val message: String
)

Tuesday, July 23, 2019

Kotlin in Action: Chapter 6 Exercises

Here's a practice problem for chapter 6.

Exercise 1: Promo Emails

In this example we have multiple users and we want to send a promo email advertising a sale to them (for simplicity's sake, sending an email will just be printing to the console), so long as they have an email on record. If we have the user's first name, then the email will address them by first name. Otherwise we will address them using their username, so long as we have that. Barring that, we will simply address them as "Valued Customer".

If a user is null, or a list of users is null, or a user within a list of users is null, then we will throw an exception. (This is a bit contrived, I know, but it allows us to better test out a few things from chapter 6.)

Note that this conversion will primarily focus on converting the sendSalesPromotion and sendSalesPromotions methods and supporting methods. As such I will provide a partial Kotlin conversion that converts everything except these methods and their supporting methods.
import java.util.ArrayList;
import java.util.List;

public class JavaExample {

  public static void main(String[] args) {
    User jsmith432 = new User("jsmith432",
                              "John",
                              "Smith",
                              "john.smith@yahoo.com");
    User jdoe = new User("jdoe",
                         null,
                         null,
                         "jdoe@gmail.com");
    User unknown = new User(null,
                            null,
                            null,
                            "abc123@hotmail.com");
    User bob = new User("bob",
                        "Bob",
                        null,
                        null);
    List<User> users = new ArrayList<>();
    users.add(jsmith432);
    users.add(jdoe);
    users.add(unknown);
    users.add(bob);
    List<User> usersWithNull = new ArrayList<>();
    usersWithNull.add(jsmith432);
    usersWithNull.add(null);
    usersWithNull.add(jdoe);

    try {
      sendSalesPromotion(null);
    } catch (Exception e) {
      System.out.println(e.getMessage() + "\n");
    }

    sendSalesPromotion(jsmith432);
    sendSalesPromotion(jdoe);
    sendSalesPromotion(unknown);
    sendSalesPromotion(bob);

    try {
      sendSalesPromotions(null);
    } catch (Exception e) {
      System.out.println(e.getMessage() + "\n");
    }

    try {
      sendSalesPromotions(usersWithNull);
    } catch (Exception e) {
      System.out.println(e.getMessage() + "\n");
    }

    sendSalesPromotions(users);
  }

  public static void sendSalesPromotions(List<User> users) {
    if (users != null) {
      if(users.stream().anyMatch(user -> user == null)) {
        nullParameterException("user");
      }
      users.forEach(JavaExample::sendSalesPromotion);
    } else {
      nullParameterException("users");
    }
  }

  public static void sendSalesPromotion(User user) {
    if (user != null) {
      if (user.getEmailAddress() != null) {
        String emailAddress = user.getEmailAddress();
        String subject = "Blowout XYZ Widget Sale!";
        String message = "Dear " +
            (user.getFirstName() != null ?
                user.getFirstName() :
                user.getUsername() != null ?
                    user.getUsername() :
                    "Valued Customer") + "\n\n" +
            "We're having a massive sale on XYZ Widgets!\n" +
            "95% Off! Get yours today!";
        sendEmail(new Email(emailAddress, subject, message));
      }
    } else {
      nullParameterException("user");
    }
  }

  public static void nullParameterException(String paramName) {
    throw new RuntimeException("Param " + paramName + " is null");
  }

  public static void sendEmail(Email email) {
    System.out.println("Email sent:\n" +
        "  emailAddress: " + email.getEmailAddress() + "\n" +
        "  subject: " + email.getSubject() + "\n" +
        "  message: " + email.getMessage() + "\n");
  }
}

public class User {

  private final String username;
  private final String firstName;
  private final String lastName;
  private final String emailAddress;

  public User(String username,
              String firstName,
              String lastName,
              String emailAddress) {
    this.username = username;
    this.firstName = firstName;
    this.lastName = lastName;
    this.emailAddress = emailAddress;
  }

  public String getUsername() {
    return username;
  }

  public String getFirstName() {
    return firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public String getEmailAddress() {
    return emailAddress;
  }
}

public class Email {
  private final String emailAddress;
  private final String subject;
  private final String message;

  public Email(String emailAddress,
               String subject,
               String message) {
    this.emailAddress = emailAddress;
    this.subject = subject;
    this.message = message;
  }

  public String getEmailAddress() {
    return emailAddress;
  }

  public String getSubject() {
    return subject;
  }

  public String getMessage() {
    return message;
  }
}
And here's the partial Kotlin conversion
import java.lang.Exception

fun main() {
  val jsmith432 = User("jsmith432",
                       "John",
                       "Smith",
                       "john.smith@yahoo.com")
  val jdoe = User(username = "jdoe",
                  emailAddress = "jdoe@gmail.com")
  val unknown = User(emailAddress = "abc123@hotmail.com")
  val bob = User(username = "bob",
                 firstName = "Bob")
  val users = listOf(jsmith432, jdoe, unknown, bob)
  val usersWithNull = listOf(jsmith432, null, jdoe)

  try {
    sendSalesPromotion(null)
  } catch (e: Exception) {
    println("${e.message}\n")
  }

  sendSalesPromotion(jsmith432)
  sendSalesPromotion(jdoe)
  sendSalesPromotion(unknown)
  sendSalesPromotion(bob)

  try {
    sendSalesPromotions(null)
  } catch (e: Exception) {
    println("${e.message}\n")
  }

  try {
    sendSalesPromotions(usersWithNull)
  } catch (e: Exception) {
    println("${e.message}\n")
  }

  sendSalesPromotions(users)
}

data class User(val username: String? = null,
                val firstName: String? = null,
                val lastName: String? = null,
                val emailAddress: String? = null)

data class Email(val emailAddress: String,
                 val subject: String,
                 val message: String)
The answer can be found here.

Wednesday, July 17, 2019

Kotlin in Action: Answers to Chapter 5 Exercises

So here are the answers to the last post.

Exercise 1: Json Manipulation

import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonParser

fun main() {
  val usersJson = """[
                    |  {
                    |    "id": "543",
                    |    "username": "john123",
                    |    "firstName": "John",
                    |    "lastName": "Smith",
                    |    "email": "john@smith.com"
                    |  },   {
                    |    "id": "438",
                    |    "username": "janedoe5",
                    |    "firstName": "Jane",
                    |    "lastName": "Doe",
                    |    "email": "jane.doe@gmail.com"
                    |  }
                    |]""".trimMargin()
  val booksJson = """[
                    |  {"id": "1", "title": "Kotlin in Action"},
                    |  {"id": "2", "title": "Kotlin in Action"},
                    |  {"id": "3", "title": "Learning RxJava"},
                    |  {"id": "4", "title": "Refactoring"},
                    |  {"id": "5", "title": "Grokking Algorithms"},
                    |  {"id": "6", "title": "Code Complete"}
                    |]""".trimMargin()
  val checkoutsJson = """[
                        |  {"userId": "543", "bookId": "1"},
                        |  {"userId": "543", "bookId": "5"},
                        |  {"userId": "438", "bookId": "2"},
                        |  {"userId": "438", "bookId": "3"}
                        |]""".trimMargin()

  val parser = JsonParser()
  val users = parser.parse(usersJson).asJsonArray
  val books = parser.parse(booksJson).asJsonArray
  val checkouts = parser.parse(checkoutsJson).asJsonArray

  val userCheckouts = users.map { it.asJsonObject }
    .flatMap { user -> checkouts.map { it.asJsonObject }
      .filter { checkout -> user["id"] == checkout["userId"] }
      .map { checkout -> JsonObject().apply {
        addProperty("firstName", user["firstName"].asString)
        addProperty("lastName", user["lastName"].asString)
        addProperty("bookId", checkout["bookId"].asString)
      } }
    }
    .flatMap { userCheckout -> books.map { it.asJsonObject }
      .filter { book -> userCheckout["bookId"] == book["id"] }
      .map { book -> userCheckout.deepCopy().apply {
        addProperty("title", book["title"].asString)
        remove("bookId")
      } }
    }
    .fold(JsonArray()) { array, obj -> array.apply { add(obj) }}
  println(userCheckouts)
}
Now honestly the java code wasn't very clean and readable to start with, so if we wanted to clean it up a little we could do the following:
import com.google.gson.JsonArray
import com.google.gson.JsonObject
import com.google.gson.JsonParser

fun main() {
  val usersJson = """[
                    |  {
                    |    "id": "543",
                    |    "username": "john123",
                    |    "firstName": "John",
                    |    "lastName": "Smith",
                    |    "email": "john@smith.com"
                    |  },   {
                    |    "id": "438",
                    |    "username": "janedoe5",
                    |    "firstName": "Jane",
                    |    "lastName": "Doe",
                    |    "email": "jane.doe@gmail.com"
                    |  }
                    |]""".trimMargin()
  val booksJson = """[
                    |  {"id": "1", "title": "Kotlin in Action"},
                    |  {"id": "2", "title": "Kotlin in Action"},
                    |  {"id": "3", "title": "Learning RxJava"},
                    |  {"id": "4", "title": "Refactoring"},
                    |  {"id": "5", "title": "Grokking Algorithms"},
                    |  {"id": "6", "title": "Code Complete"}
                    |]""".trimMargin()
  val checkoutsJson = """[
                        |  {"userId": "543", "bookId": "1"},
                        |  {"userId": "543", "bookId": "5"},
                        |  {"userId": "438", "bookId": "2"},
                        |  {"userId": "438", "bookId": "3"}
                        |]""".trimMargin()

  val parser = JsonParser()
  val users = parser.parse(usersJson).asJsonArray
  val books = parser.parse(booksJson).asJsonArray
  val checkouts = parser.parse(checkoutsJson).asJsonArray

  val userCheckouts = users.map { it.asJsonObject }
    .flatMap { user -> findUserCheckouts(user, checkouts)
      .map { checkout -> buildUserCheckout(user, checkout) }
    }
    .flatMap { userCheckout -> findCheckoutBooks(userCheckout, books)
      .map { book -> swapBookIdForTitleOnUserCheckout(userCheckout, book) }
    }
    .toJsonArray()
  println(userCheckouts)
}

private fun findUserCheckouts(
  user: JsonObject,
  checkouts: JsonArray
): List =
  checkouts.map { it.asJsonObject }
    .filter { checkout -> user["id"] == checkout["userId"] }

private fun buildUserCheckout(
  user: JsonObject,
  checkout: JsonObject
): JsonObject =
  JsonObject().apply {
    addProperty("firstName", user["firstName"].asString)
    addProperty("lastName", user["lastName"].asString)
    addProperty("bookId", checkout["bookId"].asString)
  }

private fun findCheckoutBooks(
  userCheckout: JsonObject,
  books: JsonArray
): List =
  books.map { it.asJsonObject }
    .filter { book -> userCheckout["bookId"] == book["id"] }

private fun swapBookIdForTitleOnUserCheckout(
  userCheckout: JsonObject,
  book: JsonObject
): JsonObject =
  userCheckout.deepCopy().apply {
    addProperty("title", book["title"].asString)
  }

private fun List<JsonObject>.toJsonArray(): JsonArray =
    fold(JsonArray()) { array, obj -> array.apply { add(obj) }}

Kotlin in Action: Chapter 5 Exercises

Just like the last few weeks, here's a practice problem, this time for chapter 5.

Exercise 1: Json Manipulation

In this example we will continue using the Gson library, We will manipulate multiple JsonArrays to return a list mapping the user's first name and last name to the titles of the books that the user has checked out.
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import static java.util.stream.StreamSupport.stream;

public class JavaExample {

  public static void main(String[] args) {
    String usersJson = "[\n" +
                       "  {\n" +
                       "    \"id\": \"543\",\n" +
                       "    \"username\": \"john123\",\n" +
                       "    \"firstName\": \"John\",\n" +
                       "    \"lastName\": \"Smith\",\n" +
                       "    \"email\": \"john@smith.com\"\n" +
                       "  }, " +
                       "  {\n" +
                       "    \"id\": \"438\",\n" +
                       "    \"username\": \"janedoe5\",\n" +
                       "    \"firstName\": \"Jane\",\n" +
                       "    \"lastName\": \"Doe\",\n" +
                       "    \"email\": \"jane.doe@gmail.com\"\n" +
                       "  }\n" +
                       "]";
    String booksJson = "[\n" +
                       "  {\"id\": \"1\", \"title\": \"Kotlin in Action\"},\n" +
                       "  {\"id\": \"2\", \"title\": \"Kotlin in Action\"},\n" +
                       "  {\"id\": \"3\", \"title\": \"Learning RxJava\"},\n" +
                       "  {\"id\": \"4\", \"title\": \"Refactoring\"},\n" +
                       "  {\"id\": \"5\", \"title\": \"Grokking Algorithms\"},\n" +
                       "  {\"id\": \"6\", \"title\": \"Code Complete\"}\n" +
                       "]";
    String checkoutsJson = "[\n" +
                           "  {\"userId\": \"543\", \"bookId\": \"1\"},\n" +
                           "  {\"userId\": \"543\", \"bookId\": \"5\"},\n" +
                           "  {\"userId\": \"438\", \"bookId\": \"2\"},\n" +
                           "  {\"userId\": \"438\", \"bookId\": \"3\"}\n" +
                           "]";

    JsonParser parser = new JsonParser();
    JsonArray users = parser.parse(usersJson).getAsJsonArray();
    JsonArray books = parser.parse(booksJson).getAsJsonArray();
    JsonArray checkouts = parser.parse(checkoutsJson).getAsJsonArray();

    JsonArray userCheckouts = stream(users.spliterator(), false)
        .map(JsonElement::getAsJsonObject)
        .flatMap(user -> stream(checkouts.spliterator(), false)
            .map(JsonElement::getAsJsonObject)
            .filter(checkout -> user.get("id").getAsString().equals(
                checkout.get("userId").getAsString()))
            .map(checkout -> {
              JsonObject jo = new JsonObject();
              jo.addProperty("firstName", user.get("firstName").getAsString());
              jo.addProperty("lastName", user.get("lastName").getAsString());
              jo.addProperty("bookId", checkout.get("bookId").getAsString());
              return jo;
            }))
        .flatMap(userCheckout -> stream(books.spliterator(), false)
            .map(JsonElement::getAsJsonObject)
            .filter(book -> userCheckout.get("bookId").getAsString().equals(
                book.get("id").getAsString()))
            .map(book -> {
              JsonObject jo = userCheckout.deepCopy();
              jo.addProperty("title", book.get("title").getAsString());
              jo.remove("bookId");
              return jo;
            }))
        .collect(JsonArray::new, JsonArray::add, JsonArray::addAll);

    System.out.println(userCheckouts);
  }
}
Similar to previous weeks, an answer will be shared in a follow up post. (And the answer can be found here.)

Wednesday, July 10, 2019

Kotlin in Action: Answers to Chapter 4 Exercises

So here are the answers to the last post:

Exercise 1: Json to Pojo and Back Again

import com.google.gson.JsonObject
import com.google.gson.JsonParser

fun main() {
  val johnJson = """{
                   |  "username": "john123",
                   |  "firstName": "John",
                   |  "lastName": "Smith",
                   |  "email": "john@smith.com"
                   |}""".trimMargin()
  val janeJson = """{
                   |  "username": "janedoe5",
                   |  "firstName": "Jane",
                   |  "lastName": "Doe",
                   |  "email": "jane.doe@gmail.com"
                   |}""".trimMargin()

  val parser = JsonParser()
  val johnJsonObject = parser.parse(johnJson).asJsonObject
  val janeJsonObject = parser.parse(janeJson).asJsonObject

  val john1 = User.fromJson(johnJsonObject)
  val john2 = User.fromJson(johnJsonObject)
  val jane = User.fromJson(janeJsonObject)

  println(john1)
  println(john2)
  println(jane)

  println("john1 = john2: " + (john1 == john2))
  println("john1 = jane: " + (john1 == jane))

  val usersSet = hashSetOf(john1, john2, jane)

  println("HashSet size (expected 2): " + usersSet.size)

  val johnFinal = john1.toJson()
  val janeFinal = jane.toJson()

  println(johnFinal)
  println(janeFinal)
}

data class User(val username: String,
                val firstName: String,
                val lastName: String,
                val email: String) {
  companion object {
    fun fromJson(jsonObject: JsonObject) : User =
      User(jsonObject.get("username").asString,
             jsonObject.get("firstName").asString,
             jsonObject.get("lastName").asString,
             jsonObject.get("email").asString)
  }

  fun toJson() : JsonObject {
    val jsonObject = JsonObject()
    jsonObject.addProperty("username", username)
    jsonObject.addProperty("firstName", firstName)
    jsonObject.addProperty("lastName", lastName)
    jsonObject.addProperty("email", email)
    return jsonObject
  }
}
Or, alternatively, if you prefer to pull the json processing out into extension functions:
import com.google.gson.JsonObject
import com.google.gson.JsonParser

fun main() {
  val johnJson = """{
                   |  "username": "john123",
                   |  "firstName": "John",
                   |  "lastName": "Smith",
                   |  "email": "john@smith.com"
                   |}""".trimMargin()
  val janeJson = """{
                   |  "username": "janedoe5",
                   |  "firstName": "Jane",
                   |  "lastName": "Doe",
                   |  "email": "jane.doe@gmail.com"
                   |}""".trimMargin()

  val parser = JsonParser()
  val johnJsonObject = parser.parse(johnJson).asJsonObject
  val janeJsonObject = parser.parse(janeJson).asJsonObject

  val john1 = User.fromJson(johnJsonObject)
  val john2 = User.fromJson(johnJsonObject)
  val jane = User.fromJson(janeJsonObject)

  println(john1)
  println(john2)
  println(jane)

  println("john1 = john2: " + (john1 == john2))
  println("john1 = jane: " + (john1 == jane))

  val usersSet = hashSetOf(john1, john2, jane)

  println("HashSet size (expected 2): " + usersSet.size)

  val johnFinal = john1.toJson()
  val janeFinal = jane.toJson()

  println(johnFinal)
  println(janeFinal)
}

data class User(val username: String,
                val firstName: String,
                val lastName: String,
                val email: String) {
  companion object
}

fun User.Companion.fromJson(jsonObject: JsonObject) : User =
  User(jsonObject.get("username").asString,
    jsonObject.get("firstName").asString,
    jsonObject.get("lastName").asString,
    jsonObject.get("email").asString)

fun User.toJson() : JsonObject {
  val jsonObject = JsonObject()
  jsonObject.addProperty("username", username)
  jsonObject.addProperty("firstName", firstName)
  jsonObject.addProperty("lastName", lastName)
  jsonObject.addProperty("email", email)
  return jsonObject
}

Kotlin in Action: Chapter 4 Exercises

Alright, so just like last week, I'm posting a Java to Kotlin conversion exercise where the Java code will be posted now and the Kotlin code will be posted in a follow up post. After reading through chapter 4 of Kotlin in Action, you should be prepared to complete the exercise.

Exercise 1: Json to Pojo and Back Again

Taking advantage of the Gson library, we'll convert some json into JsonObjects, which we will then take and convert into User pojos, run some simple tests, and then convert the pojos back to JsonObjects again. (I know that Gson supports converting json directly to pojos, but for the sake of the exercise, we'll ignore that and instead code it by hand.)
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import java.util.HashSet;

public class JavaExample3 {

  public static void main(String[] args) {
    String johnJson = "{\n" +
                      "  \"username\": \"john123\",\n" +
                      "  \"firstName\": \"John\",\n" +
                      "  \"lastName\": \"Smith\",\n" +
                      "  \"email\": \"john@smith.com\"\n" +
                      "}";
    String janeJson = "{\n" +
                      "  \"username\": \"janedoe5\",\n" +
                      "  \"firstName\": \"Jane\",\n" +
                      "  \"lastName\": \"Doe\",\n" +
                      "  \"email\": \"jane.doe@gmail.com\"\n" +
                      "}";

    JsonParser parser = new JsonParser();
    JsonObject johnJsonObject = parser.parse(johnJson).getAsJsonObject();
    JsonObject janeJsonObject = parser.parse(janeJson).getAsJsonObject();

    User john1 = User.fromJson(johnJsonObject);
    User john2 = User.fromJson(johnJsonObject);
    User jane = User.fromJson(janeJsonObject);

    System.out.println(john1);
    System.out.println(john2);
    System.out.println(jane);

    System.out.println("john1 = john2: " + john1.equals(john2));
    System.out.println("john1 = jane: " + john1.equals(jane));

    HashSet<User> usersSet = new HashSet<>();
    usersSet.add(john1);
    usersSet.add(john2);
    usersSet.add(jane);

    System.out.println("HashSet size (expected 2): " + usersSet.size());

    JsonObject johnFinal = john1.toJson();
    JsonObject janeFinal = jane.toJson();

    System.out.println(johnFinal);
    System.out.println(janeFinal);
  }
}

import com.google.gson.JsonObject;

import java.util.Objects;

public class User {

  public static User fromJson(JsonObject jsonObject) {
    return new User(jsonObject.get("username").getAsString(),
                    jsonObject.get("firstName").getAsString(),
                    jsonObject.get("lastName").getAsString(),
                    jsonObject.get("email").getAsString());
  }

  private final String username;
  private final String firstName;
  private final String lastName;
  private final String email;

  public User(String username,
              String firstName,
              String lastName,
              String email) {
    this.username = username;
    this.firstName = firstName;
    this.lastName = lastName;
    this.email = email;
  }

  public String getUsername() {
    return username;
  }

  public String getFirstName() {
    return firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public String getEmail() {
    return email;
  }

  @Override
  public String toString() {
    return "User(username=" + username +
        ", firstName=" + firstName +
        ", lastName=" + lastName +
        ", email=" + email + ")";
  }

  @Override
  public boolean equals(Object obj) {
    if (obj instanceof User) {
      User other = (User) obj;
      return equals(username, other.username) &&
          equals(firstName, other.firstName) &&
          equals(lastName, other.lastName) &&
          equals(email, other.email);
    }
    return false;
  }

  @Override
  public int hashCode() {
    return Objects.hash(username, firstName, lastName, email);
  }

  private boolean equals(String str1, String str2) {
    return (str1 == null && str2 == null) ||
        (str1 != null && str1.equals(str2));
  }

  public JsonObject toJson() {
    JsonObject jsonObject = new JsonObject();
    jsonObject.addProperty("username", username);
    jsonObject.addProperty("firstName", firstName);
    jsonObject.addProperty("lastName", lastName);
    jsonObject.addProperty("email", email);
    return jsonObject;
  }
}
Answers can be found here.