Kotlin is a statically-typed object-oriented programming language developed by JetBrains primarily targeting the JVM. Kotlin is developed with the goals of being quick to compile, backwards-compatible, very type safe, and 100% interoperable with Java. Kotlin is also developed with the goal of providing many of the features wanted by Java developers. Kotlin's standard compiler allows it to be compiled both into Java bytecode for the JVM and into JavaScript.
Kotlin has a standard IDE plugin for Eclipse and IntelliJ. Kotlin can also be compiled using Maven, using Ant, and using Gradle, or through the command line.
It is worth noting in $ kotlinc Main.kt
will output a java class file, in this case MainKt.class
(Note the Kt appended to the class name). However if one was to run the class file using $ java MainKt
java will throw the following exception:
Exception in thread "main" java.lang.NoClassDefFoundError: kotlin/jvm/internal/Intrinsics
at MainKt.main(Main.kt)
Caused by: java.lang.ClassNotFoundException: kotlin.jvm.internal.Intrinsics
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 1 more
In order to run the resulting class file using Java, one must include the Kotlin runt-time jar file to the current class path.
java -cp .:/path/to/kotlin/runtime/jar/kotlin-runtime.jar MainKt
Extensions are resolved statically. This means that the extension method to be used is determined by the reference-type of the variable you are accessing; it doesn't matter what the variable's type is at runtime, the same extension method will always be called. This is because declaring an extension method doesn't actually add a member to the receiver type.
If you want to lazy process a chain, you can convert to a Sequence
using asSequence()
before the chain. At the end of the chain of functions, you usually end up with a Sequence
as well. Then you can use toList()
, toSet()
, toMap()
or some other function to materialize the Sequence
at the end.
// switch to and from lazy
val someList = items.asSequence().filter { ... }.take(10).map { ... }.toList()
// switch to lazy, but sorted() brings us out again at the end
val someList = items.asSequence().filter { ... }.take(10).map { ... }.sorted()
You will notice the Kotlin examples do not specify the types. This is because Kotlin has full type inference and is completely type safe at compile time. More so than Java because it also has nullable types and can help prevent the dreaded NPE. So this in Kotlin:
val someList = people.filter { it.age <= 30 }.map { it.name }
is the same as:
val someList: List<String> = people.filter { it.age <= 30 }.map { it.name }
Because Kotlin knows what people
is, and that people.age
is Int
therefore the filter expression only allows comparison to an Int
, and that people.name
is a String
therefore the map
step produces a List<String>
(readonly List
of String
).
Now, if people
were possibly null
, as-in a List<People>?
then:
val someList = people?.filter { it.age <= 30 }?.map { it.name }
Returns a List<String>?
that would need to be null checked (or use one of the other Kotlin operators for nullable values, see this Kotlin idiomatic way to deal with nullable values and also Idiomatic way of handling nullable or empty list in Kotlin)
In Kotlin, it depends on the type of collection whether it can be consumed more than once. A Sequence
generates a new iterator every time, and unless it asserts "use only once" it can reset to the start each time it is acted upon. Therefore while the following fails in Java 8 stream, but works in Kotlin:
// Java:
Stream<String> stream =
Stream.of("d2", "a2", "b1", "b3", "c").filter(s -> s.startsWith("b"));
stream.anyMatch(s -> true); // ok
stream.noneMatch(s -> true); // exception
// Kotlin:
val stream = listOf("d2", "a2", "b1", "b3", "c").asSequence().filter { it.startsWith('b' ) }
stream.forEach(::println) // b1, b2
println("Any B ${stream.any { it.startsWith('b') }}") // Any B true
println("Any C ${stream.any { it.startsWith('c') }}") // Any C false
stream.forEach(::println) // b1, b2
And in Java to get the same behavior:
// Java:
Supplier<Stream<String>> streamSupplier =
() -> Stream.of("d2", "a2", "b1", "b3", "c")
.filter(s -> s.startsWith("a"));
streamSupplier.get().anyMatch(s -> true); // ok
streamSupplier.get().noneMatch(s -> true); // ok
Therefore in Kotlin the provider of the data decides if it can reset back and provide a new iterator or not. But if you want to intentionally constrain a Sequence
to one time iteration, you can use constrainOnce()
function for Sequence
as follows:
val stream = listOf("d2", "a2", "b1", "b3", "c").asSequence().filter { it.startsWith('b' ) }
.constrainOnce()
stream.forEach(::println) // b1, b2
stream.forEach(::println) // Error:java.lang.IllegalStateException: This sequence can be consumed only once.
See also: Kotlin reference documentation for Interfaces: Interfaces
Just like in Java, enum classes in Kotlin have synthetic methods allowing to list the defined enum constants and to get an enum constant by its name. The signatures of these methods are as follows (assuming the name of the enum class is EnumClass
):
EnumClass.valueOf(value: String): EnumClass
EnumClass.values(): Array<EnumClass>
The valueOf()
method throws an IllegalArgumentException
if the specified name does not match any of the enum constants defined in the class.
Every enum constant has properties to obtain its name and position in the enum class declaration:
val name: String
val ordinal: Int
The enum constants also implement the Comparable interface, with the natural order being the order in which they are defined in the enum class.
Reflection is a mechanism to introspect language constructs (classes and functions) at the runtime.
When targeting JVM platform, runtime reflection features are distributed in separate JAR: kotlin-reflect.jar
. This is done to reduce runtime size, cut unused features and make it possible to target another (like JS) platforms.
In contrast to Java's switch
, the when
statement has no fall-through behavior. This means, that if a branch is matched, the control flow returns after its execution and no break
statement is required. If you want to combine the bahaviors for multiple arguments, you can write multiple arguments separated by commas:
when (x) {
"foo", "bar" -> println("either foo or bar")
else -> println("didn't match anything")
}
In Kotlin, loops are compiled down to optimized loops wherever possible. For example, if you iterate over a number range, the bytecode will be compiled down to a corresponding loop based on plain int values to avoid the overhead of object creation.
Related question: Idiomatic way of logging in Kotlin
Input type parameters can be left out when they can be left out when they can be inferred from the context. For example say you have a function on a class that takes a function:
data class User(val fistName: String, val lastName: String) {
fun username(userNameGenerator: (String, String) -> String) =
userNameGenerator(firstName, secondName)
}
You can use this function by passing a lambda, and since the parameters are already specified in the function signature there's no need to re-declare them in the lambda expression:
val user = User("foo", "bar")
println(user.userName { firstName, secondName ->
"${firstName.toUppercase}"_"${secondName.toUppercase}"
}) // prints FOO_BAR
This also applies when you are assigning a lambda to a variable:
//valid:
val addition: (Int, Int) = { a, b -> a + b }
//valid:
val addition = { a: Int, b: Int -> a + b }
//error (type inference failure):
val addition = { a, b -> a + b }
When the lambda takes one parameter, and the type can be inferred from the context, you can refer to the parameter by it
.
listOf(1,2,3).map { it * 2 } // [2,4,6]
A type-safe builder is a concept, rather than a language feature, so it is not strictly formalized.
A single builder function usually consists of 3 steps:
The concept of type-safe builders is widely used in some Kotlin libraries and frameworks, eg.:
Type aliases is a feature of the compiler. Nothing is added in the generated code for the JVM. All aliases will be replaced by the real type.