I'm in the midst of my third project at Rocket that has been written in Kotlin. Two have been entirely in Kotlin, while one was a mixed Java and Kotlin project. They've all been Android projects, one as an SDK and two as apps, so I haven't gotten to try out the JavaScript compilation.
First, what is Kotlin? It's a language created by IntelliJ that runs in the JVM and Google announced official support for it in Android development at last year's Google I/O.
Fun fact about Kotlin: it's the named after an island near St. Petersburg.
The quickest way to see the difference between Java and Kotlin is with a simple example.
Here's a very simple model describing a user object in Java.
public class User {
private String firstName;
private String lastName;
User() { }
User(String firstName) {
this.firstName = firstName;
}
User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getFirstName() {
return firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getLastName() {
return lastName;
}
/* for sanity, we’re not going to go into toString, hashCode, equals, or copy */
}
Here's the same model, but this time, written in Kotlin.
data class User(
var firstName: String? = null,
var lastName: String? = null
)
This time, by marking it as a data
class, we get toString
, hashCode
, equals
, and copy
for free. Additionally, if we were writing Kotlin, we can reference the Java version of the model using something like myUser.firstName = "John"
instead of having to use setName
or getName
. Kotlin detects that and gives you the simpler syntax for free.
I could end the blog post here, but I'll keep going.
In the next code sample, we'll demonstrate a few things: implicit types, object creation, static methods and companion objects, and function declarations. Let's imagine we want to have a static method that creates an instance of a default user. Hiding the bulk of the User
object Java code above, we'd have:
public class User {
/* let’s use the same user object as before and pretend that's still here */
public static User createDefaultUser() {
return new User("John", "Snow");
}
}
In Kotlin, we can write that as:
data class User(
var firstName: String? = null,
var lastName: String? = null
) {
companion object {
fun createDefaultUser(): User {
return User("John", "Snow")
}
}
}
In fact, we can actually cut that code down more to the following:
data class User(
var firstName: String? = null,
var lastName: String? = null
) {
companion object {
fun createDefaultUser() = User("John", "Snow")
}
}
We now don't need to explicitly state the type being returned by createDefaultUser
and we don't need explicitly include return. Android Studio will notify you of opportunities to condense your code in this way.
Companion objects are way to have static code encapsulated, whether it's methods or constants, for example.
Additionally, we can create extension functions. Imagine that that User
object above was in a library and you didn't want to extend it, but you wanted to give it some additional functionality. You could do something like this:
fun User.getFullName(): String? {
if (lastName == null || lastName.isEmpty()) {
return firstName
} else {
return "$firstName $lastName"
}
}
This will create a new method on the User
object we can import and use elsewhere in our codebase. Also, note Kotlin's string interpolation. That's pretty neat as well.
This isn't the most concise way we could represent this, however. We could use an extension function that already exists in Kotlin, isNullOrEmpty
.
fun User.getFullName(): String? {
if (lastName.isNullOrEmpty()) {
return firstName
} else {
return "$firstName $lastName"
}
}
We can also lift the return statement out of the if statement, giving us this:
fun User.getFullName(): String? {
return if (lastName.isNullOrEmpty()) {
firstName
} else {
"$firstName $lastName"
}
}
Finally, we can make this an expression body since it's not just a simple return and we don't need to specify the type because it can be inferred, giving us this:
fun User.getFullName() = if (lastName.isNullOrEmpty()) {
firstName
} else {
"$firstName $lastName"
}
However, getFullName
doesn't need to be a method. That's really a property we want associated with an object when you think about it. Let's do it as a property instead.
val User.fullName
get() = if (lastName.isNullOrEmpty()) {
firstName
} else {
"$firstName $lastName"
}
We can pull the return keyword out of the if and else cases. That will give us the ability to remove the return keyword because we can now return the whole expression and make the type of String implicit.
For extension methods and properties, under-the-hood Kotlin is creating static methods that take the base object as an argument and executes based on that object.
For the final bit in this post, there are var
and val
declarations to make a variable immutable or not. For example:
var test = "something" // this is mutable
val test2 = "something else" // this is immutable
test = "a new value" // can do!
test2 = "another new value" // will not compile
Another important note that we don't have to specify the type above. That's because Kotlin can infer that it's a String (or Int, etc). If it can't implicitly determine the type, you'll need to specify it.
This is a quick overview to Kotlin and isn't comprehensive. Much of the difference is syntactic sugar, but the brevity and conciseness of Kotlin makes it very readable and easy to understand quickly.
The official site for Kotlin has a great run through the language features and other additional information.