-
Notifications
You must be signed in to change notification settings - Fork 99
1.) Major Syntax Differences
The first thing we might notice when we look at Kotlin code is that:
-
we must specify functions with
fun
-
we must specify argument list with the name of the argument first, colon, then the type
-
we must specify the return type of the function at the end, also separated with a colon
-
void
functions don't need to have anything written there, and implicitly they returnUnit
-
we must specify
@Override
asoverride
(mandatory language keyword) -
we must use
Any?
instead ofObject
-
there is no
new
keyword for instantiating classes
So for example, the following code:
package guide.to.kotlin;
public abstract class BaseClass {
// ...
}
public class MyClass extends BaseClass {
private final Map<String, Object> objects = new HashMap<>();
public MyClass() {
System.out.println("MyClass created");
}
@Override
protected void addToMap(String key, Object value) {
objects.put(key, value);
}
public <T> T getObject(String key) {
return (T) objects.get(key);
}
}
final MyClass myClass = new MyClass();
would now in Kotlin look like this:
package guide.to.kotlin
abstract class BaseClass {
// ...
}
class MyClass: BaseClass() {
private val objects = hashMapOf<String, Any?>()
init {
println("MyClass created")
}
override fun addToMap(key: String, value: Any?) {
objects.put(key, value);
}
fun <T> getObject(key: String): T =
objects.get(key) as T
// note: we could have used `{ return ... }` here,
// but as it is single-line, this is preferred.
}
val myClass = MyClass()
What's important to know in Kotlin is that:
-
var
represents a regular mutable variable. Althoughvar
should only be used when we actually want to mutate the variable over time, andval
should be used where able. -
val
represents afinal
variable in Java. -
Each
var
field exposes agetter
/setter
, and eachval
field exposes agetter
to Java consumers. In fact, specifyingval blah: String get() = "Text"
is equivalent topublic String getBlah() { return "Text"; }
. -
var
s can have different visibility modifiers, for example a public getter, but private setter.
With all that in consideration, an interface like this:
public interface MyContract {
String getValue();
void setValue(String value);
String getName();
void doSomething(String input1, String input2);
}
looks like this in Kotlin:
interface MyContract {
var value: String
val name: String
fun doSomething(input1: String, input2: String)
}
And implemented like this
class MyImpl(
override var value: String,
override val name: String
): MyContract {
override fun doSomething(input1: String, input2: String) {
...
}
}
Constructors are fairly special in Kotlin, in the sense that they also contain field declarations, and even passing arguments to super-constructors.
So the following Java code:
public class MyClass extends BaseClass implements MyInterface {
private String mutableField = "";
private final String name;
public MyClass(String tag, String name) {
super(tag);
this.name = name;
}
public String getMutableField() {
return mutableField;
}
public void setMutableField(String mutableField) {
this.mutableField = mutableField;
}
@Override
public void methodFromInterface() {
}
}
is (almost) equivalent to the following in Kotlin:
class MyClass(
tag: String,
private val name: String,
var mutableField: String = "" // default argument
): BaseClass(tag), MyInterface {
override fun methodFromInterface() {
}
}
It is worth knowing that it is possible to define multiple constructors using the constructor
keyword, which can call other constructors with this()
or super()
. It is also needed if annotations are added to it (for example, class MyClass @Inject constructor() {}
.
class MyCustomView : FrameLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, @AttrRes defStyleAttr: Int) : super(context, attrs, defStyleAttr)
If you know that a value in Kotlin will be initialized later so you can guarantee that it will be non-null on any future access, instead of defining it as name: String?
, you can define it as a lateinit var name: String
.
Please note that accessing an uninitialized lateinit variable results in an exception.
It's fairly common to use lateinit var
inside Activity/Fragment calls (because you commonly initialize things in onCreate
/onViewCreated
), so it's best to know about this.
Compared to just saying
enum Colors {
RED,
BLUE
}
Kotlin uses the term enum class
instead.
enum class Colors {
RED,
BLUE
}
Good news is that enum class
es know all the goodies that Java enums do, like constructor arguments, or abstract methods.
In Java, you define an annotation with public @interface MyAnnotation {}
. In Kotlin, you do this with annotation class MyAnnotation
.
Any class we create is final
by default - meaning that it cannot be extended. This is because inheritance is easy to get wrong, so as per "Effective Java", Kotlin tries to enforce this by ensuring that a class can be inherited from only if it is explicitly marked as open
.
Therefore,
public final class FinalClass {
}
is
class FinalClass {
}
While the following in Java
public class SomeClass {
}
is the following in Kotlin
open class SomeClass {
}
Note that abstract class
es in Kotlin are open
by default.
Regular for-loops in Java are actually trickier in Kotlin, as it introduces new keywords. However, we should also note that it's quite common to use .foreach {
instead of for(...) {
loops (or some other constructs from the Collections API like map
or filter
that can do the for-if
for us instead), but it can be useful to know about for
loop quirks nonetheless.
for(int i = 0; i < n; i++) {
// do something
}
is now:
for(i in 0 until n) {
// do something
}
where 0 until n
is actually a range defined as 0..n-1
.
Regular loops with iterators look like this:
for(pair in pairs) {
...
}
And pairs can actually be deconstructed (seen later), but this can be applied with maps, for example:
for((key, value) in map.entries) {
...
}
In Kotlin, it's possible to very easily (maybe a bit too easily) define a singleton. Meaning any top-level object
exposes a .INSTANCE
accessor to Java. It looks like this:
object MySingleton {
// this is a singleton
fun something() {
}
}
And accessed from places like this:
MySingleton.something()
Fairly straightforward. Try not to over-use it if possible (as you should normally be wary of singletons).
But it's worth noting that you can use it in the following ways, too (thanks jmfayard):
1.) Have an object where you define your constants:
object Network {
const val API_URL = "http://httpbin.org"
const val TIMEOUT = 1000
}
2.) Use it as a namespace for certain functions
// this is possibly a static factory method for Java users
object Positions {
@JvmStatic
fun create(x: Int, y: Int) : Int = Position(x, y)
//....
}
An important thing to note is that static
s "don't exist" in Kotlin.
So while the following in Java is pretty legit:
public class MyClassWithStatics {
public static final String SOME_CONSTANT = "Hello!";
public static String someUglyMutableField = "don't do this like ever";
public static void hello() {
System.out.println(SOME_CONSTANT + " " + someUglyMutableField);
}
}
public class MyOtherClass {
void doSomething() {
String constant = MyClassWithStatics.SOME_CONSTANT;
String someUglyMutableField = MyClassWithStatics.someUglyMutableField;
MyClassWithStatics.hello();
}
}
Instead, Kotlin introduces the concept of companion object
s that store static fields and methods.
Now, this might seem crazy, but it has the super-nice benefits that lets you inherit "static functions" from interfaces into your companion object. This might sound crazy, but this makes sense for example when you need to use the Parceler<T>
interface in combination with the @Parcelize
annotation using kotlin-android-extensions
(to simplify the creation of CREATOR
s).
It's also important for allowing the creation of extension functions that are accessed as "static methods" of a given class.
So the above code in Kotlin would look like this:
class MyClassWithStatics {
companion object {
const val SOME_CONSTANT = "Hello!"
@JvmField var someUglyMutableField: String = "don't do this like ever"
@JvmStatic fun hello() { println("$SOME_CONSTANT $someUglyMutableField") }
}
}
// consuming Java code!
public class MyOtherClass {
public void doSomething() {
String constant = MyClassWithStatics.SOME_CONSTANT;
String someUglyMutableField = MyClassWithStatics.someUglyMutableField;
MyClassWithStatics.hello();
}
}
Now you might ask, "wait what are those @JvmField
and @JvmStatic
annotations for".
If you don't specify those, your Java consumer side (if you have such) would look like this:
public class MyOtherClass {
public void doSomething() {
String constant = MyClassWithStatics.Companion.INSTANCE.SOME_CONSTANT;
String someUglyMutableField = MyClassWithStatics.Companion.INSTANCE.someUglyMutableField;
MyClassWithStatics.Companion.INSTANCE.hello();
}
}
Which is honestly hideous, so if you use the auto-converter, beware that it puts this stuff in your code by default instead of the annotations above.
In Java, we have the following visibilities:
-
private
-
package (this is the default)
-
protected
-
public
In Kotlin however, we have the following visibilities:
-
private
-
protected
-
public (this is the default)
-
internal
What's important to note is that internal
is not the same as package
, don't try to use internal
as a replacement for package
.
There is NO package visibility in Kotlin.
I say this because your auto-converter will add internal
everywhere. Just make them public instead.
internal
has two odd caveats:
1.) it has a very strange access from Java code unless you use @JvmName
2.) it doesn't really do anything if your code is not multi-module (because internal
restricts visibility to only the current compilation module).
So use internal
only where it makes sense.
In Kotlin, all nested classes are equivalent with public static class
in Java.
So to make a nested class be an "inner" class, it must be specified as inner class
.
class MyAdapter: RecyclerView.Adapter<MyAdapter.ViewHolder>() {
inner class ViewHolder(view: View): RecyclerView.ViewHolder(view) {
...
}
}
It can happen that you are using a library written in Java that uses a reserved keyword in Kotlin (such as object
or when
or anything that includes a $
symbol).
In this case, Kotlin allows you to use this without problems, as long as it is wrapped in backticks (`).
For example,
Mockito.`when`(text.hashCode())
However, we can help this scenario by using an import alias, and use a different name.
import org.mockito.Mockito.`when` as whenever
whenever(text.hashCode())
We might be used to using instanceof
and (MyClass)
for casting, but Kotlin has a different syntax.
public class Cat {
private final String name;
public Cat(String name) {
this.name = name;
}
public void meow() {
System.out.println("The cat " + name + " meowed.");
}
}
void doSomething(Object obj) {
if(obj instanceof Cat) {
((Cat)obj).meow();
}
}
would now look like this in Kotlin:
class Cat(private val name: String) {
fun meow() {
println("The cat $name meowed.")
}
}
fun doSomething(obj: Any?) {
if(obj is Cat) {
obj.meow() // note that as `obj` is a `val`, the cast is automatic here
}
}
If we cannot leverage smart-casting, then we can use as
(throws exception on failure) or as?
(returns null).
(obj as? Cat)?.meow()
Also worth noting here that smart-casting wouldn't work with mutable nullable field variables, and should be re-assigned to a val
first, like val name = name
. But there will be an example for this later.
Anonymous interface implementations have a very quirky syntax in Kotlin. Namely you need to create an anonymous object. It's easier to show an example.
myView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// do something
}
});
would now be
myView.setOnClickListener(object: View.OnClickListener {
override fun onClick(view: View) {
// do something
}
})
Of course, I can sense pitchforks on the horizon because you can use Java 8 syntax and lambdas. And if you use Kotlin against a Java interface (or a fun interface
since Kotlin 1.4.20+), then SAM (single-abstract-method) conversion will let you use a lambda expression in Kotlin as well.
myView.setOnClickListener((view) -> {
// do something
});
and
myView.setOnClickListener { view ->
// do something
}
Note that we can ditch the enclosing ()
around the last lambda argument of a function, this is called "trailing lambdas".
Important Kotlin 1.4 change: Since Kotlin 1.4, you can also use trailing lambdas (and not just object: MyInterface {
) with interface
s, if you declare the interface as fun interface
. This needs the interface to only have a single method.
Indeed, in Kotlin there is no ternary operator! It's very frustrating when you first run into it. Then you forget that you ever wanted it.
There are two tricks to know about the ternary operator in Kotlin:
-
you can use
val something = if(...) ... else ...
-
you generally shouldn't use
if-else
anyways in most cases and should generally prefer= when
val something = when {
value != "hello" -> "beep"
else -> "boop"
}
The nice thing to note here is that you can combine these keywords into assignments.
To quote the original docs:
In Kotlin, if is an expression, i.e. it returns a value. Therefore there is no ternary operator (condition ? then : else), because ordinary if works fine in this role.
As shown above, you can turn methods like this:
fun getRepository(): Repository {
return repository
}
into
fun getRepository(): Repository = repository
and in fact, in this particular case, we can ditch fun get
and make it a val get()
:
val repository: Repository get() = repository
If you're using Dagger and you have a bunch of @Provides fun getSomething() { return something }
methods, these tricks should generally be applied to them.
Kotlin provides functions that initialize collections as part of the standard library, and should typically be preferred compared to using their constructors directly.
For example,
private final List<String> strings = new ArrayList<>();
is generally replaced with
private val strings = arrayListOf<String>()
You may note that "hold on, the class used to be seen as a List
, but now it's an ArrayList<String>
", and you'd be right. In Kotlin, the mutator methods are separated into a separate MutableList
interface, so if we do want to mutate this list in place over time, we should use either mutableListOf<T>()
, or arrayListOf<T>()
.
Same goes for mapOf<K, V>()
, mutableMapOf<K, V>()
, and linkedMapOf<K, V>()
.
And of course for setOf<T>()
, mutableSetOf<T>()
, hashSetOf<T>()
, I'm sure you get the idea.
Also, you create a list with a size of elements and a lambda for each element. (thanks @jmfayard)
val squares = List(size = 6) { index -> index * index }
// same as:
val squares = listOf(0, 1, 4, 9, 16, 25)
In annotations (and in annotations only!), single-argument vararg parameters must be passed as an array.
Previously it used to be with arrayOf(value1, value2)
(and single-arg was allowed without arrayOf
), but people found this verbose compared to Java's { value1, value2 }
.
So Kotlin added support for "array literal in annotations" in 1.2, but now single-arg must also be passed in array.
@Component(modules = [PresentationModule::class], dependencies = [SingletonComponent::class])
interface PresentationComponent {
}
Many names that a given method had in Java are converted implicitly to "operator"s in Kotlin.
For example, ==
in Kotlin actually translates to .equals()
, and ===
is referential equality.
Also a notable example is String value = map.get("key")
can be written as val value = map["key"]
instead.
<
, <=
, >
and >=
are mapped to .compareTo
calls, and it is possible to override other operators such as +=
, -=
, +a
and so on.
Because in Java it was very common to do this:
try {
...
} catch(Exception e) {
throw new RuntimeException(e);
}
Instead, in Kotlin, all exceptions are unchecked. But you can still catch and throw them as you normally would.
try {
...
} catch(e: Exception) {
...
} finally {
...
}
What's important to note is that try(Closeable closeable = new Closeable()) { ... }
from Java is actually implemented with a function called use
.
Closeable().use { closable ->
...
}
It might be important to know that volatile
is no longer a keyword, but you can use @Volatile
to achieve the same behavior.
For synchronized, we can use the synchronized(lock) { ... }
function from the Kotlin standard library.
In Java, we can write the following:
public class MyClass<T extends SomeClass & Comparable> {
...
}
If we need to specify multiple bounds in Kotlin, we need to use a very different syntax.
class MyClass<T: SomeClass> { // this would be single-bounds
class MyClass<T>
where T: SomeClass,
T: Comparable { // multiple generic bounds