Java 8 added lambdas, streams, and many other language improvements. Java 9 is coming in September 2016, but with over half of Android devices stuck using Java 6, will we ever get to use a modern language?
In this talk from Droidcon NYC, Michael Pardo introduces Kotlin: a statically typed JVM language backed by JetBrains. With features like lambdas, class extensions, and null-safety, it aims to be concise, expressive, and highly interoperable — a powerful addition to your Android tool belt.
Kotlin (0:00)
Hi! I’m Michael Pardo, and I’m here to show you Kotlin and how it’s going to make you a happier and more productive Android programmer. Kotlin is a statically typed language targeting the JVM. It’s backed by and created by JetBrains, the company that brings you Android Studio and IntelliJ.
There are a few core tenants that Kotlin lives by. It aims to be:
- Concise, reducing the amount of code it takes for you to achieve something;
- Expressive, making your code more readable and understandable;
- Safe, removing the possibility for you to create errors;
- Versatile, targets the JVM and JavaScript you can run it in many places;
- Interoperable, what that means is you can call Java code from Kotlin and Kotlin code from Java, and they aim to be 100% interoperable.
But why not wait for Java 8?
Java and Android: A History (1:11)
Let’s take a look at the history of Java and Android and their relationship. In 2006, Java 6 was released. A couple of years later, Android 1 was released as an alpha, and four years ago, Java 7 was released. Android followed up with Java 7 support two years after that, and last year, Java 8 was released.
When do you think we’ll get Java 8? It might be a while, if we do get it at all. Even then, if we do get Java 8 support in the next couple of years, it’s going to be a while longer before everyone can adopt it. Android adoption is fairly fragmented now, and Java 7 is only supported for API 19 and up. Your userbase for Java 7 apps is just over half, which isn’t great. Even if we had Java 8 right now, with 100% compatibility on all devices, the language itself has some problems.
Let’s go over some of those.
Problems with Java (2:22)
- Null references, coined by its inventor as a billion dollar mistake: You can code very defensively, but null pointer exceptions sneak into everyone’s code just because Java’s type system is not safe.
- Raw types: We’ve been stuck with raw types in order to stay backwards compatible, and we should all know how to avoid raw types, or that we should, but they’re included in the language as kind of misleading and unsafe.
- Covariant arrays: You can create a string array, and an object array and then assign that string array to that object array. It compiles fine, but once you try to assign a number to that array of strings in the runtime, it’s going to throw an exception.
- Java 8 has higher-order functions but they’re implemented with SAM types. SAM is single abstract method, every function type needs an interface correlated to it, and if you want to create a lambda function that doesn’t exist or doesn’t have a function type, you need to create that function type as an interface.
- Wildcards in generics, anything but non-trivial generics can get kind of out of control and hard to read and write and understand, and checked exceptions. These are like exceptions in the method header, or the signature, rather. They’re good-intentioned, but a lot of time what you’ll see is a code base littered with try/catch blocks and ignored exceptions.
Let’s explore what Kotlin does to solve all of those issues.
Kotlin To The Rescue! (4:24)
For pretty much all the issues that we just went over, Kotlin just removes those features. It also adds great things like:
- Lambdas
- Data classes
- Function literals & inline functions
- Extension functions
- Null safety
- Smart casts
- String templates
- Properties
- Primary constructors
- Class delegation
- Type inference
- Singletons
- Declaration-site variance
- Range expressions
We’re going to cover most of these in this post. Kotlin can keep moving forward with the JVM ecosystem because it doesn’t have any restrictions. It compiles right down to JVM bytecode, which means no overhead. It looks to the JVM just like any other JVM language. In fact, if you use the Kotlin plugin for IntelliJ or Android Studio (which you should), there is a bytecode viewer that displays the generated JVM bytecode for each method in headers.
Hello, Kotlin (5:19)
Let’s have a look at some basic syntax. Here’s the simplest form of a Java Hello World:
fun main(args: Array<String>): Unit {
println("Hello, World!")
}
It’s just a function and a print statement. There’s no package declaration. There are no imports required. This is all that’s needed to start up a Kotlin Hello World. Functions are declared with a fun
keyword, followed by the function name, and then the arguments in parentheses just like Java.
However, in Kotlin, you put the argument name first, followed by the type, separated by a colon. The function’s return type is last, instead of first like in Java. If a function doesn’t return any useful type, it returns a unit
as the type, and we can infer the unit
in most cases and simply omit that return type. Then we print Hello World. This is a standard function in the Kotlin standard library and that calls into Java’s system.out.println
.
fun main(args: Array<String>) {
println("Hello, World!")
}
Next we’re going to extract the “World” portion of that string into a variable. The var
keyword followed by the variable name, and then Kotlin also has string interpolations we can include that variable right in the string without having to concatenate it. The way we do that is just by including the variable name, prepended with a dollar sign.
fun main(args: Array<String>) {
var name = "World"
println("Hello, $name!")
}
Next, let’s expand this to check and see if the arguments that we passed in have any strings. If this string array is not empty, we’ll take the first string and assign it to name
. We also have the val
keyword, and analogous to a final
variable in Java — a constant.
fun main(args: Array<String>) {
val name = "World"
if (args.isNotEmpty()) {
name = args[0]
}
println("Hello, $name!")
}
And we run into a problem trying to compile this because we cannot reassign a value to this constant. One way to solve that is using an inline if-else block. A lot of blocks in Kotlin can return a value, going through the if statement and returning arg[0]
if it’s not empty. Else, it will return “World”, and that assigns that right into the value we’ve named a name. This is a conditional assignment block:
fun main(args: Array<String>) {.
val name = if (args.isNotEmpty()) {
args[0]
} else {
"World"
}
println("Hello, $name!")
}
We can reduce it to one line and make it look like a ternary operator. Removing those braces to reduce the visual clutter makes it look even nicer, similar to the Java ternary operator.
val name = if (args.isNotEmpty()) args[0] else "World"
Class Syntax (5:19)
Let’s look at classes. We have a class defined using the class
keyword just like in Java, followed by the class name. Kotlin has primary constructors, we can put a constructor right in the class header. In that constructor we can define our parameters. We can also define them as properties by declaring them as a var
or a val
.
class Person(var name: String)
Using the earlier example, we can take our name assignment logic and remove it, and replace it with an instance of the person class. When you create an instance in Kotlin, you don’t have to use the new
keyword. You just create a new instance by referencing the type.
class Person(var name: String)
fun main(args: Array<String>) {
val person = Person("Michael")
println("Hello, $name!")
}
Now our string interpolation is wrong. We’re still referencing name
. To fix that, we can use a string interpolated expression which is a dollar sign and curly braces:
class Person(var name: String)
fun main(args: Array<String>) {
val person = Person("Michael")
println("Hello, ${person.name}!")
}
Below is an enum
class. Enum’s are a lot like Java enum’s, so this should look familiar. The way you define them is with the enum class
:
enum class Language(val greeting: String) {
EN("Hello"), ES("Hola"), FR("Bonjour")
}
Let’s add a property of language to our person class.
class Person(var name: String, var lang: Language = Language.EN)
In Kotlin you can have default arguments, the default value for this argument is Language.EN
, and then we can omit that value anywhere we create an instance of the class. We can make this example a little more object-oriented and add a function called greet to the person class, and that just takes the language greeting and appends the name into a string.
enum class Language(val greeting: String) {
EN("Hello"), ES("Hola"), FR("Bonjour")
}
class Person(var name: String, var lang: Language = Language.EN) {
fun greet() = println("${lang.greeting}, $name!")
}
fun main(args: Array<String>) {
val person = Person("Michael")
person.greet()
}
Now we can remove a line from our main function, call person.greet()
and it does the same thing.
Collections and Iteration (11:32)
val people = listOf(
Person("Michael"),
Person("Miguel", Language.SP),
Person("Michelle", Language.FR)
)
We can create a list of person
using the listOf
method, a standard library function. We can iterate that list of person using the for-in
syntax.
for (person in people) {
person.greet()
}
We can then call person.greet()
on each iteration, or better yet, we can call an extension function off this person collection and pass on a lambda for iteration. We can reduce it down to its simplest form, removing the explicitly named value and using the provided name value which is it
.
people.forEach { it.greet() }
We can move that up into the list of return value and eliminate that value people we declared. We can do a little better and create subclasses for each language, first we have to make the person class open
which means it’s non-final. Classes by default are final in Kotlin.
Let’s create two classes and pass in a default language for each, and we can replace those declaration, instance declarations in the list with their respective subclasses. Below is our full extended Hello World, showing off a lot of Kotlin’s features.
enum class Language(val greeting: String) {
EN("Hello"), ES("Hola"), FR("Bonjour")
}
open class Person(var name: String, var lang: Language = Language.EN) {
fun greet() = println("${lang.greeting}, $name!")
}
class Hispanophone(name: String) : Person(name, Language.ES)
class Francophone(name: String) : Person(name, Language.FR)
fun main(args: Array<String>) {
listOf(
Person("Michael"),
Hispanophone("Miguel"),
Francophone("Michelle")
).forEach { it.greet() }
}
What Kotlin Adds To Java (13:11)
Let’s go over what Kotlin adds to Java and what you can gain by using it.
Type Inference (13:18)
You’ve probably seen type inference in other languages. In Java, we define the type, then the name, and then the value. In Kotlin, it’s a little backwards. You define the name, then the type, and then assign the value. In most cases, though, you don’t need to define the type. A string literal is good enough to infer a string type. The same goes for characters and integers and longs, floats, doubles, Booleans.
var string: String = ""
var string = ""
var char = ' '
var int = 1
var long = 0L
var float = 0F
var double = 0.0
var boolean = true
var foo = MyFooType()
This also applies to any other type, as long as Kotlin can infer it. Usually, if it’s local, then you don’t need to specify the type when you declare a value or variable. In cases where you can’t infer, you have to define the variable with the full syntax, which is with a colon and a type, and then equal sign.
Null Safety (14:22)
One of Kotlin’s most powerful features is null-safety. we’ll go through an example.
String a = null;
System.out.println(a.length());
Here in Java, we have a string. We’ve assigned null to it. As soon as we go to print that string, though, we run into a problem at runtime, and that’s going to be a null point error exception because we’re trying to access a null value. Here is the same code in Kotlin, we declare a null string, but before we can get it even further, we encounter an error with the compiler.
val a:String = null
The problem is we’re trying to assign a null value to a non-null type. In the type system, there’s null types and non-null types, and the type system recognizes that this is a non-null type and prevents compilation to make it nullable. To make any type nullable, we add this question mark to the end of it and that means it can be assigned with a null.
val a: String? = null
println(a.length())
Now we can continue, now that we’ve fixed that error, and just like in the Java example, we print the length of the string, but, we run into the same problem as in Java. This string could be null. However, in this case, we run into it at compile time instead of runtime thanks to the type system.
The compiler stops here, and to fix it, we need to prepend the call, or append the variable with a question mark to make it safe. This essentially checks to see if the value is null before accessing it.
val a: String? = null
println(a?.length())
If the value is null, it returns null. If it’s not null then it returns the actual value. Since this value is null, null is passed to the print method and we print out null.
Ternary Null (16:19)
int length = a !- null ? a.length() : -1
Above is something you might see in Java. To access a value at a ternary operator, we check that the value is not null and then we can finish the assignment using the value. If the lefthand side passed the null check, we can access it. If the righthand side was null, we need to do something else. In Kotlin, you can do the exact same thing.
var length = if(a!= null) a.length() else -1
If a
is not equal to null, then you can access it, otherwise, return your default value. But there is a shortcut for this called the elvis operator.
var length = a?.length() ?: -1
We do an inline null check using the question mark, and if you remember, it’ll return null if a is null. If anything on the lefthand side of the elvis operator is null, it will use the righthand side. Otherwise, it will continue with the evaluation.
Smart Casts (17:30)
Kotlin has a feature called smart casting. If a local object passes a type check, you can access it as that type without explicitly casting it. Here’s an example:
if (x is String) {
print(x.length())
}
We check to see if x
is a string, and if it is we print the length. To do that we use the is
keyword. It’s similar to instanceOf
in Java, and since it passes the check, we can treat it like a string anywhere after the check.
if (x !is String) {
return
}
print(x.size())
It also works in reverse, here we check if it’s not a string, and if it’s not, we return out of the function. However, if it is a string, everywhere after that we can assume it was a string and we don’t have to cast it. Casting anywhere after the type check even includes inline logic.
if (x !is String || x.size() == 0) {
return
}
Here we’re checking if it’s not a string and then we use the or operator, if the lefthand side was false. Since it’s a string, we can access it as a string and it gets casted by Kotlin. Same goes for when
statements. when
statement are like an enhanced switch
statement:
when(x) {
is Int -> print(x + 1)
is String -> print(x.size() + 1)
is Array<Int> -> print(x.sum())
}
We do our type check, automatic cast, type check to automatic cast, and smart cast.
String Templates (19:07)
A lot of languages have string templates or string interpolation. This is how you might do it if you’re used to Java.
val apples = 4
println("I have " + apples + " apples.")
You would concatenate this apples
value in the middle of the string. In a more Kotlin-like fashion, if you’re using string interpolation, you’d just move that inside of the string and prefix it with a dollar sign.
val apples = 4
println("I have $apples apples.")
You can also do it with expressions:
val apples = 4
val bananas = 3
println("I have $apples apples and " + (apples + bananas) + " fruits.") // Java-esque
println("I have $apples apples and ${apples+bananas} fruits.") // Kotlin
In Java, you might do something in the middle of the string where concatenating an expression of apples plus bananas but we can move that into the string and interpolate it and the only thing we have to do there is wrap it with curly braces after we use the dollar sign prefix.
Range Expressions (20:00)
You might have seen these in other languages, too. You’ll notice that a lot of these features have come in from other languages. Here’s an expression saying that if i
is greater than or equal to one and less than or equal to 10, then we print it. We check if it’s in range of 1 to 10.
if (1 <= i && i <= 10) {
println(i)
}
Instead we can create use a construct that’s already created for us called an intRange
. We pass in the range of 1 to 10, and then there’s a contains
function that we can call to see if it’s in that range, and if it is, we print i
.
if (IntRange(1, 10).contains(i)) {
println(i)
}
This can be reworded using extension functions, 1.rangeTo
creates an intRange of 1 to 10, and we can call contains on that.
Preferably, you want to use this operator:
if(i in 1..10) { ... }
This aliases over rangeTo
. Because of the null type system, we can be smart about this. There are no primitives in Kotlin. There’s int
, which is integer or a primitive int, and depending on whether it’s null or not, the type system knows whether to use a primitive int or an integer, optimizing it for your case. It’ll automatically box any primitive that exists in Java, as long as you’re using the right type.
We can also iterate through ranges because they are iterate-able. You can use steps in your ranges:
for(i in 1..4 step 2) { ... }
You can reverse iterate, or you can reverse arrange and iterate with steps.
for (i in 4 downTo 1 step 2) { ... }
You can basically combine a lot of these functions to make range iteration as expressive as you want. You can iterate over pretty much any data type. You can create ranges for any data type including strings and your own types, as long as there’s a logical way for you to iterate and you can implement it. The step are internally defined as numbers.
Higher-Order Functions (22:55)
A lot of languages have these now, as does Java 8, but we can’t use Java 8. If you’re on Java 6 or 7, this is how you might implement a filter function.
public interface Function<T, R> {
R call(T t);
}
public static <T> List<T> filter(Collection<T> items, Function<T, Boolean> f) {
final List<T> filtered = new ArrayList<T>();
for (T item : items) if (f.call(item)) filtered.add(item);
return filtered;
}
filter(numbers, new Function<Integer, Boolean>() {
@Override
public Boolean call(Integer value) {
return value % 2 == 0;
}
});
First, we need to declare a function interface that takes type T
and returns type R
. Our method iterates over a collection using your supplied function and creates a new array lists, adding items that pass your predicate, and then we call the filter
function. Finally, the anonymous implementation:
fun <T> filter(items: Collection<T>, f: (T) -> Boolean): List<T> {
val filtered = arrayListOf<T>()
for (item in items) if (f(item)) filtered.add(item)
return filtered
}
Above is pretty much what it looks like in Kotlin, or at least our version of it, and then the call looks like so:
filter(numbers, { value ->
value % 2 == 0
})
You’ll notice that there’s no interface defined here, and that’s because Kotlin has function types, instead of using an interface, we specify a type that is a function type that takes in a type T
, and returns a type Boolean
, and then we can invoke the function just like any other function. We pass an anonymous function which is a lambda that matches the function type that we defined in our filter function. We filter passing in a list and a function. Also, if the last argument is a function type, we can declare it outside of the argument list.
Since function type can be inferred from the function signature, it knows it only has one argument we can remove it and it’ll name it to the pre-defined name of it, like this:
filter(numbers) {
it % 2 == 0
}
We could reduce it to one line to make it even more readable.
Inline Functions (25:27)
Inline functions go hand-in-hand with higher-order functions. In certain cases, usually when you’re using generic types, you can add the inline keyword to the function. During compilation, it will take the lambda that you provide to that function, and it will move it inline into your code.
It’ll take this:
inline fun <T> filter(items: Collection<T>, f: (T) -> Boolean): List<T> {
val filtered = arrayListOf<T>()
for (item in items) if (f(item)) filtered.add(item)
return filtered
}
filter(numbers) { it % 2 == 0 }
And change it to this:
val filtered = arrayListOf<T>()
for (item in items) if (it % 2 == 0) filtered.add(item)
This also means we can do other things that are not possible in regular functions. For example, here’s a function that takes a lambda, and you can’t return out of it:
fun call(f: () -> Unit) {
f()
}
call {
return // Not allowed
}
But if we make that function an inline function, now we can return out of that function, because it is inline with the rest of the code.
inline fun call(f: () -> Unit) {
f()
}
call {
return // Now allowed
}
Inline functions also allow you to use reified
types. Here’s an example of a real function that we use that finds a view parent from a view that matches a type T
:
inline fun <T : Any> View.findViewParent(): T? {
var parent = getParent()
while (parent != null && parent !is T) {
parent = parent.getParent()
}
return parent as T // Cast warning
}
There are some problems with this function though. We can’t check the type because generic types are erased. Even if we could check for that type, we’ll get an unchecked cast warning.
The solution is to take our function and add this reified
keyword to the type. Because that function gets compiled down to inline code, we can now check against the generic type to see if it’s an instance of that, clearing our cast warning:
inline fun <reified T : Any> View.findViewParent(): T? {
var parent = getParent()
while (parent != null && parent !is T) {
parent = parent.getParent()
}
return parent as T // Type cast allowed
}
Extension Functions (27:20)
This is one of Kotlin’s most powerful features. Below we have a utility function that checks if we’re running on Lollipop or greater, and it takes an integer as an argument:
public fun isLollipopOrGreater(code: Int): Boolean {
return code >= Build.VERSION_CODES.LOLLIPOP
}
But we can change it to an extension function by prepending the type and a dot before the function name, and you’ll notice it looks a lot like how you would actually use the syntax:
public fun Int.isLollipopOrGreater(): Boolean {
return this >= Build.VERSION_CODES.LOLLIPOP
}
We remove our argument and we need to change that code to use this
, since we’re calling the function on the integer itself now. This is how we use it, we can call the function directly on the integer:
16.isLollipopOrGreater()
It can be any integer, literal or wrapped. You can also do this on final
classes. Because it doesn’t actually add any code to the class, you can’t modify the class after what. Instead it creates a static method that looks like the original one, but has this syntax sugar that looks like it’s an actual function of that class. These methods can be defined anywhere.
Kotlin makes use of extension functions in Java collections, so here’s an example of taking a collection, and doing some operations on it.
final Function<Customer, Order> customerMapper = // ...
final Function<Order, Boolean> orderFilter = // ...
final Function<Order, Float> orderSorter = // ...
final List<Order> vipOrders = sortBy(filter(map(customers,
customerMapper),
orderFilter),
orderSorter);
We take the list of customers, map, filter, and sort it. The nesting made it messy and hard to read. Extension functions come in handy in the standard library as they can use Java collections and substantially clean up code:
val vipOrders = customers
.map { it.lastOrder }
.filter { it.total >= 500F }
.sortBy { it.total }
Properties (30:55)
Kotlin makes properties a language feature.
class Customer {
private String firstName;
private String lastName;
private String email;
public String getFirstName() { return firstName; }
public String getLastName() { return lastName; }
public String getEmail() { return email; }
public void setFirstName(String firstName) { this.firstName = firstName }
public void setLastName(String lastName) { this.lastName = lastName }
public void setEmail(String email) { this.email = email }
}
Above is a typical Java bean class. We’ve got our private members, our getter
methods, our setter
methods, it’s just three properties. It’s a lot of boilerplate, Kotlin can do better. Here’s the same class in Kotlin:
class Customer {
var firstName: String = // ...
var lastName: String = // ...
var email: String = // ...
}
You just define class members as variables, and these are public
by default, and the encapsulation is taking care of by the compiler, so it’s encapsulating Kotlin and it’ll generate the getters
and setters
in Java.
Primary Constructors (31:49)
Kotlin classes can have multiple constructors just like in Java, but you can also have a primary constructor. Below is our class from the last example, and now we’ve added a primary constructor, which just is a constructor that goes in the class header.
We can immediately use those variables to assign to our properties of this class, or we can move that into an initialization block:
class Customer(firstName: String, lastName: String, email: String) {
var firstName: String
var lastName: String
var email: String
init {
this.firstName = firstName
this.lastName = lastName
this.email = email
}
}
Better yet, we can just define them as properties in the primary constructor, and we can format it nicely like so:
class Customer(
var firstName: String,
var lastName: String,
var email: String)
Now we have a class that takes in all of those arguments and their properties, with getters and setters. If we change those properties to values using the val
keyword, the class becomes immutable, which makes it ideal for functional programming of the type we saw earlier along with concurrency.
Singletons (35:53)
You’ve likely come across a singleton pattern. Usually you see it in things like loggers. In Java, there’s several ways to implement a singleton, and the best practice changed over time.
In Kotlin, you just create an object at a package level, wherever you want it to be scoped, and you can access this object anywhere as a single instance.
object Singleton
In a more complete example, here’s a logger example:
object Logger {
val tag = "TAG"
fun d(message: String) {
Log.d(tag, message)
}
}
You would access this function D
as Logger.D
, and it’s available everywhere, and there’s just one instance.
Companion Objects (37:00)
Kotlin removes the concept of static
in objects and methods. To help with inter-operability we have companion objects. Normally you’d see something like an activity with a concept string called tag
and a static method to start the activity, creates an intent and you can access the tag statically, and you can access the method statically.
class LaunchActivity extends AppCompatActivity {
public static final String TAG = LaunchActivity.class.getName();
public static void start(Context context) {
context.startActivity(new Intent(context, LaunchActivity.class));
}
}
And here’s the same implementation in Kotlin:
class LaunchActivity {
companion object {
val TAG: String = LaunchActivity::class.simpleName
fun start(context: Context) {
context.startActivity(Intent(context, LaunchActivity::class))
}
}
}
Timber.v("Starting activity ${LaunchActivity.TAG}")
LaunchActivity.start(context)
We have our companion object, which is kind of like a singleton for this class. Inside that we have a string constant for tag
, and the start
function, and then we access it the same way as we did in Java.
Class Delegation (37:58)
Delegation is a pretty well-known pattern and Kotlin makes this a first-class language level feature. Here’s a typical Java example you would see:
public class MyList<E> implements List<E> {
private List<E> delegate;
public MyList(delegate: List<E>) {
this.delegate = delegate;
}
// ...
public E get(int location) {
return delegate.get(location)
}
// ...
}
We have my special implementation of lists which takes a list of the same type in the constructor. We can store that list internally, delegate to it, and then it passes the list methods onto the delegate when we want to. Here’s the same implementation in Kotlin:
class MyList<E>(list: List<E>) : List<E> by list
Using the by
keyword, we implement this list of E
, and we take in a list of the same type in the constructor.
Declaration-Site Variance (39:03)
This can be a confusing topic. First, let’s start off with this example of covariant arrays, which compiles just fine:
String[] strings = { "hello", "world" };
Object[] objects = strings;
An array of strings can be assigned to an array of object. But this does not:
List<String> strings = Arrays.asList("hello", "world");
List<Object> objects = strings;
You can’t assign a list of string to a list of object, because they don’t inherit each other. If you try to compile this in Java, you’ll get an incompatible types error. To fix this we use use-site variance in Java. We define our list of objects as a list whose type extends object, and now we can do that assignment. For that reason, you’ll see stuff like this in Java’s collections:
public interface List<E> extends Collection<E> {
public boolean addAll(Collection<? extends E> collection);
public E get(int location);
}
Where addAll
takes the collection, which extends type E
, but it returns type E
, the actual type. In Kotlin, you’ll see something like this instead:
public interface List<out E> : Collection<E> {
public fun get(index: Int): E
}
public interface MutableList<E> : List<E>, MutableCollection<E> {
override fun addAll(c: Collection<E>): Boolean
}
It has declaration-site variance, you’ll have to split out your collections. This interface is a list that takes in type, something that extends type E
, and returns type E
. Then we have a mutable list, which takes in type E
, and extends the list type E
.
Now we have an immutable and mutable type of collection, and the variance is much easier to understand as it’s explicitly declared. That means that you can assign a list of strings to a list of anything, even if it’s an array list or a mutable list, as long as it makes sense and the compiler allows it.
Operator Overloading (41:26)
enum class Coin(val cents: Int) {
PENNY(1),
NICKEL(5),
DIME(10),
QUARTER(25),
}
class Purse(var amount: Float) {
fun plusAssign(coin: Coin): Unit {
amount += (coin.cents / 100f)
}
}
var purse = Purse(1.50f)
purse += Coin.QUARTER // 1.75
purse += Coin.DIME // 1.85
purse += Coin.PENNY // 1.86
Here we have an enum of coin that we created, and each coin has an integer cent value. And then we have a purse
class, which has a float value for the amount of money it contains in dollars, and in this class, we create a function called plusAssign
. plusAssign
is a reserved function that the compiler will pick up on, and it will call this function whenever you use the +=
operator.
We create a purse
instance, and we can add coins to it now, and that will modify the internal value of amount and there’s operator overloading for pretty much everything.
Adding Kotlin To Your Project (42:25)
Adding to your Kotlin to your project is pretty easy as now you can just have it mixed in with Java classes. You’ll need to add the gradle plugin to your build script, apply the Kotlin Android plugin, add your source directories, and include the standard library. You should also install the Kotlin plugin to get language support in the IDE.
Resources to Get Started
Q&A (43:40)
Q: What are the reasons to not use it?
Michael: There are some performance considerations to make with certain features, such as annotation processing being slow. Overall I’d highly recommend it though.
Q: Does it work with Unit Tests?
Michael: Yes, we use Robolectric, and plain old user tests, and we use Mockito with some caveats. Nonetheless, Kotlin’s interoperability allowed us to switch to it over time as our codebase grew.
Q: Can you still debug the whole way through?
Michael: It’s just JVM bytecode, and it’s integrated nicely with IntelliJ because JetBrain wrote both of them. We previously used Retrolambda, but the bytecode that it generates is not necessarily the source code that you wrote, leading to a breakpoint mess.
Q: What about Jack and Jill?
Michael: Jack and Jill won’t work with Kotlin because it goes from source right to text, and Kotlin still has to go to that bytecode step.
Q: Does Kotlin have a problem with reflection?
Michael: The only problem with reflection is that it adds another library that is about as big as the standard runtime. However, you can always drop down to Java objects for optimized reflection.
Receive news and updates from Realm straight to your inbox