Here are some tips on this new wonderful language…
- if statement can be an assignment, as every operator is a function within Kotlin, so for instance,
val message = if ( a== b) "True" else "False"
println (message)
Null Safety
By default, references cannot be assigned to null, Kotlin will throw compile time error.
To make an object reference hold null, we need to explicitly state, something like a question mark which indicates to the compiler that the reference can take on null values
var answer:String? = null
class Book {
var title: String
}
val book:Book? = Book()
book?.title = "Group"
When Statement
Kotlin introduces powerful when operator, which acts like switch but can type reference, so something like,
val correctAnswer = "42"
var answer = "42"
when(answer) {
CorrectAnswer -> println("Correct answer")
else -> println("Something else")
}
Try/Catch
val number:Int = try {
parseInt(someNumber) } catch(e: NumberFormatException) {
null
}
println("The result is $number")
For Loops
// Declare ranges for not just numbers, anything Comparable
var rangea = 'a' ..'z'
var inRange = 1..10
for(i in 10 downTo 1 step 2) {
println(i)
}
for(i in 1..10 step 2) {
println(i)
}
for( i in 1 until 10) { // doesnt include 10,
println(i)
}
//List
var numbers = listOf(1,2,3,4,5)
for( i in numbers) {
println(numbers)
}
// For loop with index
for((index, element) in numbers.withIndex() {
println("$index = $element")
}
//Map
var ages=TreeMap<String, Int>[]
ages['Kevin"] = 55
ages["Sam"] = 24
for((name, age) in ages) {
println("$name = $age")
}
Functions
Calling from Java
// Create package class and functions, Program.kt
@file:JvmName("DisplayFunctions")
package orm
fun display(message): String {
println(message)
}
// Now from within the Java class, Test.java
import orm.*;
public class Test {
public static void main(String args[]) {
System.out.println(DisplayFunctions.display("Hello from Java");
}
}
Functions can use default parameter
fun display(message:String, logLevel: Int = 0) {
println(message)
}
For calling from Java, add annotation
@JvmOverloads
println(message)
}
// Now this function can be called from Java with/without logLevel
Named Parameters
Extension Functions
// Functions can be added to existing classes, essentially static functions
// 1. Cut down on utility classes
// 2. Code easier to read
fun main() {
val text = "Replace text tt. of. this "
// Normal usage of function call
println(replaceWithRegex(text))
// Extension usage
println(text.replaceWithRegex())
}
fun replaceWithRegex(value:String) : String {
val regex = "\\s+"
regex.replace(value, "")
}
// Two changes:
// 1. Add class that needs extension, in this case, String prefix'd to the function name
// 2. Remove the method arg, replace with this
fun String.replaceWithRegex() : String {
val regex = "\\s+"
regex.replace(this, "")
}
Infix Function
data class Header(val name: String)
infix fun Header.plus(other: Header) : Header {
return this.name + other.name
}
h1, h2, h3: Header
h3 = h1 plus h2
// Also support operator overloading
operator infix Header.plus(other: Header) : Header {
.....
}
// Now, this can be called as
h3 = h1 + h2
Tail Recursive
// Famous Fibonacci Series
tailrec fun fib(n:Int, a:BigInteger, b:BigInteger) : BigInteger = {
if (n == 0) b else fib(n-1, a+b, a)
}
Interfaces
- Default Methods
- Properties
interface Time {
fun setTime(....)
// default method
fun time() = { ....
}
// Diamond interface problem
interface A { fun doSome() = { } }
interface B { fun doSome() = { } }
class Foo : A,B = {
override fun doSome() = {
super<A>.doSome()
}
}
Class Construction
open class Person(val name: String) {
val name: String
init {
this.name = name
}
}
open class Person(val name: String) {
val _name = name
}
open class Person(val name: String)
// Overloaded constructors
open class Person(val name: String) {
constructor class Person(name: String, age:Int) : this(name)
}
// Class extending base class
class Student(name: String) : Person(name)
class Student: Person {
constructor(name: String) : super(name)
}
class Student ; Person()
Data Classes
Immutable Classes
Provides, Equals, Hashcode, ToString
Kotlin provides copy methods
data class Meeting (val name: String, val location: String)
val aMeeting = Meeting("Yepp", "London")
val aaMeeting = aMeeting.copy(location = 'New York")
Object Classes
‘object’ classes are used the same as a class but they don’t have a constructor, essentially providing Singleton type instantiation
- Create Singletons
- Derive from classes
- Implement interfaces
- Can be created inside class
data class Course(val id:Int, val name: String)
object Courses {
var allCourses = arrayListOf<Course>()
}
class Grad(firstName: String, lastName: String, tutor: String) {
fun enrole(courseName: String) {
Courses.allCourses.add(Course(1, courseName))
val courses = Courses.allCourses.filter { it.id > 0 && it.name == courseName}.firstOrNull()
}
}
Companion Object
Essentially companion object provides static methods & interfaces for the enclosing classes, and annotate with @JvmStatic if main method wants to be called from outside, for instance
class Program {
companion object {
@JvmStatic
fun main(args: Array<String>) {
println("Hello")
}
}
}
High Level Functions
- Functions as first class
- Can pass to and from functions
- Can store in collections
fun main() {
val fibCalc = fib(11, object: Process {
override fun execute() {
println("Inside execute...")
}
})
}
interface Process {
fun execute()
}
fun fib(item:Int, action: Process) {
// fibonacci calculation
}
// Lambda style invoking the functions
fun main() {
// using lambda style
val fibCalc1 = fib(11) { s -> println(s) }
val fibCacl2 = fib (11) { println(it) }
val fibCalc3 = fib(11, ::println)
}
interface Process {
fun execute(item:String)
}
fun fib(item:Int, action: (String) -> Unit) {
// fibonacci calculation
}
Closures
Kotlin lambdas can mutate values
fun main() {
var total = 0;
// Note within closure, field can be mutated
// unless in Java, it has to be declared final.
fib(11, { it -> total+it })
}
interface Process {
fun execute(item:String)
}
fun fib(item:Int, action: (Int) -> Unit) {
// fibonacci calculation
}
with and apply functions
class Meeting(var meeting: String, var from : LocalDate, var addOns: List<String>) {
fun create(): Meeting {
return Meeting("hello", LocalDate.now(), arrayListOf())
}
}
fun main() {
var m:Meeting = Meeting("", LocalDate.now(), listOf())
with(m) {
meeting = "New Meeting"
from = LocalDate.now()
addOns = arrayListOf("Test")
}
m.apply {
meeting = "New Meeting"
from = LocalDate.now()
addOns = arrayListOf("Test")
}.create()
}
Kotlin Collections { Filter, Map, FlatMap}
Infinite Collections
- Sequences – same as streams in Java
- Lazily evaluated
- Prefer to normal collections
- Java 8 streams arent available on Andriod*
fun main() {
val meetings = listOf<Meeting>(
Meeting(1, "A"),
Meeting(2, "B")
)
meetings.asSequence()
.filter { it.name == "A" }
.map { it.id }
.forEach { println(it) }
}
Kotlin <-> Java InterOperable
Java Code
public interface Created {
public void onCreate(User user);
}
public class User {
private final String name;
public User(String name) {
this.name = name;
}
public void create(Created created) {
created.onCreate(this);
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
'}';
}
}
Kotlin -> Java Callback
fun main() {
val user = User("Raghu Ram")
user.create{ println ("user $it has been created")}
}
Nullability
- Supports nullable types
- Only explicit variables can be null
- By default, variables are non-null
To decalre null variable, declare
val m:Meeting?
// Need to use nullable checks on method invocation
// Safe call (?.)
m?.close()
Elvis Operator
val newMeeting = m?:Meeting()
Safe Cast
val saveable = o as? ISavable
Not-Null Assertions
// Asserts that m is not null, if null NPE at operator is used
m!!.close()
let assertion for nullable types
// m is nullable type, compiler will allow to call the underlying non-nullable method, onyl when
// m is not null
m?.let {
closeMeetingNonNull(m)
}
lateinit
class Meeting {
// Note it's not initialized and using lateinit to inform compiler it will be initilialized
lateinit var address: Address
fun init(addr: Address) {
address = addr
}
}
Java <-> Kotlin : @NotNull, @Nullable
Java Class
class Meeting {
private String title;
public void addTitle(@NotNull title) {
this.title = title
}
@Nullable
public String getTitle() {
return title;
}
}
Kotlin code:
fun main() {
val meeting: Meeting? = Meeting()
meeting.addTitle("Some value");
// Not allowed
meeting.addTitle(null)
// Can be null
println(meeting.getTitle())
}
Kotlin Collections
- Collections can be Nullable
- Hold Null Values
- Read-only or Mutable
- Interop with Java
- Arrays within Collections
Arrays can be created with
- arrayOf
- arrayOfNulls
- Array() function
- IntArray
- ByteArray
- CharArray
- etc
-- Provides same functions on Arrays same as Collections
High Order Functions
val action: () -> Unit = { println("Hello, World") }
val calc: (Int, Int) -> Int = { x: Int, y -> Int -> x * y }
inLine Functions
Used to substitute inline functions, add the command inline before the function name
Generics
// Generics Methods
fun <T> List<T>.itemAt(ndx: Int) : T {
return this[ndx]
}
// Generics Class
class Node<T>(private val item:T) {
fun value(): T {
return item
}
}
Kotlin can remember the reified information at runtime using this command
inline fun <reified T> foo(value: Any) = value is T
inline fun <reified T> List<*>.typeOf() : List<T> {
val returnList = mutableListOf<T>()
for (item in this) {
if (item is T) {
returnList.add(item)
}
}
}
Co and contra variance Generics
// Note, need to use 'out', so to inform comopiler that no changes will be done to the
class AllMeetings<out T:Meeting> (val meetings: List<Meeting>) {
val count: Int get() = meetings.count()
operator fun get(i: Int) = meetings[i]
}
fun attendMeetings(meetings: AllMeetings<Meeting>) {
....
}
fun main() {
var financialMeeting: FinanceMeeting = ...
attendAllMeeting(financialMeeting)
}