สรุป Kotlin introduction จากงาน Thailand Developer Konference #1
บังเอิญผมได้ไปเจอ และเริ่มรู้จัก 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 เราจะงงขนาดไหน)
ทำไมต้อง Kotlin
การจะเรียนรู้ภาษาใหม่ จะมีต้นทุน และอาจต้องใช้ระยะเวลาหนึ่งเพื่อสร้างความเข้าใจ คุณ Kittinun Vantasin ให้เหตุผลว่า
- Java พัฒนาค่อนข้างช้า ถ้ายังติดอยู่กับ Java ก็จะไม่ได้ใช้ความสามารถใหม่ๆที่ควรจะมี
- ใน Java เราจะมีการตรวจสอบค่า null เต็มไปหมด แต่ใน Kotlin จะ safe เรื่องนี้
- 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 ที่แตกต่างและดีกว่า
Null Safety
การที่เราต้องมา handle เกี่ยวกับ null จะทำให้ code ของเราค่อนข้างเละ ไม่สวย ดังนั้น Kotlin เลยจัดการตรงจุดนี้ ที่ Type ซะเลย
Java จะประกาศตัวแปร instance ต่างๆ ก็เป็น nullable ทั้งหมด จึงเกิดการ handle null ตามมา
Kotlin หากต้องการให้มีค่า null ได้ ก็ต้องเติม ? ไว้หลัง Type ด้วย แต่ถ้าไม่เติม ? แล้วไป assign ค่า null ให้จะ compile ไม่ผ่าน ทำให้ code safe มากขึ้น การ handle null น้อยลง
ตัวอย่าง 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
Frontend – Kotlin supports Javascript
http://try.kotlinlang.org/#/Examples/Canvas/Fancy%20lines/Fancy%20lines.kt
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 ที่แบ่งปันสิ่งดีๆ มา ณ ที่นี้ด้วยครับ
(: