web analytics

สรุป Kotlin introduction จากงาน Thailand Developer Konference #1

cover

บังเอิญผมได้ไปเจอ และเริ่มรู้จัก Kotlin เข้า เลยเกิดความสนใจ แต่ว่ายังไม่มีโอกาสลองจริงๆ และตอนนั้นก้ได้แค่หาข้อมูล แต่เมื่อวันก่อน ที่กรุงเทพมีการจัดงาน Thailand Developer Konference ที่เป็น  Kotlin meeting กัน แต่ทว่าผมอยู่ต่างจังหวัด เลยไม่ได้ไป เสียดายมาก (แทบทุกงานงานแหละ) เลยชมย้อนหลังผ่านทาง youtube แทน

 

เริ่มต้น

เนื้อหาในบทความนี้ ผมจะถอดคำพูด และตัวหนังสือจากสไลด์ในงาน Thailand Developer Konference ที่คุณ Kittinun Vantasin เป็นผู้บรรยาย มาเป็นคำเรียบเรียงตามความเข้าใจของผมเอง

ผมเองก็ยังเขียน Kotlin ไม่เป็น เรียกว่า แทบจะศูนย์เลย ส่วน Java ก็ได้แค่ระดับกลางๆ แต่ไม่ได้หมายความว่า จะเริ่ม Kotlin ไม่ได้นี่นา บทความนี้จึงผมเขียนขึ้นเพื่อ ผมจะได้เข้าใจ อย่างน้อยก็ซึมซับบ้าง และคนที่อ่านจะได้เข้าใจ Kotlin และอาจนำ Kotlin ไปเป็นเป็นตัวเลือก ในการใช้งานต่อไป

หากมีผิดพลาดโปรดชี้แนะด้วยนะครับ จะเป็นพระคุณอย่างมาก

 

สามารถชมได้โดยตรง ที่ youtube ด้านล่างนี้ก็ได้เช่นกัน

Thailand Developer Konference #1 – Introduction to Kotlin for Android Development

 

Slide : Introduction to Kotlin for Android Development (Kittinun Vantasin)
http://slides.com/kittinunvantasin/introduction-to-kotlin-for-android-development#

 

Facebook group : Kotlin Thailand community
https://www.facebook.com/groups/872547279487598/

 

Kotlin reference
https://kotlinlang.org/docs/reference/kotlin-doc.html

 

Kotlin คืออะไร

Kotlin เป็นภาษา Programming ภาษาหนึ่ง ที่เป็นแบบ Statically typed language ก็คือ จะมีการระบุ type เสมอ และชัดเจน ซึ่งเป็นแบบเดียวกับ Java แต่จะปรับปรุง เน้นความง่าย และ safe เดี๋ยวมีอธิบายเพิ่มเติมอีกที

ผู้สร้าง Kotlin คือทีม Jetbrain ซึ่งเป็นผู้พัฒนา IntelliJ IDE แล้ว IntelliJ เป็น base ให้ Android studio อีกที

Kotlin ชื่อนี้ เอามาจากชื่อเกาะ ในประเทศ รัสเซีย (ถ้าทีม Jetbrain ชอบภูเก็ต แล้วตั้งชื่อว่า Phuket เราจะงงขนาดไหน)

1

 

ทำไมต้อง Kotlin

การจะเรียนรู้ภาษาใหม่ จะมีต้นทุน และอาจต้องใช้ระยะเวลาหนึ่งเพื่อสร้างความเข้าใจ คุณ Kittinun Vantasin ให้เหตุผลว่า

  1. Java พัฒนาค่อนข้างช้า ถ้ายังติดอยู่กับ Java ก็จะไม่ได้ใช้ความสามารถใหม่ๆที่ควรจะมี
  2. ใน Java เราจะมีการตรวจสอบค่า null เต็มไปหมด แต่ใน Kotlin จะ safe เรื่องนี้
  3. Kotlin ไม่ได้แตกต่างจาก Java มากนัก มี Learning curve ต่ำ ถ้าเคยเรียนรู้ Java อยู่แล้ว จะไม่ต้องปรับตัวมาก

 

ตัวอย่าง code ใน Java ที่ compile error ข้างล่างนี้ เพราะ Java ไม่ได้ออกแบบมาให้ Cast system type ซึ่งในความเป็นจริงควรจะ work

       object instanceof T; 
       T t = (T) object;
       T[] array = new T[1];

 

Kotlin เน้นไปที่การเขียนง่าย เขียน code ได้สั้น สามารถรันบนทุกที่ที่ Java รันได้ ที่เป็นอย่างนี้ เพราะว่า Kotlin มีการ generate Byte code เป็น Java6 ดังนั้น Kotlin จึงไปรันในทุกๆที่ที่ Java6 รันได้นั่นเอง

Simple, expressive, versatile, interoperable

 

ยกตัวอย่าง Kotlin code ที่สั้นกระชับ
args เป็นตัวแปร Array String สามารถเรียก method getOrNull สามารถใส่ index ได้ ถ้าเป็ร null จะได้ ค่า “No name”
แต่ถ้าไม่ใช่ null ก็จะเป็นค่านั้นๆ จะเห็นว่ามีความสั้นกระชับ หากเป็น Java จะต้องมี if เช็ค null เป็นต้น

      fun main(args: Array<String>) {
            val name = args.getOrNull(0) ?: "No name"
            println("Hello $name to Kotlin Konference!")
      }

 

12 อย่างของ Kotlin ที่แตกต่างและดีกว่า

2

 

Null Safety

การที่เราต้องมา handle เกี่ยวกับ null จะทำให้ code ของเราค่อนข้างเละ ไม่สวย ดังนั้น Kotlin เลยจัดการตรงจุดนี้ ที่ Type ซะเลย

Java จะประกาศตัวแปร instance ต่างๆ ก็เป็น nullable ทั้งหมด จึงเกิดการ handle null ตามมา
Kotlin หากต้องการให้มีค่า null ได้ ก็ต้องเติม ? ไว้หลัง Type ด้วย แต่ถ้าไม่เติม ? แล้วไป assign ค่า null ให้จะ compile ไม่ผ่าน ทำให้ code safe มากขึ้น การ handle null น้อยลง

3

 

ตัวอย่าง Kotlin code
val name: String = null จะ compile ไม่ผ่าน เพราะ ไม่ได้ใส่ ? ทำให้มันเป็น null ไม่ได้
val nullableName : String? = null อันนี้ถูกต้อง
val nonNullName = “Kittinun” การกำหนดค่าแบบนี้ จะรู้ทันทีว่า เป็น type String ที่ห้ามเป็น null

 //cannot compile
 val name: String = null
 
 //compile
 val nullableName: String? = null

 //compile
 val nonNullName = "Kittinun" // type is String

 fun doSomething(name: String) {
     ...
 }
 
 //cannot compile
 doSomething(null)

 val anotherNullableString: String? = "nullable"

 //compile
 doSomething(nonNullName)

 

Safe call คือ มี ? หลัง object แล้วเรียก method ถ้า object เป็น null ก็จะผ่านไป ไม่ error
แต่ถ้า ใส่ !! หลัง object แล้ว object เป็น null เรียก method จะมี NullPointerException เกิดขึ้น

 

 val nullableString: String? = null

 //safe call
 nullableString?.length

 //NPE lovers
 nullableString!!.length //throw NPE

 

method let คือ หากเรียก let กรณีที่ object ไม่เป็น null กรอบภายในจะถูกทำงาน ดังนั้นตัวแปร object ใน กรอบไม่จำเป็นต้องใส่ ? เพราะ มันไม่มีทางเป็น null

ในขณะที่การเขียน if != null ก็ยังเขียนได้เช่นเดิม

 //unwrapping
 val anotherNullableString: String? = "Kittinunf"
 anotherNullableString?.let {
     //safe as nullable won't enter this block
     val length = anotherNullableString.length
     println(length) //print 9
 }

 if (anotherNullableString != null) {
     //also safe
     println(anotherNullableString.length) //print 9
 }

 

Enum Class

Enum คือ รูปแบบการเก็บข้อมูลที่แน่นอน อย่างเช่น Orientation ก็มีแค่ VERTICAL , HORIZONTAL หรือ เดือนก็มีแค่ 12 เดือนเรียงกันไป ใน Java ก็สามารถใช้ enum ได้

Enum ใน Kotlin จะเป็นรูปแบบ class จริงๆ มากกว่า Java สามารถใส่ค่า และฟังชันก์ให้มันได้

 enum class Orientation(val value: String) {
    HORIZONTAL("H") {
        override fun intRepresentation() = 1
    },

    VERTICAL("V") {
        override fun intRepresentation() = 2
    },
    
    REVERSE_HORIZONTAL("R_H") {
        override fun intRepresentation() = -1
    };
    
    abstract fun intRepresentation(): Int
 }
 val h = Orientation.HORIZONTAL
 val rh = Orientation.REVERSE_HORIZONTAL

 println(h.value) //print "H"
 println(rh.intRepresentation()) //print -1

 

Immutability

คือ การกำหนดความสามารถในการเปลี่ยนแปลงค่า หรือเรียกว่า เป็นค่าคงค่า หรือไม่
Immutable = ไม่สามารถเปลี่ยนแปลงได้
mutable = เปลี่ยนแปลงได้

โดยจะกำหนดที่ type ดังนี้
val คือ การบอกว่า object นี้เป็นแบบ immutable ดังนั้น จะเสมือนเป็น object แบบค่าคงที่นั่นเอง
var คือ การบอกว่า object นี้เป็นแบบ mutable

 

    val i: Int = 10
    var mi: Int = 5
    
    //cannot compile, i cannot be re-assigned
    i = 1000 
    mi = 1000
    
    //cannot compile, i is immutable and cannot be re-assigned
    i += 4

 

เช่น List ประกาศเป็น immutable นั่นหมายความว่า เราจะ add , remove สิ่งของใน List ไม่ได้
หากต้องการทำเป็น List แบบ mutable ก็ต้องใช้ MutableList แทน จึงสามารถ add สิ่งของได้

    val arr: List<Int> = listOf(1,2,3,4,5)
    
    //cannot compile, add is unresolved reference
    arr.add(8)

    val marr: MutableList<Int> = mutableListOf(1,2,3,4,5)
    marr.add(8) //becomes 1,2,3,4,5,8

การทำแบบนี้ จะทำให้ code safe มากขึ้น

 

Type Infer & Cast

ภาษาสมัยใหม่ เราไม่จำเป็นต้องเขียน Type อีกต่อไป
เช่น val j = 10 ตัว compiler จะเห็นว่าเป็น int หากในกรณีที่อยากให้มันเป็น non-nullable ก็ใช้ val j :Int? = 10

fun powerOfTwo(i: Int) = i * i
คือ การเขียนฟังชันก์ในบรรทัดเดียว จะ return เป็นค่าของ i*i ซึ่งก็คือ int
ตัวแปรที่มารับก็จะมีการ Cast ให้

ซึ่งมันคล้ายๆกับ javascript นั่นเอง

    val j = 10
    //val j: Int = 10

    //be explicit as needed
    val jj: Int? = 20
    
    val i = powerOfTwo(6)

    fun powerOfTwo(i: Int) = i * i

    jj?.let {
        //smart cast to Int
        println(jj.toString())
    }
    
    //type is Int?
    jj?.toString()

 

ถ้าเราเช็ค if ว่า เป็น object นั้นหรือไม่ ภายในกรอบเราสามารถใช้ method ของ object ได้เลย ไม่ต้อง Cast เอง
และเราสามารถใช้ Any ซึ่งคือ type ที่เป็นอะไรก็ได้

    val e: Any = "Hello"
    if (e is String) {
        //smart cast to String
        println(e.length) //print 6
    }

 

Properties & Fields

Class ของ Kotlin ไม่จำเป็นต้องเขียน method set get สามารถกำหนด var , val ได้ที่ constructor เลย

    enum class Color {
        BLUE,
        RED,
        GREEN,
        WHITE;
    }

    class Car(
        val make: String,
        val year: Int,
        var color: Color,
        val vinNumber: String,
        var secondHanded: Boolean
    )

    val jazz = Car("Honda", 2014, Color.WHITE, "aa76efc1", false)
    
    //cannot compile as make is immutable
    jazz.make = "Toyota" 
    jazz.bought()

    jazz.secondHanded = true //compile

 

กรณีที่เขียน function เอง เช่น Class Controller
เรียก method computed ตัว get() จะทำงาน

lateinit คือ การประกาศตัวแปรที่เรารู้ว่ามันต้องมีค่าแน่ๆ แต่ตอนนี้ยังไม่มีค่าให้

หากเราประกาศ แบบ lateinit แล้วไม่ได้กำหนดค่า แต่ดันไปเรียก method มันก็จะ throw exception uninitialized

   class Controller {
    
        val computed: String 
            get() { return "hello" }
        
        var counter = 0 
            set(value) {
                if (value >= 0) field = value
            }
        
        lateinit var toBeInit: Foo
    
    }
    
    val c = Controller()
    c.computed //return "hello"
    
    c.value = 10 //value = 10
    c.value = -1 //value is not set, value is still 10

    c.toBeInit //throw exception uninitialized
    
    c.toBeInit = Foo()
 
    c.toBeInit.doSomethingAwesome()

 

ใน Kotlin มีคอนเซปชื่อว่า Delegate คือ การให้ property ของ class มีพฤติกรรมอะไรบางอย่าง

เช่น การใช้  lazy คือ เรียกใช้ property  ครั้งแรก ตัวคำสั่งในกรอบจะทำงาน
Delegates.observable คือ ทุกครั้งที่มีการเปลี่ยนค่า กรอบคำสั่งจะทำงาน

Delegates.observable(“not set”) จะเป็นการกำหนดค่าเริ่มต้นว่าคือ “not set” หากมีการเปลี่ยนแปลงค่า กรอบคำสั่งใน observable จะถูกเรียกให้ทำงาน

 

    class Foo {
    
        val lazyValue by lazy {
            println("lazy will be created once")
            Bar()
        }
    
        var observedValue by Delegates.observable("not set") { 
            meta, oldValue, newValue -> 
            println("From \"$oldValue\" To \"$newValue\"")
        }
    
    }

    val f = Foo()
    f.observedValue = "hello" //print - From "not set" To "hello"
    f.observedValue = "world" //print - From "hello" To "world"

 

Data class

ปกติแล้วเรา สร้าง class แล้วพอเราเอา class มา print มันก็จะออกมาเป็น address ซึ่งเป็นสิ่งที่เราไม่ต้องการ

    class Device(
        val type = "Unknown"
        val platform = "Android"
    )

    println(Device()) //print Device@63adf08f

 

วิธีการใน Kotlin คือ ใส่ data ไปหน้า class พอเราเอา class มา print ก็จะได้ String ข้อมูล

data class DeviceData(
        val type = "Unknown"
        val platform = "Android"
    )
    
    val d1 = DeviceData()
    val d2 = DeviceData()
    println(d1) //print DeviceData(type=Unknown, platform=Android)

    println(DeviceData("Samsung")) //print DeviceData(type=Samsung, platform=Android)


    

 

สามารถนำ object มาเปรียบเทียบกันได้ โดยจะเทียบจากข้อมูล

    println(d1 == d2) //print true!

 

Destsructuring คือการแตก object เป็นตัวแปร ย่อยๆ ก็สามารถทำได้

    val (type, platform) = d1 //type="Unknown", platform="Android"

 

 

Lambda

คือการเขียนแบบรวบรัด เช่น powOfTwo จะมีค่าเป็นกำลังสอง ของค่าที่รับมา

    val powOfTwo = { i: Int -> i * i }
    
    println(powOfTwo(5)) //print 25

 

อีกตัวอย่างคือ  กำหนด Delegates.observable หากมีการเปลี่ยนแปลงค่า กรอบคำสั่งจะถูกทำงาน ซึ่งคือการเรียก updateListener แล้วส่งค่าให้มัน

โดยเริ่มต้นครั้งแรก updateListener จะมีค่าเป็น null
(Unit ใน Kotlin คือ void)

การกำหนด f.updateListener = ::println คือ การให้ updateListener ทำงานเหมือนฟังชันก์ println เป็นฟังชันพื้นฐานของ Kotlin

ดังนั้น หากมีการเปลี่ยนแปลงค่า จะมีการ print ค่าออกมา

    class Foo {
    
        var value: String by Delegates.observable("") { 
            meta, oldValue, newValue -> updateListener?.invoke(newValue)
        }
        
        var updateListener: ((String) -> Unit)? = null
    
    }

    val f = Foo()
    
    f.value = "v1"
    f.updateListener = ::println
    f.value = "v2"  //print "v2"
    f.value = "v3"  //print "v3"

 

Extension Functions

คือ การสร้างฟังชันก์ แล้วเพิ่มเข้าไปใน type นั้นๆได้

เช่น ใส่ฟังชันก์ fib() ให้ Int แล้ว ก็เรียกเหมือน method ให้ทำงานบางอย่าง

    fun Int.fib(): Int {
        var last = 0
        var prev = 1
    	
        var result = 0
        for (i in 2..this) {
            result = last + prev
            last = prev
            prev = result
        }
        
        return if (this == 1) 1 else result
    }

    31.fib() //print 1346269

 

High Order Functions

เรียกคำสั่งตามลำดับ โดยใช้ lambda

.reduce คือ ให้แต่ละตัวทำ operation อะไร การกำหนด เป็น Int::plus คือให้แต่ละตัวบวกกัน

    (0..10).filter { it > 5 } // print 6,7,8,9,10
    
    //print 36,49,64,81,100
    (0..10).filter { it > 5 }.map { it * it } 
    
    //print sum(36,49,64,81,100) 330
    (0..10).filter { it > 5 }.map { it * it }.reduce(Int::plus)

 

Type Safe Builder

มีคลาสอันหนึ่งชื่อว่า Complicate โดยเรากำหนดว่า คลาสนี้จะต้องทำอะไรบางอย่าง ก่อนที่เราจะนำไปใช้ เช่น จะ add Bar() เข้าไป 3 ตัวก่อนจะนำไปใช้ ภายใน Complicate มี companion object ก็คือเหมือน static ใน Java มีฟังชันก์ชื่อ build โดย parameter ที่ส่งเข้ามาเป็นฟังชันก์ ซึ่งเรายังไม่รู้ว่ามันคือฟังชันก์อะไร แต่ว่าเป็นฟังชันก์ใน Complicate จึงเขียนได้ว่า Complicate.() ในกรณีนี้ ส่ง add(Bar()) เข้ามานั่นเอง

    data class Bar(val i: Int)

    class Complicate {
        private val children = mutableListOf<Bar>()
        
        companion object {
            fun build(init: Complicate.() -> Unit): F {
                val c = Complicate()
                c.init()
                return c
            }
        }
        
        fun add(b: Bar) {
            children.add(b)
        }
    }

    val c = Complicate.build {
        add(Bar(1))
        add(Bar(2))
        add(Bar(3))
    } //c.children [Bar(1), Bar(2), Bar(3)]

 

Collection API

Kotlin มี collection อยู่เยอะมาก

เช่น .any คือการ ถามว่าใน object มีสัก element ที่มีการหาร 2 ลงตัว แล้วจะคืนค่าเป็น boolean

 a.any { it % 2 == 0 }

 

คือการ สร้าง HashMap ที่เอาข้อมูลจาก a มาสร้าง เช่น หาก a คือ 1-10 จาก code ด้านล่าง ก็จะได้ Map ที่มี key even แล้วมีค่าที่หารสองลงตัว ส่วน key odd ก็เก็บค่าที่หารสองไม่ลงตัว

a.toMap({ if (it % 2 == 0) "even" else "odd" }, { it.toString })

 

ตัวอย่างอื่นๆ

    val a = (1..10) + (1..10)
    
    a.all { it > 0 }                            a.subtract(5..10)
    
    a.any { it % 2 == 0 }                       a.sortedWith { .... } 

    a.average()                                 a.sum()

    a.distinct()                                a.take(3)

    a.dropWhile { it < 5 }                      a.toList()/a.toArrayList()

    a.filterNot { it > 0 }                      a.toSet()/a.toHashSet()

    a.groupBy { if (it < 3) "<3" else ">3" }    a.toCollection(MyAwesomeCollection())
    
    a.intersect(2..4)                           a.toLinkList()

    a.joinToString("-")                         a.toMap({ if (it % 2 == 0) "even" else "odd" }, 
                                                        { it.toString })

    a.max(), a.min()                            a.map { it to it.toString() }.unzip()

    a.none { it < 1 }                           a.withIndex()

    a.reversed()                                a.union(10..20)

    a.sortedBy { it % 3 == 0 }                  a.zip(10..20)

 

ลองเล่น Kotlin

http://try.kotlinlang.org/#/Examples/Hello,%20world!/Simplest%20version/Simplest%20version.kt

 

การใช้ Kotlin

Kotlin มีรองรับในหลายๆ ด้าน

Backend  – Ktor
https://github.com/Kotlin/ktor

Desktop  – TornadoFx
https://github.com/edvin/tornadofx

Android –  Android studio and Kotlin plugin
https://plugins.jetbrains.com/plugin/6954?pr=androidstudio

 

จบแล้ว

Kotlin ถือว่าเป็นภาษาทางเลือกที่ดีอันนึง มีความยากระดับนึงเลย แอบมึนอยู่นะ แต่คุณ Kittinun Vantasin ก็บอกว่า ลองแล้วจะติดใจ จะลืมการเขียน Java ไปเลยล่ะ พอได้เขียน ได้อ่านสไลดีๆแล้ว เข้าใจ kotlin พอสมควร ผมคงต้องลองเล่นดูสักพัก โดยลองไปใช้กับ Android แบบขำๆก่อน บทความหน้าจะมาต่อ Session ของ Kotin for Android Development ครับ หวังว่าบทความนี้จะมีประโยชน์ครับ

ขอขอบคุณ คุณ Kittinun Vantasin ที่แบ่งปันสิ่งดีๆ มา ณ ที่นี้ด้วยครับ

(: