This is the part 9 of series Java to Kotlin - Learning Simplified. I hope you are learning something new from every part. I believe last 2 parts were little difficult to grasp but this part is very very easy.
If you have not read part 8, please do here.
Index of this Part: Collection Utilities, Generics, Annotations
Collection Utilities
In Java and Kotlin both, we have similar collections and data structure. The slight difference is Kotlin offers a lot of utility methods and concise
syntax to create collections. We do not consider Array
under collection or not but for the sake of this topic, I am including Array as well.
The Kotlin utilities are available in the package kotlin.collections
Kotlin is offering xxxOf
utility method for almost any collection+array you want. To name a few there are arrayOf
, listOf
, arrayListOf
, mapOf
, setOf
, emptyXXX
etc
Interesting part to note is, the default collection objects are immutable
unless you specify. It means you can not add the elements later, you need to ask for mutableXXX
explicitly if thats what you desire. Few examples are:
// Kotlin
val cars = arrayOf("iPace","GLA 100","Mustang","Sirocco")
for(car in cars) {
// do your magic
}
val cars = listOf("iPace","GLA 100","Mustang","Sirocco")
for (car in cars){
println(car)
}
val isPresent = "iPace" in cars // π₯ you can use in with collection
// same goes to set
Kotlin has a Pair
class which it uses inside Map, There are two ways to create a Pair:
// Kotlin
val p1 = Pair(1, "Gaurav")
val p2 = 1 to "Gaurav"
π‘ Check the syntax in 2nd statement above, We can use X to Y
syntax to create a pair. Map is made up of pairs.
// Kotlin
val m = mapOf( 1 to "Gaurav", 2 to "Khanna", 3 to "Gaurav Khanna")
for ((key, value) in m){
println("Key is $key and value is $value")
}
// you can access map element as
m.get(key)
// OR
m[key]
π₯ Map elements can also be accessed in array like fashion map[key]
π‘ In Java the default collections are mutable
and we need to create immutable explicitly if required. Kotlin took to opposite approach and in my opinion it makes me think clearly my architecture.
There are collection transformation functions like filter
, map
similar to Java. I suggest you to read official documentation here
Generics
Collections without generics is like politics without scams. They complete each other. The goodpart is Kotlin and Java both have same Generic implementation with slight syntax change. I do not have preference for any but I see people tend to like Kotlin syntax over Java in longer run.
// a simple hierarchy of Car
open class Car{}
open class RaceCar:Car(){}
class SuperCar:RaceCar(){}
class CityCar:Car(){}
Letβs see generic in action below:
// Java
class Track<T>{}
Track<Car> carTrack = new Track<Car>(); ππ
Track<RaceCar> raceCarTrack = new Track<RaceCar>(); ππ
carTrack.add(new Car()); β
allowed
carTrack.add(new RaceCar()); β
allowed
raceCarTrack.add(new RaceCar()); β
allowed
raceCarTrack.add(new Car()); β not allowed
π‘ raceCarTrack
is made up of RaceCar
and it can have RaceCar
OR any of its subclass as per OOPS IS
relation
But
// Java
carTrack = raceCarTrack β not allowed
π€― At first glance it looks odd! Why canβt we assign raceTrack to carTrack Instance. The answer is very simple, carTrack is made of Track<Car>
which can accept any Car
but raceCar is made of Track<RaceCar>
which can accept any RaceCar
if the assignment of carTrack = raceCarTrack
was allowed, you could do carTrack.add(cityCar)
and raceCar Track contains a city car now. Boom!! Not fair and crash! Accident! Bad things!!
// why not allowed
carTrack = raceCarTrack;
// if it was allowed
carTrack.add(new CityCar()); // allowed because carTrack can accept any car
β Now race track has a city car! Boom! ππ₯ππ₯π
To solve the other problem, Java has a syntax <? extends T>
which means T OR any child of T is allowed but only to get from the class, not to add. It is called Covariance
, Letβs make the above code work:
// Java
Track<? extends Car> carTrack = new Track<>();
Track<RaceCar> raceCarTrack = new Track<RaceCar>();
carTrack = raceCarTrack; β
allowed with a catch
// Now if you use T anywhere as input in the Track
// Java wont allow to call the function
π Java error when you try to send any Car instance in any method of the carTrack : capture not allowed
Kotlin solution for co-variance
// Kotlin
class Track<T>{
var obj:T? = null
fun add(t:T){}
fun get():T{
return obj!!
}
}
var carTrack: Track<Car> = Track()
var raceCarTrack = Track<RaceCar>()
carTrack = raceCarTrack β not allowed , covariance
Kotlin has a different syntax for the same problem. It is out
π‘ out
of Kotlin is equivalent to ? extends T
in Java, Here is Kotlin solution:
// Kotlin, either we can make the instance co-variant OR the class
// Solution 1
// now T can only be used as return type and not in the paramters
class Track<out T>{
fun add(t:T){} β not allowed
fun get():T{} β
allowed
}
// Solution 2
var carTrack: Track<out Car> = Track()
// we can not use Car in the function parameters but only accept
π Kotlin error when you try to send any Car instance in any method of the carTrack : out projected type prohibits the use inside
ContraVariance
// Java
class Track<T>{}
Track<Car> carTrack = new Track<Car>(); ππ
Track<RaceCar> raceCarTrack = new Track<RaceCar>(); ππ
raceCarTrack = carTrack; β not allowed
If the assignment raceCarTrack = carTrack
was allowed, then there is a clear problem. raceCarTrack instace is type Track<RaceCar>
and when it will read from the Track, it will expect only RaceCars
but carTrack
can have other types as well. The software might try to fit race tyres to city car! Boom! Bad things! Lets see solution:
// Java solution
Track<Car> carTrack = new Track<Car>();
Track<? super RaceCar> raceCarTrack = new Track<RaceCar>();
// Now it will work
raceCarTrack = carTrack; β
allowed with a catch
// we can add cars in the raceCarTrack
raceCarTrack.add(new RaceCar()); β
allowed
// but we can not read from raceCarTrack
Car xyz = raceCarTrack.get(); β not allowed
In the java solution ? super T
, as you are not sure what type you will get at runtime, read is not allowed but only write is allowed.
Kotlin contraVariance solution
π‘ Kotlin offers same solution with different syntax. The syntax is in T
// Kotlin
class Track<in T>{
fun add(t:T){} β
allowed
fun get():T{} β not allowed
}
Kotin in T
is same as Java ? super T
, which means T is only allowed to write. T is allowed as a parameter in function but not as return type.
π‘ out
means out is allowed (return type) and in
means only in is allowed (method parameters) which is easier to read than ? extends
and ? super
Enum and Sealed Classes
Enum in kotlin is same as Java with a small syntax change. We need to write enum and class both keywords in Kotlin. Lets see a snippet:
// Java
enum States{
ERROR("Some error while downloading"), LOADING("Downloading the file.."), COMPLETED("Downlaod Completed");
States(String message) {
}
}
// Kotlin
enum class States(val message: String) {
ERROR("Some error while downloading"),
LOADING("Downloading the file..")
COMPLETED("Downlaod Completed")
}
The only difference is class
keyword! What happened to concise
principle? I have no idea!
π‘ Have you ever felt need of a state/property in one of the ENUM, lets say in our example LOADING
shuld show a message and the load percentage. In Java if we add such property to one enum instance, it has to be added in all. Many developers use a dummy value in other enum instances which overall is a sacrifice and not a good architecture.
π₯ Presenting Sealed Classes
There are many adjectives this concept has gained. Few say it is enum on steriod, feww say it is enum on swag, few day it is not enum, few say β¦.! Let people say what they want π₯ I call it Smart Enum
. You are free to call whatever you want. The concept is simple, It is an abstract class which has a limited scope. It can only be extended in the same file and it can be used similar to enums (e.g in When block). Here is an example:
// Kotlin
sealed class SealedStates(val message:String) {
object ERROR:SealedStates("Some error while downloading")
object COMPLETED:SealedStates("Downlaod Completed"),
class LOADING(var percentage:Int):SealedStates("")
}
π‘ π€― π₯ In the above example, we have a sealed class which we extended 3 times, ERROR
and COMPLETED
are made singleton on purpose where LOADING
can be instantiated with new percentage
The only rule is Sealed
classes must be declared in the same file. The sublclasses can have states as well. Are not they extension to enums? Sorry, Smart Enums
JVM related Annotation
Annotation as a concept is same in both Java and Kotlin, however there are a very few special annotations
which I want to cover here and these are: @JvmStatic
, @JvmField
, @JvmOverloads
, @file:JvmName
, and @Throws
. Letβs see some code:
@JvmStatic
Remember our Singleton and Companion classes in Kotlin? Singleton has only one instance named INSTANCE
and Companion classes are singleton inside any class which is static equivalent in Kotlin classes. But if we access singleton OR companion from the Java code, we need to use following:
object Single{
fun doSomething(){}
}
class Car{
companion object{
fun doSomething(){}
}
}
Access these in Kotlin and Java
// FROM Kotlin
Single.doSomething() β
allowed even though it is singleton
Car.doSomething() β
allowed even though it is singleton
// FROM Java
Single.doSomething() β not allowed
Single.INSTANCE.doSomething() β
allowed
Car.doSomething() β not allowed
Car.Companion.doSomething() β
allowed
As we see in Java, we need to use the singleton instance
but kotlin has static
like structure. If you want to access these static fashion, you need @JvmStatic
annotations
// Kotlin with annotation
object Single{
@JvmStatic fun doSomething(){}
}
class Car{
companion object{
@JvmStatic fun doSomething(){}
}
}
// Now Java can call similar to Kotlin
// From Java
Single.doSomething() β
Car.doSomething() β
π‘ @JvmField
has exact use case but for fields
@JvmOverloads
If you remember Kotlin has a notion of default value in function parameters. If a function has a parameter with default value, Kotlin allows you not to pass the parameter but if you use same method from Java, Kotlin wants you to pass all the values. By marking it @JvmOverloads
, you say to Kotlin to generate overloaded methods for Java and you get variant will default value passed from the overloaded variant.
@file:JvmName
This annotation is very simple. If you have a file name MyFile.kt
Kotlin generates a clas for Java named MyFileKt
which you can use to access the top level members. If you want to rename the file for Java callers, you can use this annotation on the top of the Kotlin file above package statement. You can say @file:JvmName('MyCustomName')
and now Java can call const members with MyCustomName.
@Throws
It is a simple one. Java has a notion of checked exception and unchecked exceptions. If a method is throwing some checked exception then the caller must handle it. But Kotlin does not differentiate between checked and unchecked exception. If you want Java to handle the exception, use @Throws(ExceptionName)
in front of the fun definition.
Summary
We started this part with Collection Utilities
and saw many offerings from Kotlin. A few popular are arrayOf
, listOf
, mapOf
and setOf
. Collections by default are immutable in Kotlin and we have mutableXXX
variants for all.
We learned Generics after that and saw Kotlin flavor of Covariance
and contraVariance
. I feel Kotlin syntax out T
and in T
is easier than Java alternative ? extends T
and ? super T
.
Then we saw enum
and Smart enum
. Enum is same in both Kotlin and Java where Kotlin forces us to use class
keyword along with enum
. Sealed classes are what I am referring to smart enum
Sealed classes are special as they are abstract and their subclasses must be defined in the same file. You can use them as enum + extended enums.
Finally we learned popular annotations in Kotlin which you will need to interact with Java. @JvmStatic
, @JvmField
, @Throws
, @JvmOverloads
, @file:JvmName