Kotlin: Difference between revisions
Line 887: | Line 887: | ||
} | } | ||
=Reactive Programming= | =Reactive Programming= | ||
==Setting up | ==Setting up RxJava2 in Android== | ||
We set up RxJava by add the following to the build.gradle | |||
<syntaxhighlight lang="gradle"> | |||
//ReactiveX | |||
implementation 'com.jakewharton.rxbinding2:rxbinding-kotlin:2.2.0' | |||
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' | |||
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0' | |||
</syntaxhighlight> | |||
==Example usage== | |||
This example throttle button presses and is here just to remind ourselves on what RxJava looks like in Kotlin | |||
<syntaxhighlight lang="kotlin"> | <syntaxhighlight lang="kotlin"> | ||
private fun sugar() { | |||
println("sugar was ere") | |||
RxView.clicks(binding.buttonClickMe).map { | |||
incrementCounter1() | |||
} | |||
.throttleFirst(1000, TimeUnit.MILLISECONDS) | |||
.subscribe { | |||
incrementCounter2() | |||
} | |||
} | |||
private fun incrementCounter1() { | |||
var newVal = binding.textviewCounter1.text.toString().toInt() | |||
newVal++ | |||
binding.textviewCounter1.text =newVal.toString() | |||
} | |||
private fun incrementCounter2() { | |||
var newVal = binding.textviewCounter2.text.toString().toInt() | |||
newVal++ | |||
binding.textviewCounter2.text =newVal.toString() | |||
} | |||
</syntaxhighlight> | |||
==Example manual== | |||
To remind ourselves on what goes on under the hood we have use observers. | |||
<syntaxhighlight lang="kotlin"> | |||
private fun noSugar() { | |||
val emmitter = PublishSubject.create<View>() | |||
binding.buttonClickMe.setOnClickListener(object : View.OnClickListener { | |||
override fun onClick(v: View) { | |||
emmitter.onNext(v) | |||
} | |||
}) | |||
val observer = object : Observer<View> { | |||
override fun onSubscribe(d: Disposable) { | |||
} | |||
override fun onNext(t: View) { | |||
incrementCounter2() | |||
} | |||
override fun onError(e: Throwable) { | |||
} | |||
override fun onComplete() { | |||
} | |||
} | |||
emmitter.map(object : Function<View, View> { | |||
override fun apply(t: View): View { | |||
incrementCounter1() | |||
return t | |||
} | |||
}) | |||
.throttleFirst(1000, TimeUnit.MILLISECONDS) | |||
.subscribe(observer) | |||
} | |||
</syntaxhighlight> | |||
Overriding the four methods makes the code unneccesarily large and RxJava2 provides Comsumer<T> to reduce this. | |||
<syntaxhighlight lang="kt"> | |||
... | |||
val consumer = Consumer<View> { | |||
incrementCounter2() | |||
} | |||
... | |||
emmitter.map(object : Function<View, View> { | |||
override fun apply(t: View): View { | |||
incrementCounter1() | |||
return t | |||
} | |||
}) | |||
.throttleFirst(1000, TimeUnit.MILLISECONDS) | |||
.subscribe(consumer) | |||
</syntaxhighlight> | </syntaxhighlight> |
Revision as of 01:29, 19 December 2020
Introduction
Kotlin is like java
- JVM language
- Object orientated
- Functional language, high order functions, we can
- store, pass and return functions
fun main(args: Array<String>) {
println("Hello World")
}
Types
The following data type are available.
- Numbers
- Characters
- Boolean
- Strings
- Arrays
- Collections
- Ranges
Numbers
The following Number type are available
- Byte Size 8, min -128, max 127
- Short Size 16, min -32768, max 32767
- int Size 32, min -2,147,483,648 max 2,147,483,647
- Long Size 64, min -9,223,372,036,854,775,808, max 9,223,372,036,854,775,807
val one = 1 // Int
val threeBillion = 3000000000 // Long
val oneLong = 1L // Long
val oneByte: Byte = 1
For Floating Point Numbers
- Float Size 32, Significant bit 24, Exponent Bits 8, Decimal Digits 6-7
- Double Size 64, Significant bit 53, Exponent Bits 11, Decimal Digits 15-16
val pi = 3.14 // Double
val e = 2.7182818284 // Double
val eFloat = 2.7182818284f // Float, actual value is 2.7182817
Characters
Kotlin represents character using char. Character should be declared in a single quote like ‘c’. Please enter the following code in our coding ground and see how Kotlin interprets the character variable. Character variable cannot be declared like number variables. Kotlin variable can be declared in two ways - one using “var” and another using “val”.
fun main(args: Array<String>) {
val letter: Char // defining a variable
letter = 'A' // Assigning a value to it
println("$letter")
}
Boolean
Two values true or false. (See Oracle for why two values was mentioned)
Strings
Strings are character arrays. Like Java, they are immutable in nature. We have two kinds of string available in Kotlin - one is called raw String and another is called escaped String. In the following example, we will make use of these strings.
fun main(args: Array<String>) {
var rawString :String = "I am Raw String!"
val escapedString : String = "I am escaped String!\n"
println("Hello!"+escapedString)
println("Hey!!"+rawString)
}
Arrays
Arrays in Kotlin are represented by the Array class, that has get and set functions (that turn into [] by operator overloading conventions), and size property, along with a few other useful member functions:
class Array<T> private constructor() {
val size: Int
operator fun get(index: Int): T
operator fun set(index: Int, value: T): Unit
operator fun iterator(): Iterator<T>
// ...
}
To create an array, we can use a library function arrayOf() and pass the item values to it, so that arrayOf(1, 2, 3) creates an array [1, 2, 3]. Alternatively, the arrayOfNulls() library function can be used to create an array of a given size filled with null elements.
fun main(args: Array<String>) {
val numbers: IntArray = intArrayOf(1, 2, 3, 4, 5)
println("Hey!! I am array Example"+numbers[2])
}
Ranges
Ranges is another unique characteristic of Kotlin. Like Haskell, it provides an operator that helps you iterate through a range. Internally, it is implemented using rangeTo() and its operator form is (..).
In the following example, we will see how Kotlin interprets this range operator.
fun main(args: Array<String>) {
val i:Int = 2
for (j in 1..4)
print(j) // prints "1234"
if (i in 1..10) { // equivalent of 1 < = i && i < = 10
println("we found your number --"+i)
}
}
// Output is 1234we found your number
Here is an example reading bytes
val myTest = 212
for(i in 0..7)
{
val myGetValue = IsByteSet(myTest,i)
val myTest2 = myGetValue
}
And a reading bits backwards
val myTest = 212
for(i in 7 downTo 0)
{
val myGetValue = IsByteSet(myTest,i)
val myTest2 = myGetValue
}
Collections
Filtering and Sorting
Kotlin provides filter, map and flat map
- filter Similar to a where in SQL
- map Similar to a select in SQL
- flatmap See below (flattens maps)
Filter
No any surprises here
val ints = listOf(1,2,3,4,5)
val smallInts = ints.filter{it < 4}
// 1,2,3
Map
No any surprises here either
val ints = listOf(1,2,3,4,5)
val smallInts = ints.map{it * it}
// 1,4,9,16, 25
Flatmap
This functions flattens a list of lists into a single list
val meetings = listOf(
Meeting(1, "Board"),
Meeting(2, "Committee"))
val people: List<Person> = meetings
.flatMap(Meeting::people)
// You could add distinct() to ensure distinction
Combining
val ints = listOf(1,2,3,4,5)
val smallInts = ints
.filter(it < 5)
.map{it * it}
// 1,4,9,16
Predicates
Example predicates available in kotlin can be used on collections too.
- all
- any
- count
- find (returns first elements with match predicate or null)
Here are some examples again no surprises.
val ints = listOf(1,2,3,4,5)
var largeInts = ints.any{it > 3}
var numberOfLargeInts = ints.count{it > 3}
We can declare predicates as variables too.
val greaterThanThree = {v:Int -> v > 3}
var numberOfLargeInts = ints.count{greaterThanThree}
Sequences
When we use sequences they are lazy evaluation. That means we can pass them around without great performance hits. I guess it is a bit like a yield keyword.
fun iter(seq: Sequence<String>) {
for(t in seq) println(t)
}
fun main(args:Array<String>) {
val titles: Sequence<String> = meetings
.asSequence()
.filter{...}
.map{...}
iter(titles)
}
Creating Collections
kotlin provides an extensive set of collections
- Arrays, maps, sets and lists
- Can be readonly
The two things to remember is that there are mutable and non mutable lists, nullable and not nullable lists. For iteration you can use the filterNotNull.
var people : MutableList<Person?>? = null
people = mutableListOf(Person(23), Person(24))
people.add(null)
// for(person: Person? in people) {
for(person: Person in people.filterNotNull()) {
println(person?.age)
}
Flow Control
If
if can be used as expression and statements.
if(q.Answer == q.CorrectAnswer) {
// Do something
}
else
{
// Do something else
}
// Or we can do
var message = if(q.Answer == q.CorrectAnswer) {
"You were correct"
} else {
"Try again"
}
When
When can be used like a switch statement.
when(number) {
0 -> println("Invalid number")
1, 2 -> println("Number too low")
3 -> println("Number correct")
4 -> println("Number too high, but acceptable")
else -> println("Number too high")
}
But it can also be used like an expression too.
var result = when(number) {
0 -> "Invalid number"
1, 2 -> "Number too low"
3 -> "Number correct"
4 -> "Number too high, but acceptable"
else -> "Number too high"
}
// it prints when returned "Number too low"
println("when returned \"$result\"")
Try
In kotlin you can use the try as an expression too.
val result:Int = try {
Integer.parseInt("ABC")
} catch(e:NumberFormationException) {
42
}
While
Same a java
while (x > 0) {
x--
}
Do
Same a java
do {
val y = retrieveData()
} while (y != null) // y is visible here!
For Loops
Basic
Kotlin does not support the for(var x=0; x<Total; x++). To do this in kotlin you need to use ranges
for(i in 1..10) {
println("iain is ace")
}
// Or
for(i in 1..10 step 2) {
println("iain is ace")
}
// Or
for(item in collection) {
println("iain is ace {item.attribute1}")
}
Lists
For lists we can deconstruct the list into an element and index
var numbers = listOf(1,2,3,4,5)
// blahhh blahhh
for((index, element) in numbers.withIndex()) {
print("$element at $index")
}
Maps
For maps we can deconstruct the map into a key and value
var ages = TreeMap<String, Int>()
// blahhh blahhh
for((name, age) in ages) {
print("$name is $age")
}
Functions
General
- Kotlin Functions can be expressions
- Functions can be called by Java
- Can have default parameters
- Can have named parameters
- Can extend existing types
- Infix functions
- Tailrec functions
- High-level functions
Here we go derrrrr
fun test(inString : String) : Boolean {
println("No alarms and no surprise $inString")
return true
}
Function Expressions
We can attach an expression to a function name.
package rsk
fun main(args: Array<String>) {
println(max(1,2))
}
fun max(a: Int, b: Int): Int = if(a >b) a else b
Calling From Java
To call from java we just need to add the package name to call the function
fun main(args: Array<String>) {
println(rsk.max(1,2))
}
We can override this name in the kotlin package with
@file:JvmName("DisplayFunctions")
Default Parameters
fun test(param1: String = "Test") {
println("No alarms and no surprise $param1")
}
Named Parameters
Nothing special here
fun test(param1: String = "Test", param2: String = "Test") {
println("No alarms and no surprise $param1")
}
test (param2 = "Test", param1 = "Test2")
Extension Functions
This is like the approach for C#. This results in a static function but cuts down the use of utility code. These are static functions but reduce the number of utility functions. So in the example below we extend the String class
fun String.myOwnFunction(param1: String, param2: String): String {
// this represents the receiver
println("Iain is ace $param1, $param1, this")
}_
var myString = "This is a test"
myString.myOwnFunction("Ten", "Two")
println
Infix Functions
This allows better looking code to achieve the same thing. To concatenate two fields we might do the following.
class Header(var Name: String {}
fun Header.plus(other: Header): Header {
return Header(this.Name + other.Name)
}
var myH1 = new Header("H1")
var myH2 = new Header("H2")
val myH3 = myH1.plus(myH2)
By changing the prefix to have infix we can now write
...
infix fun Header.plus(other: Header): Header {
return Header(this.Name + other.Name)
}
...
val myH3 = myH1 plus myH2
Tailrec Functions
I really liked this example. It demonstrates the fib series inside a recursive function. When looking for the 100,000 number a stack overflow occurs so you can see the point of marking recursive functions, where appropriate tailrec which turns it into a for loop under the covers.
fun main(args: Array<String>) {
println(fib(100000, BigInteger("1"), BigInteger("0")))
}
tailrec fun fib(n: Int, a: BigInteger, b: BigInteger): BigInteger {
return if (n == 0) b else fib(n-1, a + b, a)
}
High-Order Functions
Declarations and Calling
To declare functions we put the parameters on the right and the return value on the left.
val action: () -> Unit = { println("Hello, World") }
val calc: (Int, Int) -> Int = { x,y -> x*y}
fun doSomething(func: () -> Unit) {
func()
}
fun main(...) {
doSomething(action)
}
Inlining Functions
So we have
fun main(..) {
val ints = listOf(1,2,3,4,5)
val i = first(int, {i -> i == 3}
println(i)
}
inline fun <T> first(items: List<T>, predicate: (T) -> Boolean) : T {
for(item in items) {
if(predicate(item)) return item
}
throw Exception()
}
Let's decompile to see the java code using fernflower.jar
java -jar fernflower.jar out/blah/blah/blah.class .
Looking at the class generated we see inline has created code inline with the main method. Very like c++ line.
Old Approach
To print the fibonacci sequence we could do
fun main(args: Array<String>) {
var program = Program()
program.fibonacci(8)
}
class Program {
fun fibonacci(limit: Int) {
var prev = 0
var prevprev = 0
var current = 1
// 1,1,2,3,5,8,13
for(i: Int in 1..limit) {
println(current)
var temp = current
prevprev = prev
prev = temp
current = prev + prevprev
}
}
}
Separate Strategy
What we want to do with the result can be separated from the calculation itself by passing in the approach using anonymous functions.
fun main(args: Array<String>) {
// var program = Program()
program.fibonacci(8, object : Process {
override fun execute(value: Int) {
println(value)
}
})
}
interface Process {
fun execute(value: Int)
}
class Program {
fun fibonacci(limit: Int, action: Process) {
var prev = 0
var prevprev = 0
var current = 1
// 1,1,2,3,5,8,13
for(i: Int in 1..limit) {
// println(current)
action.execute(current);
var temp = current
prevprev = prev
prev = temp
current = prev + prevprev
}
}
}
Quick Break Lambda
Lambda are similar to the c# lambdas. Functions and be declared and called as below.
fun calculateResult(a: Int, b: Int, func: (Int) -> Unit) {
// a and b = some result
...
func(result)
}
// This can be called either by
// My favourite
calculateResult(1,2, { s -> print(s) } )
calculateResult(1,2, { print(it) } )
// Their favourite
calculateResult(1,2) { s -> print(s) }
calculateResult(1,2) { print(it) }
// The super short and should be my favourite
calculateResult(1,2, ::print)
Implementiing Lambda
So we can now use lambda with our code.
fun main(args: Array<String>) {
program.fibonacci(8, {n ->println(n)})
// Or as "it" is the variable name
program.fibonacci(8) { println(it) }
// Or
program.fibonacci(8, ::println)
}
class Program {
fun fibonacci(limit: Int, action: (Int) -> Unit) {
var prev = 0
var prevprev = 0
var current = 1
// 1,1,2,3,5,8,13
for(i: Int in 1..limit) {
// println(current)
action(current);
var temp = current
prevprev = prev
prev = temp
current = prev + prevprev
}
}
}
Closures
Closures are functions that can access and modify properties defined outside the scope of the function. To do this in java is quite difficult but this is easy in kotlin and demonstrating the point of separating concerns. We could total up the values in a fib sequence by doing
fun main(args: Array<String>) {
var total = 0;
program.fibonacci(8) {it -> total += it) }
println(total)
}
With and Apply
These are extension methods and allow the initialisation of objects. e.g.
val m = SomeClass()
with(m) {
SomeProp1 = "prop1"
SomeProp2 = "prop2"
SomeProp3 = "prop3"
}
// Apply returns an object to allow chaining
m.apply {
SomeProp1 = "prop1"
SomeProp2 = "prop2"
SomeProp3 = "prop3"
}.nextFunctionCall()
Classes
General
Defining classes is much like c++ and java
class Person() {
var Name: String = ""
fun display() {
println("Display :$Name"
}
}
They have the following charactistics
- final by default.
- abstract classes
- Sealed classes
- Constructors
- Data Classes
- Lambda Functions
Final by Default
The classes cannot be derived from by default. There functions final by default too. To override you need to specify the open keyword.
open class Person() {
var Name: String = ""
open fun display() {
println("Display :$Name"
}
}
Abstract Classes
This forces the user, as it does in java, c# and c++ to derive a class before it can be instantiated. Any functions which are abstract must also be implemented.
Sealed classes
Sealed classes allow you restrict class hierarchies.
sealed class Event {
class SendEvent(id: Int, to: String) : Event()
class ReceiveEvent(id: Int, to: String) : Event()
}
// This can subsequently be used with a when
fun handEvent(e: Event) =
when(e) {
is SendEvent -> print(e.to)
is ReceiveEvent -> print(e.from)
}
Constuctors
General
If you pass a var to a constructor the name is then associated with the class without declaring the value separately. You can also use val for readonly property.
class Person(var Name: String, val IAmReadonlyLastName: String) {
fun display() {
println("Display :$Name $IAmReadonlyLastName"
}
}
Other Initialization
We have initialise class properties also with the init function
class Person() {
val name: String
fun init() {
this.name = name
this.last_name = last_name
}
}
Or initialize using the incoming
class Person(_name: String) {
val name = _name
}
Secondary Constructors can be used.
class Person(_name: String) {
val name = _name
val age = _age
constructor(_name: String, _age: Int) {
name = _name;
age = _age;
}
}
Data Classes
Using dataclass you get a hashcode method, a equals() method, a toString() method and a copy method. When a data class the objects are compared by value.
fun main(args: Array<String>) {
var obj1 = Test(1,"Test")
var obj2 = Test(1,"Test")
if(obj1 == obj2) // Fails if not a data class
}
data class Test(val prop1: Int, val prop2: String) {}
Lambda Functions
Kotlin supports lambda functions
class Person(var Name: String) {
fun display() {
println("Display :$Name"
}
fun displayWithLambda(func: (s:String) -> Unit) {
func(Name)
}
}
Generics
Constraining Types
When defining generics we can constrain the types they can use
class Node<T : Number> (private val item:T) {
fun value() :T {
return item
}
}
Generics At Runtime
- Java erases all generic type information
- Cannot check generic type at runtime
Using the keyword reified on line functions can allow you to know the types.
Intefaces
Like java, kotlin has interfaces
General
You can have default arguments and functions. The default access if public by default.
interface Time {
fun setTime(hours: Int, mins: Int = 0, secs: Int = 0)
fun setTime(time: IainTime) = setTime(time.hours)
}
class MyTime : Time {
override fun setTime(hours: Int, mins: Int = 0, secs: Int = 0) {
}
}
Overriding
To override which interface to use if there are two, use the super keyword. This looks like something most unlikely but hey.
interface A { fun doIt() = {} }
interface B { fun doIt() = {} }
class foo : A, B {
override fun doIt() = {
super<A>.doIt()
}
}
Companion Objects
Singletons
With kotlin we can create object classes for singletons. E.g.
object Meetings {
var allMeetings = arrayListOf<Meeting>()
}
Meetings.allMeetings.add(Meeting(...))
These are difficult to test so should be avoided where possible.
Companion Objects for Classes
Kotlin does not have static methods. To achieve the same as these you used companion objects. For example maybe add two factory methods for a class
class Student {
// Interface is optional
companion object : SomeInterface {
override fun SomeInterface(item: Student) {
}
fun createType1Student(name: String) : Type1 {}
fun createType2Student(name: String) : Type2 {}
}
}
val myObject = Student.createType1Student("Fred");
Exceptions
General
Kotlin, unlike old java does not require exceptions but you can use the standard approach.
var reader = FileReader("Filename.txt")
try {
reader.read()
} catch(e :IOException) {
} finally {
}
Other
Nullable Stuff
Let
Let allows you to process non null checks.
m?.Let {
callFunctionWithNonNull)
}
Lateinit
Late init allows you to state the variable is lateinit. This let you override the compiler and initialize later in the code. i.e. you can have a non null declared but not initialized
Java Nulls
In java we can add @Nullable to ensure that nulls are not passed between the two.
public void addTitle(@NotNull String title) {
this.title = title;
}
public @Nullable String meetingTitle() {
return title;
}
Reading and Writing to Gson
Given the following
var mySportsActivity = SportsActivity( 0.0, 0.0, 0, 0.0, Date())
val gson = Gson() val json = gson.toJson(mySportsActivity)
var filename = "D:\\IAIN\\Output.json";
You can write to a file with
FileWriter(filename).use { writer -> val gson = GsonBuilder().setPrettyPrinting().create() gson.toJson(mySportsActivity, writer) }
And read it back with
FileReader("D:\\IAIN\\Output.json").use { reader -> val gson = GsonBuilder().setPrettyPrinting().create() mySportsActivity2 = gson.fromJson(reader,SportsActivity::class.java) }
Reactive Programming
Setting up RxJava2 in Android
We set up RxJava by add the following to the build.gradle
//ReactiveX
implementation 'com.jakewharton.rxbinding2:rxbinding-kotlin:2.2.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
Example usage
This example throttle button presses and is here just to remind ourselves on what RxJava looks like in Kotlin
private fun sugar() {
println("sugar was ere")
RxView.clicks(binding.buttonClickMe).map {
incrementCounter1()
}
.throttleFirst(1000, TimeUnit.MILLISECONDS)
.subscribe {
incrementCounter2()
}
}
private fun incrementCounter1() {
var newVal = binding.textviewCounter1.text.toString().toInt()
newVal++
binding.textviewCounter1.text =newVal.toString()
}
private fun incrementCounter2() {
var newVal = binding.textviewCounter2.text.toString().toInt()
newVal++
binding.textviewCounter2.text =newVal.toString()
}
Example manual
To remind ourselves on what goes on under the hood we have use observers.
private fun noSugar() {
val emmitter = PublishSubject.create<View>()
binding.buttonClickMe.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View) {
emmitter.onNext(v)
}
})
val observer = object : Observer<View> {
override fun onSubscribe(d: Disposable) {
}
override fun onNext(t: View) {
incrementCounter2()
}
override fun onError(e: Throwable) {
}
override fun onComplete() {
}
}
emmitter.map(object : Function<View, View> {
override fun apply(t: View): View {
incrementCounter1()
return t
}
})
.throttleFirst(1000, TimeUnit.MILLISECONDS)
.subscribe(observer)
}
Overriding the four methods makes the code unneccesarily large and RxJava2 provides Comsumer<T> to reduce this.
...
val consumer = Consumer<View> {
incrementCounter2()
}
...
emmitter.map(object : Function<View, View> {
override fun apply(t: View): View {
incrementCounter1()
return t
}
})
.throttleFirst(1000, TimeUnit.MILLISECONDS)
.subscribe(consumer)