Kotlin is a language invented by JetBrains in 2011.
Kotlin is used as major language by some newer companies (like AWS, NetFlix and Uber) and are widely used for Android apps (and Google is officially supporting it for that).
Kotlin is in many ways an "improved Java" or "Java how it would look like if it was designed today".
Some of Kotlin's syntax is taken from C#.
Kotlin can target:
The entire article assume that the reader will be looking at using Kotlin for JVM platform.
The entire article assumes that the reader is proficient in the Java language and everything is explained by comparing it to Java.
Some C# syntax will also be shown. Both to show that Kotlin got some syntax from C# and to help developers that are more familar with C# than Java.
This is not a complete reference to Kotlin. This only covers some of the more important features. For more details see the Kotlin Reference or get a Kotlin book.
Variable declarations are different in Kotlin:
Kotlin | Java equivalent | Java 10+ equivalent | C# equivalent |
---|---|---|---|
val v = 123 | final int v = 123; | final var v = 123; | N/A |
val v: Int = 123 | final int v = 123; | final int v = 123; | N/A |
var v = 123 | int v = 123; | var v = 123; | var v = 123; |
var v: Int = 123 | int v = 123; | int v = 123; | int v = 123; |
Example:
package jwj
fun main(args: Array<String>) {
val iv1 = 123
//iv1 = 456
var iv2 = 456
println("$iv1 $iv2")
iv2 = 789
println("$iv1 $iv2")
val iv3: Int = 123
//iv3 = 456
var iv4: Int = 456
println("$iv3 $iv4")
iv4 = 789
println("$iv3 $iv4")
}
Types xxx in Kotin are not nullable. Types xxx? in Kotlin are nullable.
Kotlin | Java equivalent | C# equivalent | C# equivalent version 8.0+ with non-nullable reference types |
---|---|---|---|
var v: Int | int v; | int v; | int v; |
var v: Int? | Integer v; | int? v; | int? v; |
var o: MyClass | N/A | N/A | Myclass o; |
var o: MyClass? | MyClass o; | MyClass o; | Myclass? o; |
res = o.m() | res = o.m(); | res = o.m(); | res = o.m(); |
res = o?.m() | if(o != null) res = o.m(); else res = null; or: res = (o != null) ? o.m() : null; |
res = o?.m() | res = o?.m() |
Example:
package test
fun main(args: Array<String>) {
//val iv1: Int = null;
val iv2: Int? = null
println("${iv2 == null}")
//val sv1: String = null;
val sv2: String? = null
println("${sv2 == null}")
val sv3: String? = sv2?.substring(0, 1);
println("${sv3 == null}")
}
Kotlin has the same basic types as Java plus unsigned integers.
Kotlin | Java equivalent | C# equivalent |
---|---|---|
Byte | byte | sbyte |
Byte? | java.lang.Byte | sbyte? |
Short | short | short |
Short? | java.lang.Short | short? |
Int | int | int |
Int? | java.lang.Integer | int? |
Long | long | long |
Long? | java.lang.Long | long? |
Float | float | float |
Float? | java.lang.Float | float? |
Double | double | double |
Double? | java.lang.Double | double? |
Char | char | char |
Char? | java.lang.Character | char? |
Boolean | boolean | bool |
Boolean? | java.lang.Boolean | bool? |
String | N/A | N/A |
String? | java.lang.String | string |
Unit | void or Void | void |
UByte | N/A | byte |
UShort | N/A | ushort |
UInt | N/A | uint |
ULong | N/A | ulong |
Note that unsigned types in Kotlin are experimental.
Arrays are classes in Kotlin.
Kotlin | Java equivalent | C# equivalent |
---|---|---|
Array<MyClass> | MyClass[] | MyClass[] |
ShortArray | short[] | short[] |
IntArray | int[] | int[] |
LongArray | long[] | long[] |
FloatArray | float[] | float[] |
DoubleArray | double[] | double[] |
Example:
package jwj
@kotlin.ExperimentalUnsignedTypes // avoid warnings for new unsigned types
fun main(args: Array<String>) {
val vb: Byte = 123
println("$vb")
val vs: Short = 123
println("$vs")
val vi: Int = 123
println("$vi")
val vl: Long = 123L
println("$vl")
val vf: Float = 123.456f
println("$vf")
val vd: Double = 123.456
println("$vd")
val vc: Char = 'A'
println("$vc")
val v: Boolean = true;
println("$v")
val vs1: String = "ABC"
println("$vs1")
val vs2: String = """ABC"""
println("$vs2")
// new unsigned types:
val vub: UByte = 123U
println("$vub")
val vus: UShort = 123U
println("$vus")
val vui: UInt = 123U
println("$vui")
val vul: ULong = 123UL
println("$vul")
}
Kotlin has the normal arithmetic operators: +, -, *, / and %.
Kotlin has the normal comparison operators: ==, !=, <, <=, > and >=.
Note that == is same value and Kotlin has a separator operator === for same identity.
Kotlin | Java equivalent | C# equivalent |
---|---|---|
a == b | a.equals(b) | a.Equals(b) or: a == b |
a === b | a == b | Object.ReferenceEquals(a, b) |
Kotlin has the normal boolean operators: &&, || and !.
Kotlin use method calls for type conversion.
Kotlin | Java equivalent | C# equivalent |
---|---|---|
v.toByte() | (byte)v | (sbyte)v |
v.toShort() | (short)v | (short)v |
v.toInt() | (int)v | (int)v |
v.toLong() | (long)v | (long)v |
v.toFloat() | (float)v | (float)v |
v.toDouble() | (double)v | (double)v |
Note that in Kotlin explicit conversion is required for both larger->smaller and smaller->larger conversion unlike Java where it is only needed for larger->smaller.
Kotlin'a bitwise operators are different from Java.
Kotlin | Java equivalent | C# equivalent |
---|---|---|
shl | << | << |
shr | >> | >> |
ushr | >>> | N/A |
and | & | & |
or | | | | |
xor | ^ | ^ |
inv | ~ | ~ |
Example:
package jwj
fun main(args: Array<String>) {
val v1 = 123
val v2 = ((4 * v1 + 5) / 6 - 7) % 8
println("$v2")
var bv: Byte = 123
var iv: Int = 123
//bv = iv
bv = iv.toByte()
//iv = bv
iv = bv.toInt()
val b1 = 0x0000001
val b2 = b1 shl 31
val b3 = b1 xor b2
val b4 = b3.inv()
println("$b4")
val iv1 = 123
val iv2 = 123
println("${iv1 == iv2}")
println("${iv1 === iv2}")
val a = 'A'
val b = 'B'
val c = 'C'
val sv1 = "$a$b$c"
val sv2 = "$a$b$c"
println("${sv1 == sv2}")
println("${sv1 === sv2}")
}
Kotlin has very powerful control structures.
Kotlin | Java equivalent | C# equivalent |
---|---|---|
if(a) { foo() } else { bar() } |
if(a) { foo(); } else { bar(); } |
if(a) { foo(); } else { bar(); } |
res = if(a) b else c |
res = a ? b : c; |
res = a ? b : c; |
when(x) { 1 -> { foo() } 2 -> { bar() } else -> { throw new Exception("Ooops") } } |
switch(x) { case 1: foo(); break; case 2: bar(); break; default: throw new Exception("Ooops"); } |
switch(x) { case 1: foo(); break; case 2: bar(); break; default: throw new Exception("Ooops"); } |
when(o) { in 1..10 -> { println("in 1..10") } is Int -> { println("Int not in 1..10") } else -> { println("not Int") } } |
if(o instanceof Integer) { int io = (Integer)o; if(1 <= io && io <= 10) { System.out.println("in 1..10"); } else { System.out.println("Int not in 1..10"); } } else { System.out.println("not Int"); } |
if(o is int) { int io = (int)o; if(1 <= io && io <= 10) { Console.WriteLine("in 1..10"); } else { Console.WriteLine("Int not in 1..10"); } } else { Console.WriteLine("not Int"); } |
res = when(o) { is Int -> "Int" is String -> "String" else -> "Other type" } |
if(o instanceof Integer) { res = "Int"; } else if(o instanceof String) { res = "String"; } else { res = "Other type"; } |
if(o is int) { res = "Int"; } else if(o is string) { res = "String"; } else { res = "Other type"; } |
for(i in ia) { ... } |
for(int i : ia) { ... } |
foreach(int i in ia) { ... } |
for(i in 1..10) { ... } |
for(int i = 1; i <= 10; i++) { ... } |
for(int i = 1; i <= 10; i++) { ... } |
while(c) { ... } |
while(c) { ... } |
while(c) { ... } |
do { ... } while(c) |
do { ... } while(c) |
do { ... } while(c) |
Kotlin supports both default values for arguments and call by argument name.
Kotlin | Java equivalent | C# equivalent |
---|---|---|
fun m(v: Int = 0) { ... } |
void m() { m(0); } void m(int v) { ... } |
void m(int v = 0) { ... } |
m(v = 1) |
N/A | m(v: 1); |
Example:
package jwj
fun m(a: Int = 123, b: Int = 456): Unit {
println("$a $b")
}
fun main(args: Array<String>) {
val v = 7
val s1 = if(v == 0) "zero" else "not zero"
println(s1)
val s2 = when(v) {
0 -> "zero"
else -> "not zero"
}
println(s2)
val a = arrayOf(1, 2, 3)
for(i in a) {
println(i)
}
for(i in a.indices) {
println(i)
}
for(i in 1..3) {
println(i)
}
for(i in 3 downTo 1) {
println(i)
}
m(321, 654)
m(321)
m(b = 654)
m()
}
Both classes and methods are by default final in Kotlin unlike Java.
Properties in Kotlin are simply declared with val and var to indicate only getter or both getter and setter. If needed then explicit getter and setter can be specified.
One constructor is declared automatically by the class declaration.
A class can be declared as a data class to automatically get suitable equals, hashcode, tostring and copy methods added.
As a result the Kotlin code is often significant shorter than the equivalent Java code.
Visibility can be controlled via public, internal, protected and private modifiers. Public, protected and private has same meaning as in Java. Internal means visibile within same compilation. Default is public
Kotlin | Java equivalent | C# equivalent |
---|---|---|
class X { ... } open class Y { ... } |
final class X { ... } class Y { ... } |
sealed class X { ... } class Y { ... } |
class X { ... fun m1() { ... } open fun m2() { ... } ... } |
class X { ... final void m1() { ... } void m2() { ... } ... } |
class X { ... void m1() { ... } virtual void m2() { ... } ... } |
class X { val a: Int = 123 var b: Int ... } |
class X { private int a = 123; public int getA() { return a; } private int b; public int getB() { return b; } public void setB(int b) { this.b = b; } ... } |
class X { public int A { get; } = 123 public int B { get; set; } ... } |
class X { val a: Int = 123 get() { ... return field } var b: Int get() { ... return field } set(value) { ... field = value } ... } |
class X { private int a = 123; public int getA() { ... return a; } private int b; public int getB() { ... return b; } public void setB(int b) { ... this.b = b; } ... } |
class X { private int a = 123; public int A { get { ... return a; } } private int b; public int B { get { ... return b; } set { ... b = value; } } ... } |
class X(val v: Int) { ... } class Y(var v: Int) { ... } class Z(v: Int) : W(v) { ... } |
class X { private int v; public X(int v) { this.v = v; } public int getV() { return v; } ... } class Y { private int v; public Y(int v) { this.v = v; } public int getV() { return v; } public void setV(int v) { this.v = v; } ... } class Z extends W { public Z(int v) { super(v): } ... } |
class X { private int v; public int V { get { return v; } } public X(int v) { this.v = v; } ... } class Y { public int V { get; set; } public Y(int v) { V = v; } ... } class Z : W { public Z(int v) : base(v) { } ... } |
data class X(val a: Int, val b: Int) |
class X { private int a; private int b; public X(int a, int b) { this.a = a; this.b = b; } public int getA() { return a; } public int getB() { return b; } @Override public boolean equals(Object o) { // check for equality based on a and b } @Override public int hashCode() { // calculate hash code based on a and b } @Override public String toString() { // construct String based on class name, a and b } // copy method is not easily implementable in Java } Java 14+: record X (int a, int b) { } |
class X { public int A { get; set; } public int B { get; set; } public X(int a, int b) { A = a; B = b; } public override bool Equals(Object o) { // check for equality based on a and b } public override int HashCode() { // calculate hash code based on a and b } public override string ToString() { // construct String based on class name, a and b } // copy method is not easily implementable in C# } |
Abstract classes and interfaces in Kotlin are just like in Java.
Enums in Kotlin are just like in Java.
Kotlin | Java equivalent | C# equivalent |
---|---|---|
abstract class AC { ... abstract fun m() ... } class CC : AC { ... override fun m() { ... } ... } |
abstract class AC { ... abstract void m(); ... } class CC extends AC { ... @Override void m() { ... } ... } |
abstract class AC { ... abstract void m(); ... } class CC : AC { ... override void m() { ... } ... } |
interface I { ... fun m() ... } class C : I { ... override fun m() { ... } ... } |
interface I { ... void m() ... } class C implements I { ... @Override void m() { ... } ... } |
interface I { ... void m() ... } class C : I { ... void m() { ... } ... } |
enum class E { A, B, C } |
enum E { A, B, C } |
enum E { A, B, C } |
Example:
package jwj
open class P(val iv: Int) {
open fun dump() {
println("$iv")
}
}
class C(iv: Int, val sv: String) : P(iv) {
override fun dump() {
super.dump()
println("$sv")
}
}
data class Data(val iv: Int, val sv: String)
fun main(args: Array<String>) {
val o = C(123, "ABC")
o.dump()
val d1 = Data(123, "ABC")
val d2 = Data(123, "ABC")
println("$d1 $d2 ${d1==d2}")
val d3 = d1.copy()
val d4 = d1.copy(iv=456)
println("$d3 $d4 ${d3==d4}")
}
Kotlin does not have static instead it has objects and companion objects.
An object is an all static class or a singleton.
A companion object represent what in Java would be static part of a class.
Kotlin | Java equivalent | C# equivalent |
---|---|---|
object X { var v = 0 fun m() { ... } } ... tmp = X.v X.m() |
class X { private static int v; public static int getV() { return v; } public static void setV(int v) { X.v = v; } public void m() { ... } } ... tmp = X.getV(); X.m(); |
static class X { public static int V { get; set; } static void M() { ... } } ... tmp = X.V; X.M(); |
class X { companion object XO { fun mo() { ... } } fun m() { ... } } ... o = X() o.m() X.mo() |
class X { static void mo() { ... } void m() { ... } } ... o = new X(); o.m(); X.mo(); |
class X { static void Mo() { ... } void M() { ... } } ... o = new X(); o.M(); X.Mo(); |
Example:
package jwj
object O {
fun m() {
println("O.m")
}
}
class SC {
companion object SCO {
var compf = 0
}
var instf : Int
init {
compf++
instf = compf
}
override fun toString() : String {
return "$instf of $compf"
}
}
fun main(args: Array<String>) {
O.m()
val o1 = SC()
println(o1)
val o2 = SC()
println(o1)
println(o2)
}
Kotlin got a very nice feature to make delegation easier.
Kotlin supports operator overload.
Kotlin supports extension methods.
Kotlin | Java equivalent | C# equivalent |
---|---|---|
interface I { // method definitions // ... // ... // ... } class C : I { // implementation of all methods // ... // ... // ... } class CX(val real: I) : I by real { // few overrides that do extra work // and delegate to real object } |
interface I { // method definitions // ... // ... // ... } class C implements I { // implementation of all methods // ... // ... // ... } class CX(I real) implements I { private I real; public CX(I real) { this.real = real; } // implementation of all methods // that for a few do extra work // and for all delegate to real object // ... // ... // ... } |
interface I { // method definitions // ... // ... // ... } class C : I { // implementation of all methods // ... // ... // ... } class CX(I real) : I { private I real; public CX(I real) { this.real = real; } // implementation of all methods // that for a few do extra work // and for all delegate to real object // ... // ... // ... } |
class X { ... operator fun plus(o: X) = ... operator fun minus(o: X) = ... operator fun times(o: X) = ... operator fun div(o: X) = ... operator fun get(ix: Int): E = ... operator fun set(ix: Int, o: E) { ... } } |
N/A | class X { ... static X operator+(X a, X b) { ... } static X operator-(X a, X b) { ... } static X operator*(X a, X b) { ... } static X operator/(X a, X b) { ... } E this[int] { get { return ...; } set { ... value ...; } } } |
fun SomeType.newmethod(arg: ArgTyp): RetType { // do whatever with this (SomeType) and arg (ArgTyp) } |
N/A | static class Extensions { RetType newmethod(this SomeType me, ArgTyp arg) { // do whatever with me (SomeType) and arg (ArgTyp) } } |
Example:
package jwj
interface I {
fun m1()
fun m2()
}
class C1 : I {
override fun m1() { println("C1.m2") }
override fun m2() { println("C1.m2") }
}
class C2(val real: I) : I by real {
override fun m2() {
println("Voila")
real.m2()
}
}
data class MyInt(val v: Int) {
operator fun plus(o: MyInt) = MyInt(v + o.v)
operator fun minus(o: MyInt) = MyInt(v - o.v)
operator fun times(o: MyInt) = MyInt(v * o.v)
operator fun div(o: MyInt) = MyInt(v / o.v)
}
class TenInt {
val a = arrayOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
operator fun get(ix: Int) = a[ix]
operator fun set(ix: Int, v: Int) { a[ix] = v }
}
fun Int.plusPowerOf2(n: Int): Int {
return this + (1 shl n)
}
fun main(args: Array<String>) {
val ox = C2(C1())
ox.m1()
ox.m2()
val i1 = MyInt(1)
val i2 = MyInt(2)
val i3 = MyInt(3)
val i7 = i3 * i2 + i1
println(i7)
val ten = TenInt()
println(ten[7])
ten[7] = 17
println(ten[7])
println(1.plusPowerOf2(0))
println(1.plusPowerOf2(1))
println(1.plusPowerOf2(2))
println(1.plusPowerOf2(3))
}
Kotlin also provide operators for Java BigInteger and BigDecimal:
package jwj
import java.math.BigInteger
import java.math.BigDecimal
fun main(args: Array<String>) {
val i1 = BigInteger("1")
val i2 = BigInteger("2")
val i3 = BigInteger("3")
val i = i3 * i2 + i1
println(i.toString() + " " + i.javaClass.name)
val x1 = BigDecimal("1.1")
val x2 = BigDecimal("2.2")
val x3 = BigDecimal("3.3")
val x = x1 / x2 + x3
println(x.toString() + " " + x.javaClass.name)
}
Generic programming in Kotlin is similar to generic programming in Java - the syntax is just sligtly different.
Kotlin | Java equivalent | C# equivalent |
---|---|---|
class C<T> { ... } |
class C<T> { ... } |
class C<T> { ... } |
class C<T : MyClass> { ... } |
class C<T extends MyClass> { ... } |
class C<T> where T : MyClass { ... } |
interface I<in T> { // methods that take a T as input } ... fun m(o: I) { ... } ... m(instanceOfClassImplementingIforTorSuperClass) |
interface I<T> { // methods that take a T as input } ... void m(I<? super T> o) { ... } ... m(instanceOfClassImplementingIforTorSuperClass); |
interface I<in T> { // methods that take a T as input } ... void m(I<T> o) { ... } ... m(instanceOfClassImplementingIforTorSuperClass); |
interface I<out T> { // methods that produce a T as output } ... fun m(o: I) { ... } ... m(instanceOfClassImplementingIforTorSubClass) |
interface I<T> { // methods that produce a T as output } ... void m(I<? extends T> o) { ... } ... m(instanceOfClassImplementingIforTorSubClass); |
interface I<out T> { // methods that produce a T as output } ... void m(I<T> o) { ... } ... m(instanceOfClassImplementingIforTorSubClass); |
Example:
package jwj
class G1<T>(val o: T)
open class GP {
override fun toString() = "GP"
}
class GC : GP() {
override fun toString() = "GC"
}
class G2<T : GP>(val o: T)
interface I3I<in T> {
fun mi(o: T?)
}
interface I3O<out T>
{
fun mo(): T?
}
class G3I<T>: I3I<T> {
override fun mi(o: T?) { }
}
class G3O<T> : I3O<T> {
override fun mo(): T? = null
}
fun <T> f3i(o:I3I<T>) { }
fun <T> f3o(o:I3O<T>) { }
fun main(args: Array<String>) {
val o1int = G1<Int>(123)
println(o1int.o)
val o1infint = G1(123)
println(o1infint.o)
val o1string = G1<String>("ABC")
println(o1string.o)
val o1infstring = G1("ABC")
println(o1infstring.o)
//val dummy = G2(123)
//val dummy = G2("ABC")
val o2p = G2(GP())
println(o2p.o)
val o2c = G2(GC())
println(o2c.o)
f3i<GP>(G3I<GP>())
//f3i<GP>(G3I<GC>())
f3i<GC>(G3I<GP>())
f3i<GC>(G3I<GC>())
f3o<GP>(G3O<GP>())
f3o<GP>(G3O<GC>())
//f3o<GC>(G3O<GP>())
f3o<GC>(G3O<GC>())
}
Normal Kotlin generics are type-erased like Java generics, but Kotlin can use reified genercis in inline functions (functions where the byte code get inserted into the calling code).
Example:
package jwj
class C { }
fun <T> chktyp(o1: T, o2: Any): Boolean = o1!!::class == o2::class
inline fun <reified T> chktyp_smart(o: Any): Boolean = o is T
fun main() {
val o = C()
println(chktyp(C(), o))
println(chktyp_smart<C>(o))
}
Kotlin supports functions as first class objects and lambda's.
Kotlin | Java equivalent | C# equivalent |
---|---|---|
(Type1, Type2) -> ReturnType |
@FunctionalInterface interface SomeInterface { ReturnType someMethod(Type1 arg1, Type2 arg); } |
delegate ReturnType name(Type1 arg1, Type2 arg2) |
{ arg1: Type1, arg2: Type2 -> ... } |
(Type1 arg1, Type2 arg2) -> ... |
(Type1 arg1, Type2 arg2) => ... |
{ arg1, arg2 -> ... } |
(arg1, arg2) -> ... |
(arg1, arg2) => ... |
Example:
package jwj
val eps = 0.0000000001
fun diff(f: (Double) -> Double, x: Double): Double {
return (f(x + eps) - f(x)) / eps
}
fun newton_solve(f: (Double) -> Double): Double {
var x = 1.0
var xold:Double
do {
xold = x
x = x - f(x) / diff(f, x)
} while(x/xold < (1 - eps) || x/xold > (1 + eps))
return x
}
fun main(args: Array<String>) {
val sqrt_func = { x: Double -> x * x - 2 }
val sqrt2 = newton_solve(sqrt_func)
println("$sqrt2 ${sqrt2 * sqrt2}")
val curt3 = newton_solve({ x: Double -> x * x * x - 3 })
println("$curt3 ${curt3 * curt3 * curt3}")
val fortg_func = { x: Double, a: Double -> x * x * x * x - a }
val fort4_func = { x: Double -> fortg_func(x, 4.0) }
val fort4 = newton_solve(fort4_func)
println("$fort4 ${fort4 * fort4 * fort4 * fort4}")
}
Kotlin and Java classes with methods and property accessor/mutator are fully interoperable.
Example:
package jwj;
public class JavaClass {
private int v;
public int getV() {
return v;
}
public void setV(int v) {
this.v = v;
}
public void m() {
System.out.printf("v=%d\n", v);
}
public void callback() {
KotlinClass okt = new KotlinClass(0);
okt.setV(123);
okt.m();
}
}
package jwj
class KotlinClass(var v: Int) {
fun m() { println("v=$v") }
}
fun main(args: Array<String>) {
val oj = JavaClass()
oj.v = 123
oj.m()
oj.callback()
}
Calling from Kotlin to Java is in general very simple. But calling from Java to Kotlin have a few special cases due to Kotlins more compact code.
A Kotlin property becomes one field and one getter and if mutable also one setter. So if one put an annotation on a Kotlin property then one may want some control over where the annotation is actually put.
There are special get, set and field prefixes that can be used to specify where a property annotation is put.
Kotlin does not have static methods. But one may need to provide a static method for Java to call.
There is a @JvmStatic annotation to specify that a method should be a Java static method.
Kotlin can have default values for method arguments, but Java does not. One may want to call such a method from Java.
There is a @JvmOverloads annotation to specify that Kotlin should generate overloads for Java to call.
Kotlin does not have fields like Java. But one may need to expose a field for Java to use.
There is a @JvmField annotation to specify a property to be a Java field.
Example:
package jwj;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class JavaAnn {
public void check(C1 c1) {
try {
Field f = C1.class.getDeclaredField("p");
System.out.println(f.getAnnotation(A1.class) != null);
System.out.println(f.getAnnotation(A2.class) != null);
System.out.println(f.getAnnotation(A3.class) != null);
Method mg = C1.class.getDeclaredMethod("getP", new Class<?>[0]);
System.out.println(mg.getAnnotation(A1.class) != null);
System.out.println(mg.getAnnotation(A2.class) != null);
System.out.println(mg.getAnnotation(A3.class) != null);
Method ms = C1.class.getDeclaredMethod("setP", new Class<?>[] { String.class });
System.out.println(ms.getAnnotation(A1.class) != null);
System.out.println(ms.getAnnotation(A2.class) != null);
System.out.println(ms.getAnnotation(A3.class) != null);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
public void test(C2 c2) {
c2.p = c2.p + "X";
c2.m();
c2.m(0);
c2.m(0, 0);
O.m();
}
}
package jwj
annotation class A1
annotation class A2
annotation class A3
class C1 {
@get:A1
@set:A2
@field:A3
var p = "ABC"
}
class C2 {
@JvmField
var p = "ABC"
@JvmOverloads
fun m(a: Int = 123, b: Int = 456) {
println("C1.m $a $b")
}
}
object O {
@JvmStatic
fun m() {
println("O.m")
}
}
fun main(args: Array<String>) {
val ja = JavaAnn()
val c1 = C1()
ja.check(c1)
val c2 = C2()
ja.test(c2)
}
Kotlin fundamentally uses the Java runtime, but Kotlin has added some extra Kotlin specific classes.
This is an overview of some of the more important ones.
See also basic types here.
Kotlin has its own set of collections. They are very similar to Java's collection but provide better support for immutable collections.
The hierarchys are:
List<out E> MutableList<E> ArrayList<E>
Map<K, out V> MutableMap<K, V> HashMap<K, V>
Set<out E> MutableSet<E> HashSet<E>
There are conversion methods between arrays and collections:
Example:
package jwj
fun main(args: Array<String>) {
val a = arrayOf(1, 2, 3)
println(a.indices)
val lst1 = listOf(1, 2, 3)
println(lst1)
println(lst1.indices)
//lst1.add(4)
//lst1[1] = 0
val lst2 = mutableListOf(1, 2, 3)
println(lst2)
println(lst2.indices)
lst2.add(4)
lst2[1] = 0
println(lst2)
val map1 = mapOf(Pair(1, "A"), Pair(2, "BB"), Pair(3, "CCC"))
println(map1)
println(map1.keys)
println(map1.values)
//map1.add(Pair(4, "DDDD"))
//map1[2] = "X"
val map2 = mutableMapOf(Pair(1, "A"), Pair(2, "BB"), Pair(3, "CCC"))
println(map2)
println(map2.keys)
println(map2.values)
map2.put(4, "DDDD")
map2[2] = "X"
println(map2)
val set1 = setOf(1, 2, 3)
println(set1)
//set1.add(4)
val set2 = mutableSetOf(1, 2, 3)
println(set2)
set2.add(4)
println(set2)
}
Besides the usual methods for collections that Java has had since forever then Kotlin also got a number of methods for lambda usage.
Example:
package jwj
fun main(args: Array<String>) {
val lst = mutableListOf(1, 2, 3, 3)
println(lst)
println(lst.sum())
println(lst.count())
println(lst.average())
println(lst.any({ v -> v > 2 }))
println(lst.any({ v -> v > 3 }))
println(lst.all({ v -> v > 0 }))
println(lst.all({ v -> v > 1 }))
println(lst.distinct())
println(lst.filter({ v -> v > 1}))
println(lst.fold(0, { acc, v -> acc + v }))
lst.forEach({ v -> println(" $v") })
println(lst.map({ v -> v + 1 }))
println(lst.map({ v -> "Item: $v" }))
println(lst.associate({ v -> Pair(v, "Item: $v")}))
}
Kotlin got some features for enabling writing DSL (Domain Specific Language) in Kotlin.
Kotlin supports infix notation.
The statement:
a b c
is the same as:
a.b(c)
And when combined with lambda's that can produce some new language syntax.
Languages like Pascal and Visual Basic have a WITH statement.
In Kotlin:
o.a = 123
o.b = "abc"
can be written as:
with(o) {
a = 123
b = "abc"
}
And even though it looks like a with statement then it really is an infix method call with a lambda argument.
Example:
package jwj
class CI {
infix fun moveto(n: Int) {
println("moveto $n")
}
}
class CW {
var a = 0
var b = ""
}
fun main(args: Array<String>) {
val o = CI()
o.moveto(3)
o moveto 3
val w = CW()
with(w) {
a = 123
b = "abc"
println("$a $b")
}
}
The language seems pretty stable since version 1.4 so the last drawback no longer applies.
Kotlin release history:
Version | Release date |
---|---|
1.0 | February 15th 2016 |
1.1 | February 28th 2017 |
1.2 | November 28th 2017 |
1.3 | October 29th 2018 |
1.4 | August 17th 2020 |
1.5 | May 5th 2021 |
1.6 | November 16th 2021 |
1.7 | June 9th 2022 |
1.8 | December 28th 2022 |
1.9 | July 6th 2023 |
The article above covers Kotlin version 1.3. A few selected new features in newer versions are covered below.
Keyword for Java style functional interface:
package jwj
fun interface I {
fun doit(v: Int)
}
fun m123(h: I) {
h.doit(123)
}
fun main() {
m123({ v -> println(v) })
}
Inline value classes:
package jwj
@JvmInline
value class X(val v: Int)
fun main() {
val o = X(123)
println(o.v)
}
Unsigned integers are now stable and no longer experimental.
Collection builders are now stable:
package jwj
fun main() {
val lst = buildList {
add(123)
addAll(listOf(456, 789))
}
println(lst)
val mp = buildMap {
put(1, "A")
putAll(mapOf(Pair(2, "BB"), Pair(3, "CCC")))
}
println(mp)
}
Mostly experimental stuff and platform stuff.
Mostly experimental stuff and platform stuff.
A bunch of features became stable.
Open ended ranges Swift style lowerincl..<upperexcl:
package jwj
fun main() {
for(i in 0..2) println(i)
for(i in 2 downTo 0) println(i)
for(i in 0 until 3) println(i)
for(i in 0..<3) println(i) // new
}
The data classes are now supplemented with data objects:
package jwj
data object X {
var a: Int = 123
var b: Int = 456
}
fun main() {
X.a++
X.b++
println("$X ${X.a} ${X.b}")
}
The enum values function is replaced/supplemented by an entries property:
package jwj
enum class E { V1, V2, V3 }
fun main() {
for(e in E.values()) println(e)
for(e in E.entries) println(e) // new
}
Huge changes to the tool chain, the generated code and various platform stuff. But not many changes in functionality.
Version | Date | Description |
---|---|---|
1.0 | December 22nd 2018 | Initial version |
1.1 | March 24th 2019 | Add information about JVM annotation and DSL features |
1.2 | July 8th 2021 | Add small Java 10+ and Java 14+ notes |
1.3 | March 3rd 2022 | Add version history |
1.4 | September 29th 2023 | Add Kotlin 1.7, 1.8 and 1.9 |
See list of all articles here
Please send comments to Arne Vajhøj
I definitely believe that for some projects it will be beneficial to switch from Java to Kotlin.
Benefits of Kotlin compared to Java (in order of significance):
Overall I would expect Kotlin code to have 25-50% fewer lines of code than the same Java code.
Drawbacks of Kotlin compared to Java (in order of significance):