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"
}