1.增強的類型推導。(類型推導是Kotlin在Java基礎上增強的語言特性之一)
fun main(args: Array<String>) {
val str = "Kotlin"
val numberInt = 123456
val numberLong = 123456L
val numberFloat = 12.45f
val numberDouble = 12.34
val numberDouble2 = 10.1e6
println(str.javaClass.name)
println(numberInt.javaClass.name)
println(numberLong.javaClass.name)
println(numberFloat.javaClass.name)
println(numberDouble.javaClass.name)
println(numberDouble2.javaClass.name)
}
java.lang.String
int
long
float
double
double
2.函數會預設被當成傳回Unit類型。是以必須顯示聲明傳回值類型。
fun sum(x: Int, y: Int): Int {
return x + y
}
fun sum2(x: Int, y: Int) {
return 1
}
//報錯
The integer literal does not conform to the expected type Unit
3.Kotlin支援單行表達式與等号的文法來定義函數,叫作表達式函數體。
這種情況下可以不聲明傳回值類型。
fun sum3(x: Int, y: Int) = x + y
但是:目前編譯器并不能針對遞歸函數的情況進行推導類型。
//錯誤
fun foo(n: Int) = if (n == 0) 1 else n * foo(n - 1)
Type checking has run into a recursive problem. Easiest workaround: specify types of your declarations explicitly
修改如下就可以了:
fun foo(n: Int): Int = if (n == 0) 1 else n * foo(n - 1)
判斷是否需要顯示聲明類型:
如果它是一個函數的參數,必須使用。
如果它是一個非表達式定義的函數,除了傳回Unit,其他情況必須使用。
如果是一個遞歸函數,必須使用。
從代碼可讀性上建議顯示聲明類型。
val 和 var的使用規則
val引用不可變,val聲明的變量是隻讀變量,它的引用不可更改,但并不代表其引用對象也不可變。
fun main(args: Array<String>) {
val payBook = PayBook("Kotlin")
payBook.name = "Pay Kolin"
payBook.printName()
}
class PayBook(var name: String) {
fun printName() {
println(this.name)
}
}
Pay Kolin
是以盡可能使用val、不可變對象及純函數來設計程式。
Kotlin支援一開始不定義val變量的取值,随後再進行指派。因為引用不可變,val聲明的變量隻能被指派一次。
//局部變量
fun main(args: Array<String>) {
val count:Int
count = 1
println(count)
}
var, 因為Kotlin相容java,是以Kotlin必須支援var。用var聲明一個局部變量可以讓程式的表達更加直接。
高階函數和Lambda
Ktolin支援部分函數特性
不僅可以像類一樣在頂層直接定義一個函數,也可以在一個函數内部定義一個函數,可以直接将函數像普通變量一樣傳遞給另一個函數,或在其他函數内被傳回。
fun copy(x: Int) {
fun double(y: Int): Int {
return y * 2
}
println(double(x))
}
高階函數:以其他函數作為參數或傳回值的函數。
函數作為參數的需求
//data class 聲明類一個Country資料類
data class PayCountry(val name: String, val continient: String, val population: Int)
class PayCountryApp {
//該方法用于找出歐洲國家
fun filterCountries(countries: List<PayCountry>): List<PayCountry> {
val res = mutableListOf<PayCountry>()
for (c in countries) {
if (c.continient == "EU") {//EU代表歐洲
res.add(c)
}
}
return res
}
//将洲作為參數傳入
fun filterCountries(countries: List<PayCountry>, continient: String): List<PayCountry> {
val res = mutableListOf<PayCountry>()
for (c in countries) {
if (c.continient == continient) {
res.add(c)
}
}
return res
}
//将洲和人口作為篩選條件
fun filterCountries(countries: List<PayCountry>, continient: String, population: Int): List<PayCountry> {
val res = mutableListOf<PayCountry>()
for (c in countries) {
if (c.continient == continient && c.population > population) {
res.add(c)
}
}
return res
}
}
從上面的代碼可以看出如果需要增加篩選條件,那麼方法參數會不斷增加。
是以需要對filterCountries方法進行解耦。是以需要把所有的搜尋邏輯抽象成一個參數。
思路一
傳入一個類對象是一種解決方法,可以根據不同的篩選需求建立不同的子類,它們各自實作了一個校驗方法。
思路二
因為Kotlin支援高階函數,就可以把篩選的邏輯變成一個方法傳入。
class CountryTest {
fun isBigEuropeanCountry(country: PayCountry): Boolean {
return country.continient == "EU" && country.population > 1000
}
}
嘗試将isBigEuropeanCountry變為一個參數。
Kotlin 中的函數類型
例:
(Int) -> Unit
Kotlin中的函數聲明需要遵循以下幾點:
1.通過->符号來組織參數類型和傳回值類型,左邊是參數類型,右邊是傳回值類型;
2.必須用一個括号來包裹參數類型;
3.傳回值類型即使是Unit,也必須顯示聲明。
如果是一個沒有參數的函數類型,參數類型部分就用()來表示。
()-> Unit
多個參數的情況下,需要使用逗号來進行分割。
(Int, String) -> Unit
Kotlin支援為聲明參數指定名字
(errCode: Int, errMsg: String) -> Unit
因為函數類型的變量也是可選的
((errCode: Int, errMsg: String?) -> Unit)?
高階函數還支援傳回另一個函數。
例:
傳入一個類型為Int的參數,然後傳回另一個函數,類型為(Int) ->Unit的函數
(Int) -> ((Int) -> Unit)
後半部分的括号可以省略掉
(Int) -> Int -> Unit
修改為傳入一個函數類型的參數,傳回一個Unit
((Int) -> Int) -> Unit
重新定義filterCountries方法
//增加一個函數類型的參數
fun filterCountries(countries: List<PayCountry>, test: (PayCountry) -> Boolean): List<PayCountry> {
val res = mutableListOf<PayCountry>()
for(c in countries) {
if(test(c)){ // 直接調用test來進行帥選
res.add(c)
}
}
return res
}
方法引用表達式
方法和成員引用
Kotlin 通過兩個冒号來實作對于某個類的方法進行引用。
例:定義一個類的構造方法引用變量。
//getBook的類型為(name:String)->Book
fun main(args: Array<String>) {
val getBook = ::PayBook
println(getBook("Kotlin Book").name)
}
Kotlin Book
如果我們需要引用某一個類中的成員變量,例如PayBook類中name
PayBook::name
//上面建立的是類型為(PayBook)->String
如果name是private屬性,就不可以像上面這樣寫。
val bookNames = listOf(PayBook("6666"), PayBook("7777")).map(PayBook::name)
println(bookNames)
[6666, 7777]
//使用方法引用傳遞參數
fun main(args: Array<String>) {
val payCountryApp = PayCountryApp()
val countryTest = CountryTest()
val countries = listOf<PayCountry>(PayCountry("A", "EU", 40000), PayCountry("B", "UE", 40000))
val countriesAfterFilter = payCountryApp.filterCountries(countries, countryTest::isBigEuropeanCountry)
println(countriesAfterFilter)
}
[PayCountry(name=A, continient=EU, population=40000)]
匿名函數
Kotlin支援在預設函數名的情況下,直接定義一個函數。
payCountryApp.filterCountries(countries, fun(country: PayCountry): Boolean {
return country.continient == "EU" && country.population > 10000
})
Lambda表達式可以了解成是簡化表達後的匿名函數。用->把參數和傳回值連接配接在一起。
例:
val filterCountries1 = payCountryApp.filterCountries(countries, { payCountry ->
payCountry.continient == "EU" && payCountry.population > 1000
})
Lambda表達式總結
1.一個Lambda表達式必須通過{}來包裹;
val paySum: (Int, Int) -> Int = {x:Int, y:Int -> x + y}
2.如果Lambda聲明了參數部分的類型,且傳回值類型支援類型推導,那麼 Lambda變量就可以省略函數類型聲明。
val paySum = { x: Int, y: Int -> x + y }
3.如果Lambda變量聲明了函數類型,那麼Lambda 的參數部分的類型就可以省略。
val paySum: (Int, Int) -> Int = { x, y -> x + y }
4.如果Lambda表達式傳回的不是Unit,那麼預設最後一行表達式的值類型就是傳回類型。
例:
val payFool = { x: Int ->
val y = x + 1
y
}
val payFool2 = {
val payCountry = PayCountry("zy", "EU", 666)
payCountry
}
如果使用fun關鍵字來聲明Lambda表達式
例子:
fun payFool3(a: Int) = {
println(a)
}
//結果是列印不出任何東西的
listOf<Int>(1,2,3).forEach{
payFool3(it)
}
// 兩者等價
//預設情況下,直接使用it來代表item,而不需要用item->聲明
listOf<Int>(1,2,3).forEach{
item->
payFool3(item)
}
1.單個參數的隐式名稱
上面的it是Kotlin 簡化Lambda表達的一種文法糖,叫作單個參數的隐式名稱。代表了這個lambda所接收的單個參數。上面的例子是列印不出任何例子的。
Function 類型
Kotlin 在JVM 層設計了Function 類型,(Function0、Function1......Function22)來相容 java的lamdba表達式,其字尾數字代表了Lambda參數的數量。
設計Function類型的主要目的之一就是要相容Java,實作在Kotlin中也能調用java的Lambda。在Java中,實際上不支援把函數作為參數,而是通過函數式接口
來實作這一特性。是以如果我們要把 java的Lambda傳給Kotlin,那麼它們必須實作Kotlin的Function接口。
invoke方法
例
實際上調用payFool3(it)僅僅是創造了一個Function0對象,是以需要調用Function0的invoke 方法才能執行print方法。
listOf<Int>(1,2,3).forEach{
payFool3(it).invoke()// 增加invoke調用
}
其實也是可以通過括号調用來替代invoke。
listOf<Int>(1,2,3).forEach{
payFool3(it)()
}
函數、 Lambda和閉包
fun可以聲明函數、Lambda表達式,如何區分?
1.fun在沒有等号、隻有花括号的情況下,是我們最常見的代碼塊函數體,如果傳回非Unit值,必須帶return。
fun sum(x: Int, y: Int): Int {
return x + y
}
2.fun帶有等号,是表達式函數體。該情況下可以省略return
fun sum(x:Int, y:Int) = x + y
3.不管是用val還是fun,如果是等号+花括号的文法,那麼建構的就是一個Lambda表達式,Lambda的參數在花括号内部聲明。是以如果左側是fun,
那麼就是Lambda表達式函數體,也必須通過()或invoke來調用Lambda。
val payFool4 = { x: Int, y: Int -> x + y }
println(payFool4.invoke(5,6))
println(payFool4(5,6))
fun payFool5(x: Int) = { y: Int -> x + y }
println(payFool5(5).invoke(6))
println(payFool5(5)(6))
在Kotlin中,發現匿名函數體、Lambda(以及局部函數、object表達式)在文法上都存在“{}”,由這對花括号包裹的代碼塊如果通路了外部環境變量則被稱為一個閉包。
一個閉包可以被當作參數傳遞或者直接使用,它類似于“通路外部變量的函數”。
與java不同的是,Kotlin中的閉包不僅可以通路外部變量,還能夠對其進行修改。
var total = 0
listOf<Int>(1,2,3).filter { it > 0 }.forEach {
total +=it
}
println(total)
6
Kotlin支援一種自運作的Lambda文法
val test = { x: Int -> println(x) }(1)
函數式程式設計 “柯裡化”,就是函數作為傳回值的典型應用。
一個函數傳回另一個函數
fun paySum(x: Int) = { y: Int -> x + y }
//等價,執行完paySum函數後,會傳回一個類型為(Int)->Int的函數
fun paySum2(x: Int): (Int) -> Int {
return { y: Int -> x + y }
}
簡單來說,柯裡化指的是把接收多個參數的函數變成一系列僅接收單一參數函數的過程,在傳回最終結果之前,前面的函數依次接收單個參數,然後傳回下一個新函數。
fun paySum3(x: Int, y: Int, z: Int) = x + y + z
//按照柯裡化的思路重新設計
fun paySum4(x: Int) = { y: Int ->
{ z: Int ->
x + y + z
}
}
println(paySum4(3)(2)(1))
6
在Lambda表達式中,還存在一種特殊的文法,如果一個函數隻有一個參數,且該函數為函數類型,那麼在調用函數時,外面的括号就可以省略。
例
fun omitParentheses(block: () -> Unit) {
block()
}
omitParentheses {
println("6666")
}
6666
如果參數不止一個,且最後一個函數為函數類型時,就可以采用類似柯裡化的調用:
fun currying(content: String, block: (String) -> Unit) {
block(content)
}
currying("currying style") {
content->
println(content)
}
//等價
currying("currying style", {
content->
println(content)
})
currying style
Kotlin的一個特性——擴充函數
Kotlin中的擴充函數允許我們在不修改已有類的前提下,給它增加新方法。
fun View.invisible() {
this.visibility = View.INVISIBLE
}
類型View被稱為接受者類型,this對應的是這個類型所建立的接收者對象。this可以被省略。
fun View.invisible() {
visibility = View.INVISIBLE
}
基于擴充函數的文法,我們可以對Array<T>類型增加一個方法。由于Kotlin的特殊文法支援,定義普通多參數函數的形式。
fun <A, B> Array<A>.corresponds(that: Array<B>, p: (A, B) -> Boolean): Boolean {
val i = this.iterator()
val j = that.iterator()
while (i.hasNext() && j.hasNext()) {
if (!p(i.next(), j.next())) {
return false
}
}
return !i.hasNext() && !j.hasNext()
}
val a = arrayOf(1, 2, 3)
val b = arrayOf(2, 3, 4)
println(a.corresponds(b) { x, y ->
x + 1 == y
})
println(a.corresponds(b) { x, y ->
x + 2 == y
})
true
false
面向表達式程式設計
表達式可以是一個值、常量、變量、操作符、函數,或者它們之間的組合,程式設計語言對其進行解釋和計算,以求産生另一個值。
表達式就是可以傳回值的語句。
1//單純的字面量表達式,值為1
-1//增加字首操作符,值為-1
1+1//加法操作符,傳回2
listOf(1,2,3)//清單表達式
"kotlin".length //值為6
Kotlin中複雜的表達式例子
{x:Int -> x + 1}//Lambda表達式,類型為(Int) -> Int
fun(x: Int) { println(x))}//匿名函數表達式,類型為(Int) -> Unit
if(x > 1) x else 1//if-else表達式,類型為 Int,假設x已指派
與Java的函數不同,Kotlin中所有的函數調用也都是表達式。
Unit類型:讓函數調用皆為表達式
因為Java 中存在void ,是以java中的函數調用不全是表達式。Java中如果聲明的函數沒有傳回值,那麼它需要使用void來修飾。因為此時不存在值和類型資訊,是以
此時不能算作表達式。
在kotlin中,函數在所有的情況下都具有傳回類型,是以引入了Unit代替Java中的void關鍵字。
void 與 Void
在描述void的時候,需要注意首字母的大小寫。Java在語言層設計了一個Void類。 java.lang.Void類似java.lang.Integer,Integer是為了對基本類型int的執行個體進行裝箱操作,
Void對應void,由于void表示沒有傳回值,是以Void并不能具有執行個體。它繼承自Object。
Unit和int一樣,是一種類型,然而它不代表任何資訊,用面向對象的術語來描述就是一個單例,它的執行個體隻有一個,可寫為()。
Kotlin 引入Unit的原因?
函數式程式設計側重于組合,尤其是很多高階函數,在源碼實作的時候都是使用泛型實作的。然而void在涉及泛型的情況時會出現問題。
Java不天然支援函數時頭等公民,模拟一種函數類型。
interface Function<Arg, Return> {
Return apply(Arg arg);
}
Function<String, Integer> strLength = new Function<String, Integer>() {
@Override
public Integer apply(String arg) {
return arg.length();
}
};
int result = strLength.apply("hello");
如果隻是需要實作一個print 方法,此時Return的類型不能使用void,隻能用Return換成Void,由于Void沒有執行個體,則傳回一個null。
Function<String, Void> payPrint = new Function<String, Void>() {
@Override
public Void apply(String s) {
return null;
}
};
Java8實際解決方法是通過引入Action<T>這種函數式接口來解決問題。最好解決方法就是引入一個單例類型Unit.
複合表達式,表達式組合
1.try 在Kotlin中也是一個表達式,try/catch/finally文法的傳回值類型由try或catch部分決定,finally不會産生影響。
2.在Kotlin中,if-else很大程度上替代了傳統三元運算符的做法。
Kotlin中的"?:"
雖然kotlin中沒有采用三元運算符,但存在"?:"的文法,注意這裡的問号和冒号必須放在一起使用。由于 kotlin可以用“?”來表示一種類型的可空性,
是以可以使用 “?:”來給一種可空類型的變量指定為空情況下的值。
fun main(args: Array<String>) {
val maybeInt: Int? = null
println(maybeInt ?: 1)
}
1
枚舉類和when表達式
1.枚舉是類
在Kotlin中,枚舉是通過一個枚舉類實作的。
enum + class + 類名
enum class Day {
MON,
TUE,
WEN,
THU,
FRI,
SAT,
SUN
}
由于是一種類,就可以有構造函數
enum class DayOfWeek(val day: Int) {
MON(1),
TUE(2),
WEN(3),
THU(4),
FRI(5),
SAT(6),
SUN(7); //如果以下有額外的方法或屬性定義,則必須強制加上分号
fun getDayNumber(): Int {
return day
}
}
需要注意的是,當在枚舉類中存在額外的方法或屬性定義,必須強制加上分号。
2.用when來代替if-else,可以使用when來優化if-else
fun schedule(sunny: Boolean, day: Day) = when (day) {
Day.MON-> println(Day.MON)
Day.THU-> println(Day.THU)
else -> when { //when關鍵字的參數吧可以省略。該情況下,分支->的左側部分需要傳回布爾值。
sunny -> println("sunny")
else -> println("rainy")
}
}
when表達式具體文法
1.一個完整的when 表達式由when關鍵字開始,用花括号包含多個邏輯分支,每個分支通過->連接配接,此時不需要break,直到比對完為止。否則執行else分支(類似switch的
default)。
2.每個邏輯分支都具有傳回值,并且可以不同。
fun payFoo(a:Int) = when (a) {
1->1
2->2
else -> 0
}
可以減少when表達式的嵌套。
fun schedule(sunny: Boolean, day: Day) = when {
day == Day.MON -> println(Day.MON)
day == Day.THU -> println(Day.THU)
sunny -> Day.FRI
else -> 1
}
for循環和範圍表達式
1.for循環
kotlin中for循環更加簡潔
for(i in 1..10) {
println(i)
}
//等價
for(i: Int in 1..10) {
println(i)
}
2.範圍表達式
Range表達式通過rangeTo函數實作,通過“..”操作符與某種類型的對象組成,除了整型的基本類型之外,該類型需要實作java.lang.Comparable接口。
String類實作了Comparable接口,字元串之間可以比較大小。
kotlin提供step函數來定義疊代的步長。
for(i in 1..10 step 2) {
println(i)
}
downTo 實作倒序
for(i in 10 downTo 1 step 2) {
println(i)
}
until函數實作一個半開區間
for(i in 1 until 10) {
println(i)
}//不包含10
3.用in 檢查成員關系
kotlin用in關鍵字來對檢查一個元素是否是一個區間或集合中的成員。
println("a" in listOf<String>("b", "c")) false
in前面加上!表示相反的結果。
println("a" !in listOf<String>("b", "c"))
in結合範圍表達式使用
println("kot" in "abc".."xyz")
//兩者等價
println("kot">="abc" && "abc"<="xyz")
任何提供疊代器(iterator)的結構都可以使用 for語句進行疊代
val array = arrayOf(1, 2, 3, 4, 5)
for (i in array) {
println(i)
}
通過調用withIndex方法,提供一個鍵值元組。
val array = arrayOf(1, 2, 3, 4, 5)
for((index, value) in array.withIndex()) {
println("element at $index is $value")
}
element at 0 is 1
element at 1 is 2
element at 2 is 3
element at 3 is 4
element at 4 is 5
中綴表達式
例如:in、step、downTo、until
kotlin标準庫中一個類似的方法to的設計:
infix fun <A, B> A.to(that: B): Pair<A,B>
to這種形式定義的函數稱為中綴函數。
A 中綴方法 B
定義一個中綴函數需要滿足的條件:
1.該中綴函數必須是某個類型的擴充函數或者成員方法。
2.該中綴函數隻能有一個參數。
3.雖然kotlin的函數支援預設值,但中綴函數的參數不能有預設值,否則以上形式的B會缺失,對中綴表達式的語義造成破壞。
4.參數數量始終為1,不能是可變參數。
函數可變參數
kotlin 通過varargs關鍵字來定義函數中的可變參數,Java中的可變參數必須是最後一個參數,kotlin中沒有這個限制,兩者都可以在函數體中以數組的方式來使用
可變參數變量。
例:
fun printLetters(vararg letters: String, count: Int) {
println("$count letters are")
for(letter in letters) {
println(letter)
}
}
可以使用*(星号)來傳入外部的變量作為可變參數的變量
var letters = arrayOf("a", "b", "c")
printLetters(*letters, count = 3)
自定義中綴表達式
called方法用infix進行修飾,中綴方法也可以使用普通的文法習慣進行調用。
class PayPerson {
infix fun called(name: String) {
println("My name is $name")
}
}
val p = PayPerson()
p called "ZY"
p.called("LC")
My name is ZY
My name is LC
參考Kotlin核心程式設計