Hopping Islands: From Java to Kotlin Part 5
This series is aimed at existing Java developers that might feel tempted to switch over.
The goal of this series is to introduce Kotlin as a language you can use in tandem with Java, or even as a complete replacement.
Across the series, I will be covering a multitude of topics (usually with a few topics per part) on the benefits of using Kotlin over Java.
Here are the topics I picked out for Part 5 (open class got merked and replaced by functional interfaces)- lateinit modifier
- Functional Interface
- delegation
lateinit modifier
In Kotlin, things are meant to be as intuitive as possible. That means requiring your properties to return something! However, even at jetbrains they know that you aren't always instantiating every property and will often let them be nullable types.
You know how jetbrains feels about null... so they created lateinit
variable modifier, it stands for "late initialization" and functions like a normal variable (unless you are using it improperly!!).
lateinit
allows a variable to remain uninitialized until you set it. However, you should be wary, if you try to access the value when nothing has been set you will get an exception thrown at you, so ONLY USE LATEINIT IF YOU ARE 100% SURE YOU WILL HAVE SET THE VALUE BEFORE YOU EVER ACCESS IT!!!
There are a few built-in rules to use lateinit
- You can only use
lateinit
with a var
, this is because you cannot set a val
- You cannot use
lateinit
with primitives, this is because you cannot find an invalid value for any primitive in kotlin. - You can however use the
Delegates.notNull()
property delegate to achieve the same functionality -
var lateInt: Int by Delegates.notNull()
- You cannot use
lateinit
with nullable types, this is because that is essentially how it works under the hood but without requiring you to deal with the nullable type
Functional Interface
This is the Kotlin implementation of Single Abstract Method Interface conversion that they introduced when using Java classes.
This is a very useful piece of functionality that really avoids a lot of boilerplate code for simple things.
You define a functional interface just like any other interface, except fun
is in front of interface
!
To instantiate an instance of a functional interface, you simply call the type and put your braces just like a last parameter lambda and put your code in! Your lambda will have the same signature as the one abstract function in the interface.
There are a few rules to fun interface
- You can only define a single abstract function, this is for extremely convenient syntax purposes and boilerplate elimination
- You can define as many functions as you want, but only ONE of them may be abstract (no implementation provided)
- You cannot define any abstract properties. This is because there is no way to do it via SAM lambda syntax!
Here is an example usage from one of my more recent personal projects
Code:
fun interface AcceptsItemPolicy<T> {
fun test(item: T): Boolean
}
val generalStorePolicy = AcceptsItemPolicy<Item> { item -> // This lambda is (Item) -> Boolean, so item is an Item
item.isTradable()
}
Delegation
Delegation is a very extremely powerful feature that will obliterate boilerplate code all over the place!
First off, let's talk property delegation! You may have noticed some weird syntax in the lateinit
modifier. What is by
? Well, that's the delegation keyword! It's how you delegate a property (or class!) type to a separate class without sacrificing API readability and confusing types.
There are 4 parts to a property delegate. Let's take the lateinit
example: var lateInt: Int by Delegates.notNull()
-
var lateInt
-- This is the property itself -
: Int
-- This is the provided type (can be ommitted but you must provide a type to the delegate provider if your delegate is generic like Delegates.notNull()
) -
by
-- This is the keyword to provide a delegate for the left side from the right side -
Delegates.notNull()
-- This is the delegate provider!
The power is in the delegate provider. So how do I make a delegate provider? Your delegate provider must do 2 things - Implement
override fun getValue(thisRef: Any?, property: KProperty<*>): T // T can be whatever type you are returning if you have a specifically typed delegate
- This is REQUIRED in delegates for both
var
and val
. -
thisRef
is going to be the type of the "receiver" if any, this is only not Any?
if you are making an extension delegate. For example a delegate delegating hasSkull
for a Player
to an Attribute<T>
would look like: val Player.hasSkull: Boolean by Attribute("HasSkull")
-
property
is going to be the actual property, so in this case lateInt
. You can access the property's setter, getter, name, basically anything reflection would allow you to get from the property - this does NOT have to return the value you set it to, you can transform it in any way you'd like before giving the value back!
- Implement
public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T)
- This is REQUIRED in delegates for only
var
, as you cannot set a val
, but you can still use a delegate with setValue
for val
. - [inline]thisRef[inline] and
property
are going to be the same as getValue
-
value
this will be what you put after lateInt =
, in this case an Int
- It is recommended for the delegate to implement the interface
ReadOnlyProperty<Any?, T>
or ReadWriteProperty<Any?, T>
where Any?
is the receiver type and T
is the take\return type.
With this information, let's create a delegate that takes an Int
and when you get it, it will actually give you back that number but doubled! Try it out before you sneak a peek
It's seriously that easy!
Now on to another really nice feature: Class delegation!
Class delegation lets you delegate any class to an interface via a property in the constructor or an expression by using the same by
keyword but in the place where you declare what interface(s) you are implementing.
You don't have to write any special code to delegate an interface for a class like you did with the properties! It's as easy as having an interface and a value that is of the same type.
Here's an example where you provide 3 values to a class and make that class a list comprised of those 3 values!
Code:
class ThreeValueList<T>(val firstValue: T, val secondValue: T, val thirdValue: T): List<T> by listOf(firstValue, secondValue, thirdValue)
ThreeValueList<T>
is now a List<T>
! No overriding all the functions or having the IDE do it for you. It just works!
There are 3 rules to keep in mind when using class delegation - You can ONLY delegate to an interface! No
open
or abstract
classes. - You CANNOT delegate a
inline class
\value class
(as of kotlin 1.5, inline class
has been replaced with value class
) to an interface. As of Kotlin 1.7, you CAN do this. - You MUST manually override any functions or properties that have multiple declarations from multiple delegated interfaces!
I hope you found this useful and learned more about kotlin!