Kotlin cơ bản
1. Val và var
Trong Java chúng ta khai báo biến như vầy:
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à:
Note:
Kotlin không yêu cầu dấu
;
sau mỗi statement nữaCơ 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 | 46 |
Float | 32 |
Long | 64 |
Int | 32 |
Short | 16 |
Byte | 8 |
Note: character không được coi là kiểu số trong Kotlin
Constant literal
Tương tự như trong Java, Kotlin cũng có các constant literal để xác định kiểu dữ liệu cho các số:
Dấu gạch ngang '_'
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
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
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 boxedA
và anotherBoxedA
đượ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 boxedA
và anotherBoxedA
sẽ trỏ đến 2 vùng nhớ khác nhau. Bởi vậy, boxedA ==== anotherBoxedA
trả về false
Thử lại với a = 127
Mặt khác, nó giữ lại sự bằng nhau
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
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.
Để 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
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 andor(bits)
-- bitwise orxor(bits)
-- bitwise xorinv()
-- bitwise inversion
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
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
Kotlin
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
...
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
String template
Kotlin cho phép truyền biến vào string bằng cách sử dụng từ khóa ${}
Java
Kotlin
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
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
Kotlin
Các lớp Array khác
Kotlin có hỗ trợ một số các lớp để chuyên lưu trữ các giá trị nguyên thủy nhằm giảm bớt chi phí boxing: ByteArray
, ShortArray
, IntArray
... Tuy nhiên thì các lớp này k có quan hệ kế thừa gì với Array
. Tuy nhiên, các lớp này vẫn có thuộc tính và function tương tự như Array
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 ?
Với một biến non-null, việc gán null sẽ là không thể
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
Trong trường hợp này, nếu biến e null, kết quả trả về sẽ là null. Ngược lại, kết quả là độ dài của xâu e.
Đặc biệt, cơ chế safe call có thể gọi theo chain:
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
...
Toán tử elvis ?:
Bình thường, để gán giá trị cho một biến, ta làm như thế này
Với toán tử elvis, ta có thể làm như thế này
Nếu b khác null, l = b.length, nếu b null, l = -1
Ngoài ra, vì throw
và return
là biểu thức trong Kotlin, ta có thể làm như thế này
Toán tử !!
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
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
Kotlin
Casting
Sử dụng toán tử as
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
Safe cast
Để tránh việc exception được bắn ra, Kotlin cung cấp as?
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:
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
Kotlin
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
Cấu trúc when thay thế switch trong Java. Cấu trúc của when là
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:
Các trường hợp của when không nhất thiết phải là constant, chúng có thể là một function
Hoặc kiểu dữ liệu
Có thể sử dụng when để gán giá trị
Điều kiện có thể sử dụng để kiểm tra trong khoảng
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ểuBoolean
Tất cả 3 function này đều cần được đánh dấu làoperator
VD:
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:
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:
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:
Một số so sánh
Vòng lặp xuôi
Java
Kotlin
Vòng lặp bước 2
Java
Kotlin
Vòng lặp lùi
Java
Kotlin
Vòng lặp từng giá trị
Java
Kotlin
d. Cấu trúc while
Tương tự như Java
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ấtbreak
: thoát khỏi vòng lặp gần nhấtcontinue
: 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
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
, break@loop
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
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
Ngoài ra, ta còn có thể sử dụng label vs return
trong trường hợp muốn trả về một giá trị nào đó tại label đó bằng cách sử dụng
7. Visibility modifier
Trong Java, để định nghĩa khả năng truy cập của các property, function của một class, ta sử dụng access modifier. Trong Kotlin, đó là visibility modifier. Visibility modifier của Kotlin gồm 4 mức, tương đương như với Java:
public
internal
protected
private
Để hiểu chi tiết hơn, hãy xem Visibility modifier
8. 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
Java
Kotlin
9. Biểu thức This
Để biểu thị đối tượng hiện tại, ta sử dụng bieru thức this
Trong một thành phần của class,
this
tham chiếu đến đối tượng hiện tại của classTrong extension function, hoặc function literal with receiver,
this
biểu thị param của function - tức là giá trị được truyền vào bên trái dấu.
Nếuthis
không có label chỉ định đối tượng chỉ vào,this
tham chiếu tới phạm vi chứa gần nhất. Để tham chiếu đến một phạm vi khác, label chỉ định sẽ được sử dụng
Label chỉ định
Để 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 this@label
với @label
là chỉ định đến phạm vi this
mong muốn
10. Package
Package cho phép chúng ta chia nhỏ code bằng namspace. Bất kỳ file nào cũng có thể bắt đầu bằng việc khai báo một package
Ở đây, tất cả các nội dung(VD: class, function...) đều thuộc package đã được khai báo. Bởi vậy, fully qualified name (FQN) của các thành phần trong đó tương ứng là: com.packt.myproject.Foo
là tên của class Foo
, com.packt.myproject.bar
là tên của function bar
.
Nếu package không được khai báo, các thành phần trong file đó sẽ thuộc về root package
.