Skip to main content

Programming Foundations - Lotto Session #2 (Kotlin)

Expanding on our lottery model from session #1 we will convert the model from its existing Java form to Kotlin, which is a programming language that is becoming quite popular.

More than just being a "brand new toy" Kotlin has been endorsed by Google for Android development, has integration support with the Spring framework libraries, and has excellent integration with IntelliJ IDEA's integrated development environment. 


In this session, we will cover the following:

  • Reviewing key concepts learned in the initial session
  • Introduction to Kotlin syntax and grammar
  • Migration of our Java lotto implementation to Kotlin
  • Null handling deep-dive with Kotlin
  • Expansion of our Spock unit testing with Kotlin



What is Kotlin?

Kotlin was named after a Russian island, west of St. Petersburg in the Baltic Sea.  For comparison, the Java programming language was named after the Indonesian island of Java.

Kotlin is a statically typed programming language for the Java virtual machine. Some interesting things to know about Kotlin are:
  • Used for building server-side, Android, JavaScript, and native applications.
  • Compatible with Java and all Java-based frameworks.
  • Google has endorsed Kotlin as a first-class language for writing Android applications.  That is a big deal in my opinion!
  • There is an entire ecosystem of tools and frameworks being constructed to support Kotlin.
We won’t go into all of the advantages in this session as I want to share examples of developing and learning to program with Kotlin.  If you want to follow along using a different language, that should be fine as well.  Check out the following sites for more information why you should consider giving Kotlin a try:
Note: It is unclear at this point if Kotlin will become as popular as Java, Scala, or Groovy. However, from my personal experience, I’ve used both Scala and Groovy, and they both have benefits over developing with straight Java.  Kotlin appears to provide the same benefits and more in my opinion. One thing I especially like is that the developers from JetBrains (creators of IntelliJ IDEA IDE) created Kotlin and have a deep understanding (and appreciation) of how a language and an IDE interoperate.  That can be extremely powerful for developers!

Getting Started

We are going to start with our Drawing class to see how this would look in Kotlin.  Let’s examine some aspects of Kotlin as they relate to this session.
  • All classes in Kotlin have a common superclass called Any.  It is the default super class when no other super types are declared.
  • The Any class is not java.lang.Object, which is what Java classes all inherit.
  • Compared to the Object class in Java, the Any class does not have members other than toString(), equals(), and hashCode().
  • Kotlin classes and methods are final by default, as such, an ‘open’ keyword has been created to indicate a class can be inherited.
  • No need for semicolons to end a line of code
An introduction to Kotlin will be achieved through refactoring of the existing Java classes we introduced in session #1.  Included in our refactoring, is test coverage expansion using the Spock implementation.  Since Kotlin is compatible with Java, we can use Spock for testing with minimal modifications.

The required modifications are mostly due to Kotlin’s efficiency where some of the specific tests we wrote for the Java implementation are no longer necessary due to Kotllin’s built-in capabilities for handling null values.  This translates to less defensive code in our application (e.g. checking for possibility of null values all over) and leads to a reduction of overall test code. We will deep-dive into Kotlin’s null handling later in this session.

Drawing Class Migration

Starting with our Drawing class, you will recall we created a base class to represent an abstraction that we use to manage our specific date and set of core picks state.  The Drawing class requires that the subclass extending it provide its own validate() method.  This method will be specific to the min and max boundaries of a unique lottery drawing.

Let’s break down the Drawing class so you can learn some key fundamentals of Kotlin.  Don’t be intimidated by the newness here, once you understand these concepts they will be utilized all throughout your Kotlin usage.  There is a method to JetBrain’s madness, and the features offered make Kotlin very powerful when constructing applications of all sizes.

Drawing Abstraction


The first thing you may see is that the ‘abstract’ keyword is still included but the constructor and definition of instance variables are somewhat different from our Java implementation.  On line #31 is an illustration of how Kotlin supports the primary constructor for the Drawing class.  You can have more constructors, but in the case of our Drawing construction (as initiated from subclasses) we only have to consider the super class’s primary constructor passing in the date and core picks.

Also, in the constructor, we now have a ‘val’ keyword that is used.  In Kotlin, there are options when defining a variable.   The properties declared in the primary constructor can be mutable using ‘var’ or read-only using ‘val’.  In our case, we are using read-only since the constructor is the only place where we initialize our state for the Drawing.

We are allowed to reference the values after that point but can’t change the values that we initially assign in the constructor.  This mutable and immutable option is extremely important to support development techniques which support various design principles.  In future sessions, we will cover the importance of mutable and immutable in the context of multi-threaded and event pipeline development.

Another change is that there are annotations used on lines 33 and 36 to declare that the FailedValidationException can be thrown from those methods.  This is a checked exception and Kotlin gives you the flexibility to declare your methods with checked and unchecked exceptions. You will find varied opinions on the need for checked exceptions from other developers & architects. For this example, we are providing a checked exception to show that Kotlin does indeed support them as well as non-checked exceptions.

As an example, you could throw an unchecked IllegalArgumentException without the need to specify your own exception along with a specific throws clause.  With the checked exception, it indicates to the caller (as part of the method signature) that you could get a specific exception thrown as part of the contract. With checked exceptions identified for a method, it is expected that the caller of that method handles that exception (or base class of that exception) using a try/catch block.  In future sessions, unless there is a very specific need to explicitly identify a customized exception, unchecked exceptions will be used.

Power Drawing Migration

Moving along to the PowerDrawing class that extends the abstract base class.  We now have some new keywords to discuss that are specific to Kotlin.


On line #32, you will notice that the primary constructor follows the same pattern as the base class and where it references the base class constructor, a colon is used to separate the initialization of values.

So how does this work?  If you were to construct a PowerDrawing with values for required variables such as: new PowerDrawing(myDateValue, myCorePickSet, myPowerPick)

the values for myDateValue and myCorePickSet are used to support the Drawing constructor’s contract. The myPowerPick becomes an immutable value supported by the PowerDrawing class.
  
We didn’t design the base class to have a power pick, as the subclasses will support their own.  A drawing abstraction just has the core set of picks (e.g. 1,2,3,4,5 …) and a date for which the drawing occurred.  The subclass, being a specific type of drawing will bring along its own unique state and behavior.

If you compare this to the amount of code required in Java to accomplish the same thing, you can see that Kotlin reduces this down to be more simplified. That is, it can still express what is needed without the specific mapping of values passed to instance variables during construction time.

On line #38, we see something new called ‘fun’ this is a way to define your functions in Kotlin. There are some great things about functions that are different than Java methods that we will cover in more detail in another session.  For now, think of this as a Java method for the way we are defining the validate() function. Here the override keyword is included as this function is part of the contract by extending the Drawing class.  The validate function’s implementation will stay the same as our previous Java implementation, which ensures that our core pick and power pick values are within acceptable ranges.

At this point, with some reduction of code we have transformed our Java lotto model from session #1 into a Kotlin version.  We will now look at the test changes that we can make to expand our coverage and reduce the need for null pointer checks, which are now unnecessary when using Kotlin.

Addressing Nulls with Kotlin

Before we dive into testing, let’s describe the null handling in Kotlin.  First, for those that don’t know, in Java a NullPointerException can be thrown when invoking a variable’s method when the variable was previously assigned to null or not assigned a value at all where the variable would default to null. 

When we use variables in Java that can be null or not null we have to be cautious and write lots of null checks to be sure we aren’t currently invoking a method on a null object.  In Java, and in Kotlin (if you want), you would need to write defensive code to check for nulls.  For example, I’ve written many checks over the years like the following:

if (myString!=null) { lengthOfString = myString.length } else { lengthOfString = 0}

There is an easier way when using Kotlin …

Kotlin’s type system can determine between references that hold null and those that can’t hold null. This allows Kotlin to determine at compile time (not runtime) that a null value is or is not allowed for a variable. For example, to do this with Kotlin, we declare the variable:

var myString: String = “Jeff’s string that can’t be null”

If at some point in our code we try to assign this to null and we get a compile time error!

myString = null // won’t compile!

This is huge in my opinion (I know I get excited by little things)! However, the power here is that at this point in the rest of our code (could be tens of thousands of lines), we don’t have to protect ourselves from variables that we identify as not nullable.

This is not to say that Kotlin doesn’t provide a way to allow for null, it does, we just declare our variable differently to say we acknowledge this can be null and not let it ruin our logic with unexpected null pointer exceptions at runtime.  This is done with what is called “safe calls”.

var myString: String? = "Jeff’s string that could be null"
myString = null // assignment to null is acceptable

We don’t get a compile time error here when assigning to null as we defined this variable to be a String value or be null by using the ‘?’ safe call.   However, the call to our String methods (e.g. length) will still be caught by Kotlin at compile time unless we protect using ‘?’.

val lengthOfString = myString.length // compile error here still

The approach below is one way (and the best in my opinion) in Kotlin to say if the myString is null (as we declared it to be allowed) then the assignment will reflect the null without causing any trouble.

val lengthOfString: Int? = myString?.length

Why does the left-hand side for Int need to include the same protection? That is because we could now be receiving a null (as lengthOfString) and potentially be calling another method.

lengthOfString.toString() // won’t compile

So, when we declare a variable using this notation, we are telling the compiler that we are good with a null value and we will protect against that moving forward.

lengthOfString?.toString() // happy compiler

Additional Options

Besides the “safe calls” approach and checking for null conditions (with if/then) in your logic all throughout your code, Kotlin provides other ways to handle null values in your logic.

!! Operator

There is a feature “non-null asserted” using double exclamation points (!!).  This tells the compiler, we actually want to throw a null pointer exception when this variable is null.

lengthOfString!!.toString()

That supports what happens in Java when a null pointer exception is thrown at runtime.  There are cases where you may want to do this based on a logic requirement.  Kotlin doesn’t prevent you from having this capability.

Is Elvis in the Building ?:

There is an operator called the Elvis operator that says when there is a nullable reference to variable, it will check to see if it is not null and will use it, otherwise, assign it to something that is a non-null value.  The name Elvis is used as the operator ?: represents his hair style.  This is not just a Kotlin thing by the way, you can see this used with languages like Groovy as well.

Using our Java example,
if (myString!=null) { lengthOfString = myString.length } else { lengthOfString = 0}

In Kotlin, can be re-written as:
val lengthOfString: Int = if (myString!= null) myString.length else 0

In Kotlin, with the Elvis operator we can simplify even further!
val lengthOfString: Int = myString?.length ?: 0

Test Expansion

In the initial session, there was unit test coverage included using Spock, which is a Groovy based unit test framework.  The unit tests performed some basic construction and validation activities.  In this session, we will expand the testing for additional coverage and refactor out some unnecessary validation checks.

In this session, we addressed how Kotlin can handle null values at compile and runtime.  With this added capability using Kotlin, we no longer need some of the defensive coding in our validation and constructors that were required in Java.  As such, the testing coverage become less complicated and verbose as well.


The null case below for powerPick had to be protected in Java, but checks are no longer needed with Kotlin.

new PowerDrawing(date, corePicks, null) // produces a compile error

We defined the constructor to require a non-null value for powerPick when we implemented the PowerDrawing subclass. Kotlin will detect and identify when constructed with null at compile time! If we wanted to allow, we could provide a ‘safe call’ indicator in our declaration.

In the first session, if we had checked how much test coverage was provided with our initial Spock test cases, it would be lacking some key coverage.  Session #2 introduces Spock’s ability to expand test coverage through what is called Data Driven Testing.  This is handy when you have varying inputs and varying expected results for a test case.


The @Unroll annotation is used in conjunction with a ‘where’ clause that allows the test operation to be executed N # of times.  In our case, we are creating varying combinations of core pick inputs with a single power pick and then ensuring that our expected result occurs.  In this case, we are testing for edge cases expecting an exception to be thrown for each test set.

The next test case covers a set of inputs where no validations errors are expected.


The use Spock's data driven approach allows the developer to concisely represent a set of input.  It is also important to note that expected result values can also be defined in the ‘where’ table to allow for more dynamic expected results.  You will see examples of these in future sessions.  For more information about this data driven approach with Spock checkout the link in the references section.

Summary

This session covered many topics and set us up for future learning using the lotto model for build-out of a working lottery application.  To recap, we’ve migrated our working Java example into a Kotlin working example, we did a deep-dive on Kotlin’s null handling with examples of Kotlin syntax and keywords.  We closed with expansion of our unit test coverage using Spock’s more data driven approach.

Check out the complete source code using the link below and feel free to contact me for future session content! Until next time ...


Comments