這篇文章會列出我認爲入門須要掌握的特性,若是要想應用到項目中去的話能夠先去GitHub上找一些優秀的Kotlin項目學習一下Kotlin的編程思想。html
Kotlin中文站:裏面有一些參考資料以及一些推薦書籍。java
不少時候直接看下編譯後的Java代碼比看別人的解釋容易得多。 以Android Studio爲例:Tools -> Kotlin -> Show Kotlin Bytecode -> 會出現一個小窗口,點擊Decompileandroid
基本語法能夠看這個java-to-kotlin(這個很重要,必定要先看一下,內容並很少),裏面是關於Java與Kotlin語法上的不一樣,固然只看這些是不夠的git
在Kotlin中你們基本上都會用lambda表達式去寫代碼,這裏不建議直接去看Kotlin lambda表達式的語法,本身剛開始寫Demo的時候編譯器會給出一些警告,指出某些代碼能夠被轉換成lambda表達式,而後讓編譯器去幫你轉換,有了這些基本印象後再去看語法會感受比較友好,具體的語法這裏就不說了,內容仍是比較多的,能夠自行查找。github
private
:類內部可見編程
protected
:類內部及其子類可見安全
internal
:Module內可見bash
public
:全局可見dom
var
表示可變變量(variable),val
表示不可變變量(value),也就是後者是被final
修飾的val name = "xiaoming"
編譯器會自動識別爲String
類型null
的,若是想要賦值爲null
在聲明對象類型時須要加上?
,且可空類型不能直接調用其方法,須要使用?.
或!!
,若是爲null
前者會返回null
後者會拋出空指針異常,具體以下:var str1: String = "abc"
str1 = null // 編譯器會報錯
var str2: String? = "abc"
str2 = null // 沒問題
str1.length // 沒問題
str2.length // 編譯器會報錯
val out = str2?.length //沒問題
println(out) // 這裏輸出null
str2!!.length // 這裏運行時拋出空指針
if (str2 != null ) {
str2.length // 沒問題
}
複製代碼
var
和val
都是private
的(就算顯式聲明瞭public
也會被編譯成private
),編譯器會自動生成getter/setter
方法(val
只有getter
),在Kotlin中能夠直接使用.
來調用,編譯器會本身去調用它的getter/setter
方法,如user.id = 1
會去調用user
對象的setId(1)
若是不想讓成員變量被外部訪問,能夠顯示聲明變量爲private
,如private val id = 1
,這樣編譯器就不會生成getter/setter
固然getter/setter
方法是能夠被修改的,操做以下:class User{
var id = 0
get() = field - 1
set(value) {
field = value + 1
}
}
複製代碼
其中field
被稱爲」幕後字段「指的就是當前變量,像下面這種寫法是錯誤的,由於id = value + 1
會繼續調用id
的setId(value + 1)
方法,就會產生死循環。ide
class User{
var id = 0
set(value) {
id = value + 1
}
}
複製代碼
在Kotlin中不須要使用+
來拼接字符串,一個字符串中能夠經過$
來獲取變量值,以下
val name = "xiaoming"
println("name is $name") // 獲取變量值
println("length is ${name.length}") // 使用表達式
println("${'$'}29.18") // 打印 $ 符號
複製代碼
上面的代碼分別會輸出
name is xiaoming
length is 8
$29.18
public
的final
的(抽象函數和接口函數除外),想要不final
須要加上open
關鍵字修飾// 能夠被重寫
open fun canOverride() {}
// 沒法被重寫
fun cannotOverride() {}
複製代碼
overrider
關鍵字override fun toString() = "kotlin"
複製代碼
fun plus(x: Int,y: Int) : Int = x + y // 此處返回類型能夠省略
fun sout(s: String) = print(s)
複製代碼
fun userInfo(id: Int, name: String = "xiaoming", sex: String = "male") {
println("id: $id, name: $name, sex: $sex")
}
fun test() {
userInfo(1) // 輸出:id: 1, name: xiaoming, sex: male
userInfo(2, "xiaohong") // 輸出:id: 2, name: xiaohong, sex: male
userInfo(1, sex = "female") // 輸出:id: 1, name: xiaoming, sex: female
}
複製代碼
若是想要提供給Java使用能夠加上@JvmOverloads
,這樣編譯成Java代碼時就會生成對應重載函數
@JvmOverloads
fun userInfo(id: Int, name: String = "xiaoming", sex: String = "male") {
println("id: $id, name: $name, sex: $sex")
}
複製代碼
Unit
對象,Unit
能夠理解成Java的Void
,能夠做爲泛型對象,若是將Unit
類型的函數賦值給一個變量,編譯器會給變量賦值一個Unit
單例val a = test()
複製代碼
編譯爲Java以後就是這樣的
Unit a = Unit.INSTANCE;
複製代碼
Unit
直接繼承於Any
類(即Java中的Object
類),只重寫了toString
方法
override fun toString() = "kotlin.Unit"
複製代碼
在Kotlin中函數是能夠嵌套的,內部函數能夠訪問到外部函數的局部變量,且其自己只能被外部函數訪問。
fun outFun(name: String) {
fun nestFun() {
print(name)
}
nestFun()
}
複製代碼
這是Kotlin的一個很是好用的特性,它能夠爲一個類添加函數,在這個函數中能夠訪問到對象的公有屬性和方法,聲明完擴展函數以後,該類及其子類的對象就能夠直接經過.
來調用這個函數,好比爲CharSequence
類添加一個toList
方法,將字符逐個添加到一個列表中而後返回
fun CharSequence.toList(): List<Char> {
val list = ArrayList<Char>()
for (char in this) {
list.add(char)
}
return list
}
複製代碼
聲明完上面這段代碼以後,就能夠直接調用了
val s = "asdf"
val list = s.toList()
複製代碼
除了擴展函數以外,Kotlin還支持擴展屬性,可是擴展屬性不能直接賦值,只能設置它的getter/setter
方法,實際上編譯成Java代碼後仍是擴展了兩個方法
var StringBuilder.lastChar: Char
get() = get(length - 1)
set(value) = setCharAt(length - 1, value)
複製代碼
在Kotlin中函數能夠做爲參數傳入到另外一個函數中
fun run(block: () -> Unit) {
block()
}
複製代碼
上面就是一個簡單的高階函數,能夠這樣使用,運行以後就會輸出run,這裏用到了Lambda 表達式,不懂的能夠再去看下
run {
print("run")
}
複製代碼
經過::
來獲取某個對象的方法來傳入(::
也能夠用來獲取變量)
val runnable = Runnable {
print("run")
}
run(runnable::run)
複製代碼
經過inline
修飾的函數爲內聯函數
當咱們使用高階函數時,傳入的函數對象會被編譯成一個對象,而後再調用該對象的方法,這樣會增長內存和性能的開銷,而若是使用內聯函數的話就能夠將方法的調用轉換爲語句的調用
fun run(block: () -> Unit) {
block()
}
fun testRun() {
run {
print("run")
}
}
複製代碼
上面的代碼編譯成Java代碼大體是這樣的,能夠看到在testRun
方法中,直接被編譯成了語句調用
public final void run(@NotNull Function0 block) {
block.invoke();
}
public final void testRun() {
String var = "run";
System.out.print(var);
}
複製代碼
使用內聯函數能夠避免產生多餘的對象,可是會增長編譯後代碼量,因此要避免內聯代碼塊過大的函數,若是一個函數中有包含多個函數參數,能夠經過noinline
關鍵字來避免內聯
inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) {}
複製代碼
Any
類,至關於Java中的Object
public final
的,想要類能夠被繼承一樣須要用open
關鍵字來修飾:
來表示的,中間由,
隔開且沒有順序要求open class A()
interface B
class C : A(), B
複製代碼
init
代碼塊中實現,若是不須要對構造函數添加訪問修飾符或者註解,那麼constructor
關鍵字能夠省略class Test constructor(a:Int) {
init {
println("Here is the constructor.")
println("value a= $a")
}
}
複製代碼
若是隻想經過構造函數給成員變量賦值的話能夠直接這樣
class Test(val a:Int){
...
}
複製代碼
若是想讓構造函數私有能夠這樣
class Test private constructor() {
...
}
複製代碼
constructor
關鍵字來聲明,這在Kotlin中叫作次構造函數,而聲明在類名上的叫作主構造函數,次構造函數須要直接或間接的繼承主構造函數,具體能夠看這裏class Test() {
init {
...
}
constructor(a:Int): this() {
...
}
}
複製代碼
class
類,一種就是Java中Class
,另一種是Kotlin自由的KClass
,它們的獲取方式也不同val kClazz = Person::class
val clazz = Person::class.java
複製代碼
嵌套類編譯成Java代碼以後就是靜態內部類
class Out {
...
class Nest {
...
}
}
val nestClass = Out.Nest()
複製代碼
Kotlin中內部類須要使用inner
關鍵字修飾
class Out {
...
inner class Inner {
...
}
}
val innerClass = Out().Inner()
複製代碼
object
關鍵字能夠簡單地建立一個單例類,其中的變量和方法均可以直接經過類名來調用
fun main(args: Array<String>) {
val string = Singleton.str;
Singleton.printMessage(string)
}
object Singleton {
val str = "Singleton"
fun printMessage(message: String) = println("$str $message")
}
複製代碼
能夠用來寫工具類
object StringUtils {
fun isEmpty(str: String?) = str == null || str == ""
}
複製代碼
Kotlin中並無static
關鍵字,若是須要靜態變量或是靜態方法的話就要使用伴生對象,使用companion object
來聲明,伴生對象的命名能夠省略,編譯器會使用默認的命名Companion
,一個類只能擁有一個伴生對象
fun main(args: Array<String>) {
val test = Test.create()
println(Test.TAG)
}
class Test {
companion object {
val TAG = "KotlinTest"
fun create() = Test()
}
}
複製代碼
上面的代碼會將TAG
編譯成Test
類的靜態成員變量,而create()
方法其實不是一個真正的靜態方法,它是屬於伴生對象類的一個普通的public
成員方法,伴生對象其實是外部類的一個靜態單例內部類,雖然能夠直接經過Test
類來調用create()
,但其實編譯事後是這樣的
// kotlin code
val test = Test.create()
// java code
Test test = Test.Companion.create();
複製代碼
若是想要生成一個真正的靜態方法,可使用@JvmStatic
註解來實現 下面使用object
和companion object
寫一個延時加載的單例類
class Singleton private constructor() {
companion object {
fun getInstance() = Holder.instance
}
object Holder {
val instance = Singleton()
}
}
複製代碼
數據類相似於lombok的@Data
註解,能夠自動生成toString()
,equals()
,hashcode()
,copy()
等方法,具體能夠去看一下編譯成的Java代碼
data class User(val id:Int, val name:String, val age:Int)
複製代碼
除了經常使用的getter/setter
以外還能夠這樣用
val user1 = User(1, "Ben", 25)
val user2 = user1.copy(id= 2, age = 23)
val (id, name, age) = user1
複製代碼
第二行代碼將user1
拷貝給了user2
並修改了id
和age
,第三行代碼將user1
的三個數據分別賦值給了id
,name
,age
三個變量,這個被稱爲解構,之因此能夠這樣寫是由於數據類還實現了componentN()
,後面在運算符重載會講到
密封類自己是個抽象類,主要特色是它的構造方法是私有的,直接繼承它的子類只能定義在密封類所在的文件中,沒法定義在別的文件中,也就是說它限制了外部繼承,直接子類就是肯定的那幾個,因此密封類也能夠理解爲功能更多的枚舉類,由於它能夠有更多的屬性以及方法
sealed class Person(val name: String, var age: Int) {
class Male(name: String, age: Int) : Person(name, age)
class Female(name: String, age: Int) : Person(name, age)
}
複製代碼
Kotlin也對密封類使用when
語句也作了優化,能夠不寫else
,由於子類是肯定的那幾個
fun test(person: Person) {
when (person) {
is Person.Male -> println("male")
is Person.Female -> println("female")
}
}
複製代碼
Kotlin中的各類操做符都對應着一種方法,好比+
,-
,*
,/
分別對應着plus
,minus
,times
,div
,這些方法都是能夠被重載的,編寫時須要在方法前面加上operate
來修飾
fun main(args: Array<String>) {
val a = Point(1, 4)
val b = Point(2, 3)
val c = a + b
c.printPoint()
}
class Point(val x: Int, val y: Int) {
operator fun plus(another: Point): Point {
return Point(x + another.x, y + another.y)
}
fun printPoint() {
println("x= $x, y= $y")
}
}
複製代碼
上面的代碼運行以後會輸出"x= 3, y= 7",此外還有不少操做符,這裏就不列舉了,前面說到的componentN()
其實也是操做符對應的方法,val (id, name, age) = user
編譯以後就是
int id = user.component1();
String name = user.component2();
int age = user.component3();
複製代碼
使用const val
來聲明一個常量,常量只能在object
,companion object
或是類的外部聲明,且只能是基本數據類型或是String
類型,由於常量要求在編譯期就能肯定它的值
const val pi = 3.14
object A {
const val pi = 3.14
}
class B {
companion object {
const val pi = 3.14
}
}
複製代碼
前面說過val
是不可變變量,它和常量的區別是const val
編譯以後是public
的而val
是pirvate
的,訪問val
只能經過它的getter
方法,而getter
方法又是能夠修改的(以下),對於開發者來講,常量應該是一個肯定的值,因此val
不是常量
object A {
val pi = 3.14
get() = field + Math.random()
}
複製代碼
下面的這些仍是屬於Kotlin入門的範疇,並非不重要,只是內容較多,就不詳細講了,網上相關的文章也有不少
Kotlin標準庫
Kotlin標準庫提供了一些好用的函數,能夠看下那些函數的實現,對學習Kotlin也有很大幫助
委託/委託屬性
Kotlin語言是原生支持委託的,其中還包括了委託屬性
泛型
Kotlin的泛型和Java的泛型大體上是相同的,可是寫法上仍是有點區別的,並且Kotlin可使用inline
和reified
來支持真泛型
協程
Kotlin是有協程庫來支持協程的,協程能夠理解爲運行在線程中的線程,比線程更輕量,使用協程必定程度上能夠簡化代碼
集合操做符
Kotlin爲集合提供了一系列操做符(其實是集合的擴展函數),相似於RxJava,能夠鏈式調用
與Java交互
通常使用Kotlin開發避免不了與Java交互,對於調用Java代碼或是讓Java調用Kotlin代碼均可能會存在一些問題
Anko、ktx
Anko和ktx是爲android設計的一個kotlin代碼庫,對於android開發能夠了解一下