kotlin是一門靜態語言
參數定義
kotlin中沒有8中基本類型的概念了,只剩下了val / var
-
參數定義:
val/var 參數名:參數類型 = 參數值
定義參數時 " :參數類型" 可以省略 會根據參數值來自動類型推斷(靜態的-只能在編譯期推斷,運行時不能)。 - val: val 定義的參數只是可讀 不可寫 ,不能改變參數的值。
val a:Int = 10;
val b = 10
-
val和final的區別:
-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)函數參數
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 中函數的參數默認就是 val 的,不可以再修改這個參數,而 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 定義的參數 可讀 可寫。
var c = "kotlin基礎語法"
println(c)
c = "kotlin參數定義"
println(c)
kotlin 相對java的重大改進之一 “null 安全”
java中的變量都是由默認值的(局部變量沒有默認值),kotlin中的變量沒有默認值
//這兩個參數表示的值不一樣,因為?表示 參數a可以為空,所以編譯后轉換成了Integer類型,而b 沒有
//可空標識,編譯后就是int類型
var a:Int?
var b:Int
var tvMessage:TextView?
//tvMessage? 這里的?表示如果tvMessage不為空才執行賦值操作,避免null錯誤
tvMessage?.text = "消息文字"
-
非空類型 與可空類型的區別
不可以把可空類型賦值給非空類型,但是可以反過來賦值
字符串操作
val c1 = "中國"
val c2 = "漢"
val c3 = "隔壁老王"
val d = "姓名:$c3,民族:$c2,國家:$c1,名字長度:${c3.length}"
println(d)
//姓名:隔壁老王,民族:漢,國家:中國,名字長度:4
//使用三個引號 包裹的字符串,會保留字符串在編輯器上字符串的原來樣子,輸出也是換行的樣子
val e = """
我愛你,
不僅僅是因為你的樣子,
還因為,
和你在一起時,
我的樣子
""".trimIndent()
println(e)
函數
在類里叫類的成員函數 (java中的唯一一種函數),kotlin中可以在與類同級的地方就是源程序文件中聲明函數,叫頂級函數,java中是不可以的,在kotlin中 類成員方法默認是 publi成final 類型,如果希望其能夠被繼承重寫,可以在函數聲明之前添加 open 關鍵字 , kotlin 中類成員函數 無法聲明成static 類型
class Student(val name: String) {
// companion object 表示一個 ‘伴隨對象’
companion object {
//4 伴隨對象中的方法
fun test() {
println("開始考試")
}
}
// 1. 類成員函數
fun study(subject: String) {
println("開始學習$subject")
}
//如果函數只有一行 可以這么寫,返回值 就是 a * b
fun multi(a: Int, b: Int) = a * b
//vararg 表示參數列表的長度 不定 ,可不傳 可傳多個 類似于java 中的 ...
//一個方法中不定長參數 在方法參數的最后一個
fun add(vararg t: Int) {
println("參數個數: ${t.size}")
if (t.isNotEmpty()) {
var count = 0
t.forEach {
count += it
}
println("參數求和的值為:$count")
}
}
}
//2.頂級函數
fun play(student: Student) {
println("${student.name} 開始游戲 放松")
}
// 3.單例對象中的函數,object 表示這是一個單例
public object Canteen {
fun eat(student: Student) {
println("${student.name} 打好飯菜 開干")
// 5.本地函數 聲明在一個函數內部,聲明后直接調用
fun clean() {
println("吃完飯了 打掃干凈")
}
clean()
}
}
fun main() {
val student = Student("張三")
student.study("數學")
Student.test()
println("${student.name} 演算 3 * 4 = ${student.multi(3, 4)}")
student.add(1,11,111,22,33,10)
play(student)
Canteen.eat(student)
}
開始學習數學
開始考試
張三 演算 3 * 4 = 12
張三 開始游戲 放松
張三 打好飯菜 開干
吃完飯了 打掃干凈
kotlin中的函數 如果沒有定義返回值的時候 都是默認返回 unit 的 如果把一個 無返回的函數做參數傳遞進一個函數的時候,編譯不會報錯,運行才會報錯,是個坑點。
lambda 表達式
lambda 表達式 (是"函數類型"這種特殊類型的變量的實例化寫好)其實就是一個匿名函數(在kotlin中匿名函數也被實現成一種特殊的類型),主要用于 1.函數入參 2.函數返回值
- 使用格式
var variable : (argType,[,...])-> returnType
假設有一種函數對入參進行求和,則該函數可以這樣聲明
var addFun:(Int,Int)->Int
同理,假設一種參數沒有入參,沒有返回值,則可以如下聲明
var noParamFun: () -> Unit
這種函數可以指向下面的函數定義
fun add(a:Int,b:Int):Int{
return a + b
}
fun noParamFun(){
}
函數類型和普通類型的區別
-1)函數類型名稱與普通類型名稱不一樣,普通類型名稱直接使用一個單詞表達即可,函數類型名稱則需要通過 "(Type,[,.....]) -> returnType" 這種形式表達
-2)函數類型不需要開發者定義,而普通類型,只要不是kotlin核心類庫中的已有類型,就需要開發者自己定義,而函數類型并不需要這樣預定義。
-3)最大的不同,是類型實例化文法,普通類型的實例化,直接通過其構造函數完成,而函數類型的實例化卻與眾不同,通過所謂的 ‘lambda’文法完成。從這個角度看,lambda表達式其實就是一種遵循一定規則的變量賦值寫法
-4)變量的使用方式不同,普通類型的變量主要用于讀寫,而函數類型的變量怎需要調用。函數類型實例化于lambda表達式
聲明一個函數類型變量,并對其實例化:
var addFun : (Int,Int) -> Int = {a,b -> a + b}
由該示例可知,函數類型的實例化文法形式如下:
{arg1,[,arg2,,] -> block}
函數實例化的文法必須被花括號包裹,里面也主要分成兩部分,這兩部分被 分隔符 "->" 所分隔:
1.入參名稱列表
2.函數體
在上面的示例中,函數體只有一行,就是 a+b ,如果有多行,則使用 "run{} " 這種塊表達式,例如:
{arg1,[,,arg2,,] ->
run{
block
}
}
//下面實例化一個具有多行表達式的函數類型
var subFun : (Int,Int) -> Int = {
a,b -> run{
if(a - b > 0) a -b
else b - a
}
}
-
函數的返回
函數類型實例化的函數體內部,不能使用 return 關鍵字進行返回,例如上面示例中的subFun 函數變量,不能這樣實現:
var subFun : (Int,Int) -> Int = {
a,b -> run{
if(a - b > 0) return a -b
else return b - a
}
}
之所以不允許使用return返回,是應為無法確定對應的接受者,subFun變量并非一個普通的變量,而是一種函數類型,因此在這里不管是使用 return (a - b) 還是使用 return (b - a) 都不合適。當然,真實的原因也并非如此的簡單,其實這與lambda表達式的內部實現有關,總之,lambda表達式,會自動推測其返回值,并不需要顯示的通過 return關鍵字進行返回。(一步來說選擇函數體執行的最后一行做 返回)
- 函數類型賦值和調用
fun main() {
//不帶括號就是函數賦值,類似于變量賦值
val func1 = subFun
//帶有括號就是函數調用,有入參就要傳入 實參
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
}
}
-
函數類型傳遞和高階函數
函數類型做參數傳遞
fun main() {
//調用高階函數
advanceFun(13, multi)
//使用即時函數變量
advanceFun(22,{a,b -> a * b})
//或者 寫成簡化的高階函數寫法 (看起來像是一個函數的定義)
advanceFun(22) { a, b -> a * b}
}
//聲明一個高階函數
fun advanceFun(a:Int,funcType:(Int,Int) -> Int){
val result = funcType(a,3)
println(result)
}
//聲明一個函數類型的變量
var multi: (Int, Int) -> Int = { a, b -> a *b}
-
it
在函數作為一個參數時,使用lambda表達式仍然顯得賦值,甚至 讓函數調用文法看起來像是函數定義,這在很多時候都讓人抓狂,因為總是需要靜下心來仔細推敲一段包含lambda文法的程序真實意圖。
既然高階函數和lambda表達式如此復雜,那為什么又要推廣呢? 其實與 "it" 這個關鍵詞有關---當一種函數類型只包含一個入參的時候,高階函數的調用就可以簡化成 "it" 關鍵字與由其他操作數所組成的單行表達式運算。
fun main() {
//普通調用
advanceFun(5,{it -> it * it})
//簡化調用,此時連 "it ->" 都省略了
advanceFun(4) {it * it}
}
fun advanceFun(a:Int,funcType:(Int) -> Int){
val result = funcType(a)
println(result)
}
在只有一個參數的情況下,可以使用it作為函數類型入參的形參名稱,在這種情況下,可以省略"it ->" 這種參數列表聲明和分隔符,使得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核心類庫為很多類都提供了高階函數調用,并且高階函數中 “函數類型”的入參往往都只包含一個入參,所以調用時只需要使用it關鍵字進行邏輯處理,熟悉該中形式后,就會發現lambda表達式的巨大魅力。
閉包
kotlin中可以定義 “局部函數” ---- 在函數內部定義函數,閉包便是建立在這種函數的基礎之上的函數,
閉包通俗的來說就是局部函數,可以讀取其宿主函數和類內部 的數據。
//函數外部 無法訪問函數內的資源
class Closure {
var count = 0
fun foo(){
var a = 1
//閉包
fun local(){
var c = 2
c++
a++
count = a + c
//局部函數可以訪問外部宿主的資源
println("a = $a , count = $count , c = $c")
}
//無法訪問局部函數的資源
// println(c)
}
}
// 有了閉包后 使得 “函數外部 可以訪問函數內的資源”
// 閉包的返回和使用
class Closure1{
var count = 0
fun foo():()->Unit{
var a = 1
var b = 3
//聲明一個局部函數
fun local(){
a++
b++
count = a + b
println("a = $a , b = $b ,count = $count")
}
/**
* 返回閉包
* 函數 foo 的返回值類型是 ()->Unit,
* 這代表 返回的是 一個(無參,無返回值)函數
* :: 只能引用局部函數,或者頂級函數,而不能引用類的成員函數。
*/
return ::local
}
}
fun main() {
val closure1 = Closure1()
val local = closure1.foo()
//當foo 函數執行完畢后 仍然可以 通過 local 對foo的內部變量進行讀寫
local()
local()
local()
local()
}
內聯函數
內聯函數顧名思義,就是將函數體直接移到函數內部執行,從而提高效率。不管對于操作系統還是JVM這樣的虛擬機,函數調用機制都是一樣的,都包括如下核心的三點
-1)函數本身的代碼指令(機器碼指定或者java字節碼指令)單獨存放在內存中某個地址空間。
-2)函數執行前,系統需要為該函數在堆中分配??臻g
-3)調用新函數時,系統需要將調用者函數的上下文存儲起來,以便被調用函數執行完畢后,重新切換回調用者函數繼續執行。
其中,函數執行時于性能相關的是第三點,即函數調用的上下文保存(專業術語叫“現場保護”)。當系統準備調用新函數時,需要進行壓棧操作,保存調用者函數的很多運行時數據,這些數據通常被直接壓如棧中,而當被調用函數執行完畢后,系統需要恢復原來調用函數的運行時數據,進行出棧操作。因此,函數調用要有一定的時間和空間方面的開銷,如頻繁的進行函數調用,會比較耗時。
若要消除函數頻繁調用所帶來的性能損耗,一種思路便是進行函數內聯-----直接將被調用函數的函數體復制到調用者函數的函數體內,將函數調用轉換成直接的邏輯運算,從而避免函數調用。
fun main() {
val result = sub(55,21)
println(result)
}
// 在函數前加上一個inline 關鍵字 函數就會變成一個內聯函數
inline fun sub(x:Int,y:Int) : Int{
return if(x > y){
x - y
}else{
y - x
}
}
內聯函數編譯之后的樣子,會把內聯函數的函數體 直接復制到調用者函數內部,
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);
}
雖然內聯函數解決了函數調用時現場保存于恢復的性能消耗,但是這種解決方案會有一個副作用----當函數被函數調用者內聯后,會增加調用者函數的程序指令數量,同時往往也會增加調用者函數的局部變量數量,這意味者調用者函數需要分配更多的堆棧空間才能存儲下這些局部數據,所以函數內聯本質上可以看做是以空間換時間
構造函數
class User(var a: Int, var b: String) {
var c:String = ""
//次構造函數 ,必須調用主構造函數
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,"男")
//參數一一對應的賦值,如果是下劃線 _ 就跳過該賦值
val (name,_,sex) = woman
println(name)
println(sex)
}
lateinit的作用與限制規則 和 by lazy的區別
-
lateinit
修飾var不能修飾val,也不能修飾原始類型(java 中的8中基本類型),當用lateinit修飾時,只是讓編譯器忽略初始化,后續初始化可以由開發者自定。
class Woman() {
//定義name 為延遲初始化的參數
lateinit var name: String
var age: Int = 0
//判斷 延遲初始化字段是否已經初始化
fun isNameInit():Boolean = ::name.isInitialized
}
-
by lazy
真正做到了聲明的同時也指定了延遲初始化時的行為,在屬性被第一次被使用的時候能自動初始化。但這些功能是要為此付出一丟丟代價的。
代價就是不能修飾var。
val i: String by lazy {
println("初始化值之前的操作")
"sdkfj"
}