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) {
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)函数执行前,系统需要为该函数在堆中分配栈空间
-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"
}