KOTLIN - BASIC

Chào các bạn, ở bài trước https://viblo.asia/p/android-java-hay-kotlin-3P0lPzM4Kox chúng ta đã có cái nhìn tổng quát về kotlin và để nối tiếp cho loạt bài về kotlin hôm nay mình và các bạn sẽ tìm hiểu về kotlin basic.Ở bài này mình tìm hiểu dựa trên khung sường ở https://kotlinlang.org/docs/reference/basic-types.html

1.Val và var

Trong Java chúng ta khai báo biến như sau :

String s;
 s = "hello";

 final String u = "hi"; //khai báo một constant

Với Kotlin, chúng ta có từ khóa var để khai báo biến và val để khai báo một constant, cú pháp sẽ là:

var s: String
  s = "hello"

  val v: String = "hi" //khai báo một constant, bắt buộc phải khởi tạo khi khai báo

  var u = "hey" //Có thể không xác định kiểu cho biến mà để tự compiler xác định

Note:

  • Kotlin không yêu cầu dấu ; sau mỗi statement nữa
  • Cơ chế giúp Kotlin tự xác định kiểu của biến gọi là Type inference, ngoài tự suy ra kiểu biến, cơ chế này còn sử dụng trong single-expression function (sẽ nói sau), làm cho boilerplate code giảm đi rất nhiều

2. Kiểu dữ liệu

Everything trong Kotlin đều là đối tượng, không còn dữ liệu kiểu nguyên thủy (primitive type) nữa. Điều này giúp cho mọi biến đều sử dụng được generic, gọi được các function, property hoặc có thể gán được bằng null

Tiếp theo, chúng ta sẽ nói về các kiểu dữ liệu trong Kotlin:

  • numbers
  • characters
  • booleans
  • strings
  • arrays

A. Numbers

  • Size Độ dài bit của từng kiểu được thể hiện trong bảng sau:
Type Bit width
Double 64
Float 32
Long 54
Int 32
Short 16
Byte 8

Note: character không được coi là kiểu số trong Kotlin

Từ Kotlin 1.1, khi sử dụng một số có nhiều chữ số, ta có thể sử dụng dấu gạch ngang để code dễ đọc hơn

val million = 1_000_000
val billion = 1_000_000_000L
  • Representation (Biểu diễn number) Trong Java, number được lưu trữ một cách vật lý như kiểu dữ liệu nguyên thủy của JVM trừ khi chúng ta cần biến đó có thể gán được bằng null (VD: Int?) hoặc có thể sử dụng với generic. Trong trường hợp đó, number sẽ được boxing - tức là tự wrap kiểu nguyên thủy đó để nó trở thành kiểu Object. Note: khi boxing, định danh của number không nhất thiết được giữ lại
val a: Int = 10000
 print(a === a) // Prints 'true'
 val boxedA: Int? = a
 val anotherBoxedA: Int? = a
 print(boxedA === anotherBoxedA) // !!!Prints 'false'!!!

Với toán tử ===, boxedA và anotherBoxedA bằng nhau khi và chỉ khi 2 biến này trỏ đến cùng 1 đối tượng. Trong trường hợp này, khi gán giá trị 10000 cho biến a. 10000 sẽ được boxing thành kiểu Int. Bởi vậy, theo như suy luận bình thường, biến boxedAanotherBoxedA được gán bằng a sẽ bằng nhau vì cùng trỏ tới cùng một object là a. Tuy nhiên, với các giá trị từ 0 -> 127 (2^7 - 1), các đối tượng này sẽ được lưu trong cùng một vùng nhớ. Với các giá trị lớn hơn mốc 127 này, vì boxing, các định danh sẽ không được giữ, tức là 2 biến boxedAanotherBoxedA sẽ trỏ đến 2 vùng nhớ khác nhau. Bởi vậy, boxedA ==== anotherBoxedA trả về false

val a: Int = 127
print(a === a) // Prints 'true'
val boxedA: Int? = a
val anotherBoxedA: Int? = a
print(boxedA === anotherBoxedA) // !!!Prints 'true'!!!

Mặt khác, nó giữ lại sự bằng nhau

val a: Int = 10000
 print(a == a) // Prints 'true'
 val boxedA: Int? = a
 val anotherBoxedA: Int? = a
 print(boxedA == anotherBoxedA) // Prints 'true'

Toán tử == trả về true nếu 2 biến không null và có giá trị bằng nhau

  • Explicity conversion (Chuyển đổi kiểu tường minh) Trong Java, khi muốn convert một biến có kiểu dữ liệu nhỏ hơn sang kiểu lớn hơn, ta chỉ cần gán biến đó cho một biến khác với kiểu dữ liệu ta muốn convert
 byte b = 1;
 int a = b; // OK

Với Kotlin, việc này là không thể bởi: Kotlin không hỗ trợ việc mở rộng size của một số như trong Java.

val b: Byte = 1
val i: Int = b //compiler báo lỗi: type mismatch

Để làm việc này, ta bắt buộc phải dùng các function được hỗ trợ bởi Kotlin như:

  • toByte(): Byte
  • toShort(): Short
  • toInt(): Int
  • toLong(): Long
  • toFloat(): Float
  • toDouble(): Double
  • toChar(): Char
val b: Byte = 1
val i: Int = b.toInt() //ngon
  • Toán tử Với Kotlin, các toán tử là một function member của các lớp, chúng ta có thể sử dụng các toán tử này như một function hoặc gọi chúng như một toán tử trung tố(infix) và overload các toán tử này. Các toán tử thao tác về bit bao gồm:
  • shl(bits) – signed shift left (Java's <<)
  • shr(bits) – signed shift right (Java's >>)
  • ushr(bits) – unsigned shift right (Java's >>>)
  • and(bits) – bitwise and
  • or(bits) – bitwise or
  • xor(bits) – bitwise xor
  • inv() – bitwise inversion
val x = (1 shl 2) and 0x000FF000
val y = 1.shl(2).and(0x000FF000)

B. Characters

Lớp đại diện cho character trong Kotlin là Char. Để biểu diễn một ký tự, ta dùng dấu nháy đơn

val c: Char = 'a'
val d: Char = 4 //compiler sẽ báo lỗi

Note: Char không được coi là số trong Kotlin. Bởi vậy, việc gán như trên sẽ không thành công

C. Boolean

Lớp đại diện cho kiểu Boolean trong Kotlin là Boolean. Boolean có 2 giá trị là true và false. Các toán tử được hỗ trợ cho Boolean là:

  • || - phép tuyển
  • && - phép hội
  • ! - phủ định

D. String

Đại diện cho string trong Kotlin là lớp String. String là kiểu immutable. Thành phần của string là các character, có thể truy cập bằng cách dùng []

  • Java
String s = "hello";
s.charAt(0); // 'h'
  • Kotlin
var s: String = "hello"
s[0] // 'h'
  • String Literals
    Kotlin có 2 loại literal:
  • Double quote: để xuống dòng hoặc tab thì sử dụng các ký tự đặc biệt: \n, \t...
    val s1 = "hello\n"
    
  • Triple quote: chúng ta sẽ tạo được một raw string, có thể bao gồm cả một dòng mới
val text = """
for (c in "foo") {
    print(c)
}
"""
  • String template Kotlin cho phép truyền biến vào string bằng cách sử dụng từ khóa ${}

Java

int result = 3;
String s = "Kết quả là " + result;

Kotlin

val s3 = "abc"
val str = "$s3.length is ${s3.length}" // "abc.length is 3"

E. Arrays

Lớp đại diện cho mảng trong Kotlin là Array. Việc truy cập vào các phần tử sử dụng get, set hoặc [], size là một thuộc tính của lớp Array.

  • Khởi tạo :
Để tạo mảng, ta sử dụng function arrayOf hoặc hàm tạo
val a: Array<Int> = arrayOf(1,2,3,4) // [1,2,3,4]
var a1: Array<Int> = Array(3, { it ->
       it * 3
 }) //[0,3,6,9]
  • Bất biến Array trong Kotlin là một đại lượng bất biến. Bởi vậy, không thể gán một *Array<String> *cho một *Array<Any> *(Mọi lớp trong Kotlin đều extends từ đối tượng Any) Java
String a1[] = new String [3];
Object a[] = a1; //ngon
var a2: Array<String> = arrayOf(1,2,3)
var a3: Array<Any> = a2 //compiler sẽ báo lỗi

3. Null safety

Kotlin cung cấp cả 2 kiểu là non-null và nullable(như trong Java). Từ bây giờ, exception chỉ có thể null trong trường hợp

  • Tự throw NullPointerException()
  • Sử dụng toán tử !! (sẽ được nói sau)
  • Java code gây ra
  • 1 trường hợp chưa hiểu

Mặc định, kiểu của biến là non-null. Để khai báo một biến kiểu nullable, ta sử dụng ?

var e: String? = null

Với một biến non-null, việc gán null sẽ là không thể

var e: String = null //compiler báo lỗi

Khi kiểu biến là non null, việc gọi đến các biến, function hoàn toàn safe. Ngược lại, nếu truy cập đến một biến có kiểu nullable, NPE vẫn có thể xảy ra -> unsafe call

  • Safe call : Sử dụng dấu ? để safe call
e?.length

Đặc biệt, cơ chế safe call có thể gọi theo chain:

var user :User? = User("framgia", 22)
var length = user?.name?.length

Trong trường hợp này, nếu user null hoặc name null, giá trị trả về cho length là null. Nếu tất cả không null, length khi này mới có giá trị.

Để thực hiện việc gì chỉ với các giá trị khác null, ta có thể sử dụng toán tử ?. và các hàm apply, let...

 val listWithNulls: List<String?> = listOf("A", null)
 for (item in listWithNulls) {
      item?.let { println(it) } // chỉ thực hiện với các giá trị khác null
 }
  • Toán tử elvis ?: Bình thường, để gán giá trị cho một biến, ta làm như thế này:
var b: String? = "hello"
val l: Int = if (b != null) b.length else -1

Với toán tử elvis, ta có thể làm như thế này

val l = b?.length ?: -1

Ngoài ra, vì throwreturn là biểu thức trong Kotlin, ta có thể làm như thế này:

fun foo(node: Node): String? {
      val parent = node.getParent() ?: return null
      val name = node.getName() ?: throw IllegalArgumentException("name expected")
      // ...
 }

+ Toán tử !!

val l = b!!.length

Với !!, nếu b không null, l = b.length. Nếu b null, NPE sẽ được throw

-> * Với Kotlin, NPE xuất hiện nếu nó được yêu cầu một cách tường minh chứ không thể xuất hiện nếu bạn không mong muốn*

4. Toán tử so sánh

Trong Kotlin, có 2 loại đẳng thức:

  • Referential equality: 2 references trỏ tớ cùng một object
  • Structural equality: function equals

Referential equality

Toán tử === và khác là !==. a === b khi và chỉ khi a và b cùng trỏ đến cùng một object

Structural equality

Toán tử == và khác !=. Theo quy ước, khi gọi a == b, compiler sẽ tự động translate thành

 a?equals(b) ?: (b===null)

Tức là nếu a không null, sẽ gọi function equals để kiểm tra với b, nếu a null, thì sẽ kiểm tra b có trỏ tới null không

Note: Lưu ý: khi sử dụng a==null, mặc định compiler sẽ tự động chuyển thành a===null

5. Check kiểu và casting

Kiểm tra kiểu biến Với Java, ta sử dụng từ khóa instanceOf. Với Kotlin, để kiểm tra kiểu biến trong Kotlin, ta sử dụng is và !is Java

if (s instanceOf String){

 }

Kotlin

if (s is String){

}

Casting

Sử dụng toán tử as

val t: String =  y as String

Unsafe cast

Trong Java, nếu việc cast không thành công, sẽ có một exception có thể được throw: ClassCastException. Đối tượng có kiểu nullable không thể cast thành đối tượng non-null. Nếu y = null trong trường hợp trên, sẽ có exception được bắn ra. Để cast được, Kotlin cung cấp như sau :

val t:String? = null as String?

Safe cast

Để tránh việc exception được bắn ra, Kotlin cung cấp as?

val s4: String? = s5 as? String

s4 sẽ có giá trị của s5 nếu s5 không null, ngược lại, s4 = null.

Smart casting

Trong một số trường hợp, chúng ta không phải cast một cách tường minh vì compiler sẽ track việc check kiểu bằng is và sẽ tự động cast nếu cần:

if (s is String) {
    print(s.length) // x được tự động cast thành kiểu String
  }

  if (s !is String) return
  print(s.length) // x được tự động cast thành kiểu String

  if (s !is String || s.length == 0) return // x được tự động cast thành kiểu String

6. Cấu trúc điều khiển

a. Cấu trúc if Trong Kotlin, if là một biểu thức. Bởi vậy, Kotlin không còn toán tử 3 yếu tố Java

int a = result? 1 :0;

Kotlin

var a: Int = if (result) 1 else 0

var r: Int = if (result) {
     print("ok")
     1
 } else {
     print("fail")
     0
 }

Nếu là khối lệnh, giá trị ở cuối khối lệnh là giá trị trả về. Khi gán giá trị, nhánh else bắt buộc phải có

b. Cấu trúc when:

when (x) {
      1 -> print("x == 1")
      2 -> print("x == 2")
      else -> {
          print("x chả bằng 1 cũng méo bằng 2")
      }
  }

Note: else là bắt buộc , trừ khi compiler có thể chứng minh được tất cả cá trường hợp đã được cover. Có thể sử dụng when để thay thế việc sử dụng if else if

  • Nếu việc xử lý của 1 số trường hợp giống nhau:
when (x) {
    0, 1 -> print("x == 0 or x == 1")
    else -> print("ngược lại")
  }
  • Hoặc kiểu dữ liệu
var result1 = when (s) {
    is String -> s.startsWith("prefix")
    else -> false
}
  • Có thể sử dụng when để gán giá trị
var l: Int = when (s) {
   "hello" -> 0
   "hi" -> 1
   else -> 2
 }
  • Điều kiện có thể sử dụng để kiểm tra trong khoảng
 val validNumbers = arrayOf(11, 13, 17, 19)
 when (x) {
    in 1..10 -> print("x is in the range")
    in validNumbers -> print("x is valid")
    !in 10..20 -> print("x is outside the range")
    else -> print("none of the above")
 }

c. Cấu trúc for Vòng lặp for có thể sử dụng để duyệt bất kỳ thứ gì cung cấp một iterator. VD:

Có một member hoặc extension function tên là iterator(), kiểu trả về là Iterator

  • Có một member hoặc extension function next()
  • Có một member hoặc extension function hasNext trả về kiểu Boolean Tất cả 3 function này đều cần được đánh dấu là operator
class DateRange(start: Int, val end: Int) {
      var current = start

      operator fun iterator(): Iterator<Int>{
          return object : Iterator<Int>{
              override fun next(): Int {
                  val result = current
                  current++
                  return result
              }

              override fun hasNext(): Boolean {
                  return current <= end
              }
          }
      }
  }

  //for
  for (i in DateRange(1, 10)) {
      print(i) //1..10
  }

Một vòng lặp for đối với một array sẽ được compile thành một vòng lặp index mà không cần tạo một iterator object:

//Kotlin
  var array: Array<Int> = arrayOf(1,2,5,6,7)
  for (i in array) {
    // do something
  }

  //Decompile code
  Object[] elements$iv = new Integer[]{Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(5),Integer.valueOf(6), Integer.valueOf(7)};
  Integer[] array = elements$iv;

  for(int var4 = 0; var4 < array.length; ++var4) {
     int i = array[var4].intValue();
  }

Nếu bạn muốn duyệt qua một array hoặc một list bằng các chỉ số, bạn có thể làm như sau:

  val array = arrayOf(1,3,2,6,8)
  for (i in array.indices) {
      print(array[i]) //13268
  }

Lưu ý rằng việc duyệt qua một phạm vi (iteration through a range) được compile thanh một cách thực thi tối ưu mà không phải cần thêm object nào được tạo ra

Ngoài ra, bạn có thể sử dụng function withIndex trong thư viện chuẩn:

val array = arrayOf(1,3,2,6,8)
  for (pair in array.withIndex()) {
      println("element at ${pair.index} is ${pair.value}")
  }

  //result
  //element at 0 is 1
  //element at 1 is 3
  //element at 2 is 2
  //element at 3 is 6
  //element at 4 is 8

Một số so sánh

  • Vòng lặp xuôi

Java

for (int i = 1; i <= 11 ; i++) {
    //...
}

Kotlin

for (i in 1..11){

}
  • Vòng lặp bước 2

Java

for (int i = 1; i <= 11 ; i+=2) {
    //...
 }

Kotlin

for (i in 1..11 step 2){

}
  • Vòng lặp lùi

Java

for (int i = 10; i>=0;i--) {
    //...
  }

Kotlin

for (i in 11 downTo 1){

  }
  • Vòng lặp từng giá trị Java
for (String s : arrayList) {
    //...
  }

Kotlin

for (item: Int in ints){

  }

d. Cấu trúc while Tương tự như Java nên mình sẽ không giới thiệu lại

e. break, continue và return Chức năng của từng từ khóa mặc định như sau: +return: thoát khỏi function gần nhất

  • break: thoát khỏi vòng lặp gần nhất
  • continue: tiếp tục bước tiếp theo của vòng lặp gần nhất

Label với break và continue

Để chỉ định break và continue đến một expression nào đó, sử dụng label: @label

[email protected] for (i in 1..100) {
    for (j in 1..100) {
      if (j > 2) [email protected]
    }
  }

Mặc định, nếu không có label @loop ,break sẽ thoát ra khỏi vòng lặp gần nhất, vòng lặp j. Với label @loop, [email protected] sẽ thoát ra cả 2 vòng lặp. Tương tự như vậy, continue cũng có thể dung label để đi đến bước tiếp theo của vòng lặp

Label với return

fun foo(){
    var ints:Array<Int> = arrayOf(1,3,5,6)

    ints.forEach [email protected] {
      if (it == 0) return @lit
      print(it)
    }
  }

Với trường hợp trên, câu lệnh return sẽ chỉ thoát ra khỏi function forEach, và sẽ làm tiếp các công việc phía sau. Nếu không có label, return sẽ thoát ra khỏi function foo(). Ta có thể đặt label hoặc sử dụng implicit label được định nghĩa dựa theo tên function

7. Khởi tạo một lớp

Trong Java, để khởi tạo một class, ta sử dụng từ khóa new, với Kotlin. Function tạo được gọi như một function bình thường

 var user: User = User("framgia", 22)
 

8. Biểu thức This

Để truy cập đến this từ phạm vi bên ngoài (một class hoặc extension function), chúng ta sử dụng [email protected] với @label là chỉ định đến phạm vi this mong muốn

class A { // implicit label @A
      inner class B { // implicit label @B
          fun Int.foo() { // implicit label @foo
              val a = [email protected] // trỏ đến lớp A
              val b = [email protected] // trỏ đến lớp B

              val c = this // trỏ đến param (một số Int) của function foo() bởi foo() là một extension function
              val c1 = [email protected] // trỏ đến param (một số Int) của function foo() bởi foo() là một extension function

              val funLit = [email protected] fun String.() {
                  val d = this // trỏ đến param (một String) của function funLit()
              }


                val funLit2 = { s: String ->
                  // trỏ đến param (một số Int) của function foo() bởi foo() là một extension function
                  val d1 = this
              }
          }
      }
  }

Vừa rồi là tất cả các kiến thức basic theo í hiểu của mình. Nếu thấy chỗ nào chưa đúng xin vui lòng để lại comment góp í.Xin chân thành cảm ơn còn ở i tiêp theo mình sẽ giới thiệu với các bạn về Function trong kotlin,hen gặp lại các bạn vào bài sau