kotlin讀書筆記之函數基本知識以及泛型

1.函數內容

1.1 函數的聲明與用法

kotlin的函數使用fun關鍵字聲明,如下所示:

fun double(x: Int): Int {
    return 2 * x
}
double(2).tostring()

1.2 函數參數

java不一樣,kotlin的函數參數採用pascal表示法定義,即 name: type

fun powerOf(number: Int, exponent: Int) { /*……*/ }

kotlin可以設置默認參數:

fun read(b: Array<Byte>, off: Int = 0, len: Int = b.size) { /*……*/ }

kotlin可以使用具名稱的函數參數。使得代碼可讀性更強:

reformat(str,
    normalizeCase = true,
    upperCaseFirstLetter = true,
    divideByCamelHumps = false,
    wordSeparator = '_'
)
reformat(str, wordSeparator = '_')

具名參數和位置參數混用的時候,位置參數應該放在具名參數的前面,允許調用 f(1, y = 2) 但不允許 f(x = 1, 2)

可以通過使用星號操作符將可變數量參數(vararg 以具名形式傳入:

fun foo(vararg strings: String) { /*……*/ }

foo(strings = *arrayOf("a", "b", "c"))

當覆蓋一個帶有默認值的方法時,應該省略默認參數值:

open class A {
    open fun foo(i: Int = 10) { /*……*/ }
}

class B : A() {
    override fun foo(i: Int) { /*……*/ }  // 不能有默認值
}

如果要指定某個參數的值,那麼可以使用具名參數:

fun foo(bar: Int = 0, baz: Int) { /*……*/ }

foo(baz = 1) // 使用默認值 bar = 0

如果lambda表達式作爲參數的最後一個值,那麼他可以放在括號外面:

fun foo(bar: Int = 0, baz: Int = 1, qux: () -> Unit) { /*……*/ }

foo { println("hello") }        // 使用兩個默認值 bar = 0 與 baz = 1

1.3 函數返回值

1.3.1 返回Unit的函數

Unit相當於java中的void,即不返回任何值。

fun printHello(name: String?): Unit{ …… }
fun printHello(name: String?) { …… }//代碼等同於上面

1.3.2 返回單個表達式的函數

返回單個表達式的時候,可以直接把表達式寫在等號後面。並且如果返回值類型可以由編譯器推斷的時候,那麼返回值類型可以忽略。

fun double(x: Int): Int = x * 2
fun double(x: Int) = x * 2//Int類型可以省略

1.3.3 顯式返回類型

具有塊代碼體的函數必須始終顯式指定返回類型。 Kotlin 不推斷具有塊代碼體的函數的返回類型,因爲這樣的函數在代碼體中可能有複雜的控制流,並且返回類型對於讀者(有時甚至對於編譯器)是不明顯的。

1.4 中綴表示法

中綴表示法有點像C++中的運算符重載。中綴表示法的標有infix關鍵字,它三個必要條件:

  • 必須是成員函數或擴展函數;
  • 必須只有一個參數;
  • 其參數不得接受可變數量的參數且不能有默認值。
infix fun Int.add(x: Int): Int {
    return this + x
}

fun main(args: Array<String>) {
    println("Result = "+(1 add 2))
}

1.5 函數作用域

1.6 泛型函數

1.6.1 Kotlin的泛型

1.6.1.1 泛型的定義與使用

kotlin泛型的寫法和java類似,都是將泛型寫在尖括號內,並放在類後面。如下所示:

class Box<T>(t: T) {
    var value = t
}

val box: Box<Int> = Box<Int>(1)

val box = Box(1)//kotlin會自動判斷類型

還有一種就是泛型函數,寫法類似:

fun <T> singletonList(item: T): List<T> {
    // ……
}

val l = singletonList<Int>(1)

val l = singletonList(1)//kotlin會自動判斷類型

1.6.1.2 泛型約束

對應於java泛型中的extend關鍵字

fun <T:Int> printTest(value:T){
    println("result = "+(value+3))
}

fun main(args: Array<String>) {
    printTest(1)
    // printTest("nothing") //報錯:inferred type String is not a subtype of Int
}

如果是需要多個上界的話,那麼可以用where來實現

interface A{
    fun printA(infor:String)
}

interface B{
    fun printB(infor:String)
}

fun <T> printTest2(value:T)
where
T:A,
T:B
{
    value.printA("打印A")
    value.printB("打印B")
}
fun main(args: Array<String>) {
    printTest2(object:A,B{
            override fun printA(infor:String){
                println("printA = "+infor)
            }
            override fun printB(infor:String){
                println("printB = "+infor)
            }
        })
}

1.6.1.3 類型擦除

類型擦除指的是泛型聲明用法執行的類型安全檢測僅在編譯期進行。在運行期間不保留實參的任何信息,即無法用as來判斷類型。然而有一種例外即內聯函數的具體化類型參數。

1.6.2 聲明處型變

型變分爲兩種:協變和逆變。用法分別是在參數前加上out和in。

協變的時候對象是作爲生產者,也就是說它只負責產出。逆變的時候對象是作爲消費者,它只負責消費。具體代碼如下所示:

interface Operation<out T>{
    fun eat():T
}

interface Operation2<in E>{
    fun print(e:E)
}

fun main(args: Array<String>) {
    val A:Operation<Number> = object:Operation<Int>{
        override fun eat():Int{
            return 17
        }
    }

    val B:Operation2<Int> = object:Operation2<Number>{
        override fun print(e:Number){
            println("Operation2")
        }
    }
}

1.6.3 使用處型變

使用處型變也稱爲類型投影,他是在不變類型上動態做協變或者逆變。

    val list1:MutableList<String> = mutableListOf()
    list1.add("hello")
    list1.add("world")

    val list2:MutableList<out String> = mutableListOf()
    list2.add("hello")  // compile error
    list2.add("world")  // compile error

    val list3:MutableList<in String> = mutableListOf()
    list3.add("hello")
    list3.add("world")

    lateinit var list4:MutableList<String>
    list4 = list3;     // compile error

如上所示MutableList是一個不變對象。當使用out時,list2即變成協變的對象,他不能作爲消費者而只能是生產者。對應的當使用in時候,list3就變成了逆變的對象。

1.6.5 星投影

有種場景是參數類型未知,那麼星投影的功能就是聲明對象的只讀特性。
Foo <out T:Tupper>,那麼Foo<*>表示的意義就是該對象作爲生產者,可以安全的讀取讀取TUpper 的值。

fun main(args: Array<String>) {
    val star6: Star3<*> = Star3<String>("hello word")
    println(star6.getValue())
}

class Star3<T:CharSequence>(private var t: T) {

    fun getValue(): T {
        return this.t
    }
}

打印出的是"hello word",那麼可以理解爲out修飾參數的時候,星投影會則輸出的是Sting的類。

Foo <in T>,那麼Foo<*>表示的意義就是該對象作爲消費者,不能寫入任何數據。這個好理解,既然類型都不知道,輸入什麼都是不安全的。也就是隻讀不能寫了。
Foo <T:Tupper>,那麼Foo<*>表示的意義就是結合了上面兩種場景。寫入時候不能安全寫入任何數據,讀取時只能安全讀取 TUpper 的值。

2.總結

總體上感覺kotlin的函數是對java的方法做了幾點處理:

優化:比如說彌補了java泛型在只產出或者只消費的場景下無法型變的問題

智能:會在編譯的時候就自動判別類型,比如說 val key = 1,key就自動判斷爲Int類型。這點在java做不到

擴展:引入了內聯,以及類似運算符重載的中綴表示法。

接下來繼續學習內聯函數,擴展函數等其他類型的函數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章