kotlin語言的基礎語法

kotlin是一門靜態(tài)語言

參數(shù)定義

kotlin中沒有8中基本類型的概念了,只剩下了val / var

  • 參數(shù)定義:
    val/var 參數(shù)名:參數(shù)類型 = 參數(shù)值
    定義參數(shù)時 " :參數(shù)類型" 可以省略 會根據(jù)參數(shù)值來自動類型推斷(靜態(tài)的-只能在編譯期推斷,運行時不能)。
  • val: val 定義的參數(shù)只是可讀 不可寫 ,不能改變參數(shù)的值。
    val a:Int = 10;
    val b = 10
  • val和final的區(qū)別:
    -1) 屬性聲明: kotlin 中val 聲明可以改變 java中的final聲明不可以改變
class Man {
    var age: Int = 17
    val isAdult: Boolean
        get() = age >= 18
}
fun main(args: Array<String>) {
    val man = Man()
    println(man.isAdult)
    man.age = 18
    println(man.isAdult)
}
/*
打印結果:
false
true
*/

-2)函數(shù)參數(shù)
kotlin:

fun release(animator: ValueAnimator) {
    animator.cancel()
    animator = null // 編譯報錯:val cannot be reassigned
}

java:

public static void release(ValueAnimator animator) {
        animator.cancel();
        animator = null;
    }
    public static void release1(final ValueAnimator animator) {
        animator.cancel();
        animator = null; // // Cannot assign a value to final variable 'animator'
    }

在 Kotlin 中函數(shù)的參數(shù)默認就是 val 的,不可以再修改這個參數(shù),而 Java 中必須要加上 final,才能有這個效果。
-1)委托屬性

val lazyValue: String by lazy {
    println("computed--")
    "hello"
}

fun main() {
 println(lazyValue)
 println(lazyValue)
}

打印結果:

computed--hello
hello

java 中的 final 只能和 Kotlin 中 val 聲明屬性的一種情況相等。換句話說,val 包括的東西要比 Java 中的 final 多得多。需要特別說明的是,在 Kotlin 中是存在 final 這個關鍵字的。它的含義是相關成員不能被重寫,類中成員默認使用。也可以顯式地使用,用于重寫方法禁止再被重寫。在 Kotlin 中的類和方法默認都是 final 的。

  • var: var 定義的參數(shù) 可讀 可寫。
  var c = "kotlin基礎語法"
  println(c)
  c = "kotlin參數(shù)定義"
  println(c)

kotlin 相對java的重大改進之一 “null 安全”

java中的變量都是由默認值的(局部變量沒有默認值),kotlin中的變量沒有默認值

//這兩個參數(shù)表示的值不一樣,因為?表示 參數(shù)a可以為空,所以編譯后轉(zhuǎn)換成了Integer類型,而b 沒有
//可空標識,編譯后就是int類型
var a:Int?
var b:Int

var tvMessage:TextView?
//tvMessage?  這里的?表示如果tvMessage不為空才執(zhí)行賦值操作,避免null錯誤
tvMessage?.text = "消息文字"
  • 非空類型 與可空類型的區(qū)別
    不可以把可空類型賦值給非空類型,但是可以反過來賦值

字符串操作

    val c1 = "中國"
    val c2 = "漢"
    val c3 = "隔壁老王"
    val d = "姓名:$c3,民族:$c2,國家:$c1,名字長度:${c3.length}"
    println(d)
    //姓名:隔壁老王,民族:漢,國家:中國,名字長度:4
//使用三個引號 包裹的字符串,會保留字符串在編輯器上字符串的原來樣子,輸出也是換行的樣子
 val e = """
        我愛你,
        不僅僅是因為你的樣子,
        還因為,
        和你在一起時,
        我的樣子
    """.trimIndent()
    println(e)

函數(shù)

在類里叫類的成員函數(shù) (java中的唯一一種函數(shù)),kotlin中可以在與類同級的地方就是源程序文件中聲明函數(shù),叫頂級函數(shù),java中是不可以的,在kotlin中 類成員方法默認是 publi成final 類型,如果希望其能夠被繼承重寫,可以在函數(shù)聲明之前添加 open 關鍵字 , kotlin 中類成員函數(shù) 無法聲明成static 類型

class Student(val name: String) {
    // companion object 表示一個 ‘伴隨對象’
    companion object {
        //4 伴隨對象中的方法
        fun test() {
            println("開始考試")
        }
    }

    // 1. 類成員函數(shù)
    fun study(subject: String) {
        println("開始學習$subject")
    }

    //如果函數(shù)只有一行 可以這么寫,返回值 就是 a * b
    fun multi(a: Int, b: Int) = a * b

    //vararg 表示參數(shù)列表的長度 不定 ,可不傳 可傳多個 類似于java 中的 ... 
    //一個方法中不定長參數(shù) 在方法參數(shù)的最后一個
    fun add(vararg t: Int) {
        println("參數(shù)個數(shù): ${t.size}")
        if (t.isNotEmpty()) {
            var count = 0
            t.forEach {
                count += it
            }
            println("參數(shù)求和的值為:$count")
        }
    }
}

//2.頂級函數(shù)
fun play(student: Student) {
    println("${student.name} 開始游戲 放松")
}

// 3.單例對象中的函數(shù),object 表示這是一個單例
public object Canteen {
    fun eat(student: Student) {
        println("${student.name} 打好飯菜 開干")

        // 5.本地函數(shù) 聲明在一個函數(shù)內(nèi)部,聲明后直接調(diào)用
        fun clean() {
            println("吃完飯了 打掃干凈")
        }
        clean()
    }
}

fun main() {
    val student = Student("張三")
    student.study("數(shù)學")
    Student.test()
    println("${student.name} 演算 3 * 4 = ${student.multi(3, 4)}")
    student.add(1,11,111,22,33,10)
    play(student)
    Canteen.eat(student)
}

開始學習數(shù)學
開始考試
張三 演算 3 * 4 = 12
張三 開始游戲 放松
張三 打好飯菜 開干
吃完飯了 打掃干凈

kotlin中的函數(shù) 如果沒有定義返回值的時候 都是默認返回 unit 的 如果把一個 無返回的函數(shù)做參數(shù)傳遞進一個函數(shù)的時候,編譯不會報錯,運行才會報錯,是個坑點。

lambda 表達式

lambda 表達式 (是"函數(shù)類型"這種特殊類型的變量的實例化寫好)其實就是一個匿名函數(shù)(在kotlin中匿名函數(shù)也被實現(xiàn)成一種特殊的類型),主要用于 1.函數(shù)入?yún)? 2.函數(shù)返回值

  • 使用格式

var variable : (argType,[,...])-> returnType

假設有一種函數(shù)對入?yún)⑦M行求和,則該函數(shù)可以這樣聲明

var addFun:(Int,Int)->Int
同理,假設一種參數(shù)沒有入?yún)?沒有返回值,則可以如下聲明
var noParamFun: () -> Unit

這種函數(shù)可以指向下面的函數(shù)定義

fun add(a:Int,b:Int):Int{
return a + b
}
fun noParamFun(){
}

  • 函數(shù)類型和普通類型的區(qū)別
    -1)函數(shù)類型名稱與普通類型名稱不一樣,普通類型名稱直接使用一個單詞表達即可,函數(shù)類型名稱則需要通過 "(Type,[,.....]) -> returnType" 這種形式表達
    -2)函數(shù)類型不需要開發(fā)者定義,而普通類型,只要不是kotlin核心類庫中的已有類型,就需要開發(fā)者自己定義,而函數(shù)類型并不需要這樣預定義。
    -3)最大的不同,是類型實例化文法,普通類型的實例化,直接通過其構造函數(shù)完成,而函數(shù)類型的實例化卻與眾不同,通過所謂的 ‘lambda’文法完成。從這個角度看,lambda表達式其實就是一種遵循一定規(guī)則的變量賦值寫法
    -4)變量的使用方式不同,普通類型的變量主要用于讀寫,而函數(shù)類型的變量怎需要調(diào)用。

  • 函數(shù)類型實例化于lambda表達式
    聲明一個函數(shù)類型變量,并對其實例化:

var addFun : (Int,Int) -> Int = {a,b -> a + b}
由該示例可知,函數(shù)類型的實例化文法形式如下:
{arg1,[,arg2,,] -> block}
函數(shù)實例化的文法必須被花括號包裹,里面也主要分成兩部分,這兩部分被 分隔符 "->" 所分隔:
1.入?yún)⒚Q列表
2.函數(shù)體
在上面的示例中,函數(shù)體只有一行,就是 a+b ,如果有多行,則使用 "run{} " 這種塊表達式,例如:

{arg1,[,,arg2,,] -> 
   run{
      block
    }
}
//下面實例化一個具有多行表達式的函數(shù)類型
var subFun : (Int,Int) -> Int = {
  a,b -> run{
        if(a - b > 0) a -b
        else b - a
    }
}
  • 函數(shù)的返回
    函數(shù)類型實例化的函數(shù)體內(nèi)部,不能使用 return 關鍵字進行返回,例如上面示例中的subFun 函數(shù)變量,不能這樣實現(xiàn):
var subFun : (Int,Int) -> Int = {
  a,b -> run{
        if(a - b > 0) return  a -b
        else return b - a
    }
}

之所以不允許使用return返回,是應為無法確定對應的接受者,subFun變量并非一個普通的變量,而是一種函數(shù)類型,因此在這里不管是使用 return (a - b) 還是使用 return (b - a) 都不合適。當然,真實的原因也并非如此的簡單,其實這與lambda表達式的內(nèi)部實現(xiàn)有關,總之,lambda表達式,會自動推測其返回值,并不需要顯示的通過 return關鍵字進行返回。(一步來說選擇函數(shù)體執(zhí)行的最后一行做 返回)

  • 函數(shù)類型賦值和調(diào)用
fun main() {
    //不帶括號就是函數(shù)賦值,類似于變量賦值
    val func1 = subFun
    //帶有括號就是函數(shù)調(diào)用,有入?yún)⒕鸵獋魅?實參
    var result = subFun(44, 12)
    println(result)
    result = func1(12,23)
    println(result)
}
var subFun: (Int, Int) -> Int = { a, b ->
    run {
        if (a - b > 0) a - b
        else b - a
    }
}
  • 函數(shù)類型傳遞和高階函數(shù)
    函數(shù)類型做參數(shù)傳遞
fun main() {
    //調(diào)用高階函數(shù)
    advanceFun(13, multi)
    //使用即時函數(shù)變量
    advanceFun(22,{a,b -> a * b})
    //或者 寫成簡化的高階函數(shù)寫法 (看起來像是一個函數(shù)的定義)
     advanceFun(22) { a, b -> a * b}
}
//聲明一個高階函數(shù)
fun advanceFun(a:Int,funcType:(Int,Int) -> Int){
    val  result  = funcType(a,3)
    println(result)
}
//聲明一個函數(shù)類型的變量
var multi: (Int, Int) -> Int = { a, b -> a *b}
  • it
    在函數(shù)作為一個參數(shù)時,使用lambda表達式仍然顯得賦值,甚至 讓函數(shù)調(diào)用文法看起來像是函數(shù)定義,這在很多時候都讓人抓狂,因為總是需要靜下心來仔細推敲一段包含lambda文法的程序真實意圖。
    既然高階函數(shù)和lambda表達式如此復雜,那為什么又要推廣呢? 其實與 "it" 這個關鍵詞有關---當一種函數(shù)類型只包含一個入?yún)⒌臅r候,高階函數(shù)的調(diào)用就可以簡化成 "it" 關鍵字與由其他操作數(shù)所組成的單行表達式運算。
fun main() {
    //普通調(diào)用
    advanceFun(5,{it -> it * it})
    //簡化調(diào)用,此時連 "it ->" 都省略了
    advanceFun(4) {it * it}
}

fun advanceFun(a:Int,funcType:(Int) -> Int){
    val  result  = funcType(a)
    println(result)
}

在只有一個參數(shù)的情況下,可以使用it作為函數(shù)類型入?yún)⒌男螀⒚Q,在這種情況下,可以省略"it ->" 這種參數(shù)列表聲明和分隔符,使得lambda表達式得到很大簡化,于是在kotlin中可以模擬出 “語言集成查詢模式” 代碼風格,例如:

   val str = "today is saturday and i still study because i want get more knowledge to make a good life"
    val map = str.filter { it > 'a' }
        .filter { it < 'f' }
        .filterNot { it == 'c' }
    println(map)

輸出結果:

ddddbeeeeedeede

kotlin核心類庫為很多類都提供了高階函數(shù)調(diào)用,并且高階函數(shù)中 “函數(shù)類型”的入?yún)⑼贾话粋€入?yún)?,所以調(diào)用時只需要使用it關鍵字進行邏輯處理,熟悉該中形式后,就會發(fā)現(xiàn)lambda表達式的巨大魅力。

閉包

kotlin中可以定義 “局部函數(shù)” ---- 在函數(shù)內(nèi)部定義函數(shù),閉包便是建立在這種函數(shù)的基礎之上的函數(shù),
閉包通俗的來說就是局部函數(shù),可以讀取其宿主函數(shù)和類內(nèi)部 的數(shù)據(jù)。

//函數(shù)外部 無法訪問函數(shù)內(nèi)的資源
class Closure {
    var count = 0
    fun foo(){
        var a = 1
        //閉包
        fun local(){
            var c = 2
            c++
            a++
            count = a + c
            //局部函數(shù)可以訪問外部宿主的資源
            println("a = $a , count = $count ,  c = $c")
        }
        //無法訪問局部函數(shù)的資源
//        println(c)
    }
}

// 有了閉包后 使得 “函數(shù)外部 可以訪問函數(shù)內(nèi)的資源”
// 閉包的返回和使用
class Closure1{
     var count = 0
    fun foo():()->Unit{
        var a = 1
        var b = 3
        //聲明一個局部函數(shù)
        fun local(){
            a++
            b++
            count = a + b
            println("a = $a , b = $b ,count = $count")
        }
        /**
         * 返回閉包
         * 函數(shù) foo 的返回值類型是 ()->Unit,
         * 這代表 返回的是 一個(無參,無返回值)函數(shù)
         * ::  只能引用局部函數(shù),或者頂級函數(shù),而不能引用類的成員函數(shù)。
         */
        return ::local
    }
}

fun main() {
    val closure1 = Closure1()
    val local = closure1.foo()
    //當foo 函數(shù)執(zhí)行完畢后 仍然可以 通過 local 對foo的內(nèi)部變量進行讀寫
    local()
    local()
    local()
    local()
}

內(nèi)聯(lián)函數(shù)

內(nèi)聯(lián)函數(shù)顧名思義,就是將函數(shù)體直接移到函數(shù)內(nèi)部執(zhí)行,從而提高效率。不管對于操作系統(tǒng)還是JVM這樣的虛擬機,函數(shù)調(diào)用機制都是一樣的,都包括如下核心的三點
-1)函數(shù)本身的代碼指令(機器碼指定或者java字節(jié)碼指令)單獨存放在內(nèi)存中某個地址空間。
-2)函數(shù)執(zhí)行前,系統(tǒng)需要為該函數(shù)在堆中分配??臻g
-3)調(diào)用新函數(shù)時,系統(tǒng)需要將調(diào)用者函數(shù)的上下文存儲起來,以便被調(diào)用函數(shù)執(zhí)行完畢后,重新切換回調(diào)用者函數(shù)繼續(xù)執(zhí)行。
其中,函數(shù)執(zhí)行時于性能相關的是第三點,即函數(shù)調(diào)用的上下文保存(專業(yè)術語叫“現(xiàn)場保護”)。當系統(tǒng)準備調(diào)用新函數(shù)時,需要進行壓棧操作,保存調(diào)用者函數(shù)的很多運行時數(shù)據(jù),這些數(shù)據(jù)通常被直接壓如棧中,而當被調(diào)用函數(shù)執(zhí)行完畢后,系統(tǒng)需要恢復原來調(diào)用函數(shù)的運行時數(shù)據(jù),進行出棧操作。因此,函數(shù)調(diào)用要有一定的時間和空間方面的開銷,如頻繁的進行函數(shù)調(diào)用,會比較耗時。
若要消除函數(shù)頻繁調(diào)用所帶來的性能損耗,一種思路便是進行函數(shù)內(nèi)聯(lián)-----直接將被調(diào)用函數(shù)的函數(shù)體復制到調(diào)用者函數(shù)的函數(shù)體內(nèi),將函數(shù)調(diào)用轉(zhuǎn)換成直接的邏輯運算,從而避免函數(shù)調(diào)用。

fun main() {
    val result = sub(55,21)
    println(result)
}
// 在函數(shù)前加上一個inline 關鍵字 函數(shù)就會變成一個內(nèi)聯(lián)函數(shù)
inline fun sub(x:Int,y:Int) : Int{
    return if(x > y){
        x - y
    }else{
        y - x
    }
}

內(nèi)聯(lián)函數(shù)編譯之后的樣子,會把內(nèi)聯(lián)函數(shù)的函數(shù)體 直接復制到調(diào)用者函數(shù)內(nèi)部,

 public static final void main() {
      byte x$iv = 55;
      int y$iv = 21;
      int $i$f$sub = false;
      int result = x$iv - y$iv;
      boolean var4 = false;
      System.out.println(result);
   }

雖然內(nèi)聯(lián)函數(shù)解決了函數(shù)調(diào)用時現(xiàn)場保存于恢復的性能消耗,但是這種解決方案會有一個副作用----當函數(shù)被函數(shù)調(diào)用者內(nèi)聯(lián)后,會增加調(diào)用者函數(shù)的程序指令數(shù)量,同時往往也會增加調(diào)用者函數(shù)的局部變量數(shù)量,這意味者調(diào)用者函數(shù)需要分配更多的堆??臻g才能存儲下這些局部數(shù)據(jù),所以函數(shù)內(nèi)聯(lián)本質(zhì)上可以看做是以空間換時間

構造函數(shù)

class User(var a: Int, var b: String) {
    
    var c:String = ""
    
    //次構造函數(shù) ,必須調(diào)用主構造函數(shù)
    constructor(a: Int, b: String,c:String):this(a,b){
        this.c = c
    }
}

在kotlin中寫一個bean類

data class Woman(var name: String,var age:Int,var sex:String?) {
}

fun main() {
    val woman = Woman("張三",22,"男")
     //參數(shù)一一對應的賦值,如果是下劃線 _  就跳過該賦值
    val (name,_,sex) = woman
    println(name)
    println(sex)
}

lateinit的作用與限制規(guī)則 和 by lazy的區(qū)別

  • lateinit
    修飾var不能修飾val,也不能修飾原始類型(java 中的8中基本類型),當用lateinit修飾時,只是讓編譯器忽略初始化,后續(xù)初始化可以由開發(fā)者自定。
class Woman() {
    //定義name 為延遲初始化的參數(shù)
    lateinit var name: String
    var age: Int = 0
    //判斷 延遲初始化字段是否已經(jīng)初始化
    fun  isNameInit():Boolean = ::name.isInitialized
}
  • by lazy
    真正做到了聲明的同時也指定了延遲初始化時的行為,在屬性被第一次被使用的時候能自動初始化。但這些功能是要為此付出一丟丟代價的。
    代價就是不能修飾var。
val i: String by lazy {
        println("初始化值之前的操作")
        "sdkfj"
    }
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容