目录
2、kotlin的数据类型(全部是类,有自己的方法和继承结构,没有关键字的基本数据类型)
次构造函数(可用kotlin提供的给函数设定默认值的功能代替)
1、 变量
val 和 var 作为关关键字 ,kotlin有类型推导机制,与java相比一行代码末尾不加;
val 用来声明一个不可变的变量,对应java中的final
var 用来声明一个可变的变量
当对一个变量延迟赋值时无法使用类型推导机制,需要显式指明类型 val a : Int = 10
val 和 var 的使用技巧:优先使用val,val不符合要求时再使用var
2、kotlin的数据类型(全部是类,有自己的方法和继承结构,没有关键字的基本数据类型)
java的数据类型:int long short float double boolean char byte
kotlin的数据类型:Int Long Short Float Double Boolean Char Byte
3、函数
fun methodName(param1 : Int, param2 : Int) : Int {
}
参数和返回值可为空
函数的参数默认值,如果赋值,调用改函数时有默认值的可以不传参了,如:
fun printParams(num: Int = 123, str: String){
}
printParams(str= "world', num = 123),kotlin中不靠参数顺序匹配
通过给主构造函数设置参数默认值可以替代次构造函数
函数的可见性修饰符
java中public protected default private
kotlin中public proctected internal private,kotlin的private和java一样,public才是默认项,java中的protected表示对当前类和子类以及同一包名下可见,而kotlin仅表示对当前类和子类可见,kotlin中没有java的default对同一包名的类可见的特性,引入了新的概念,对同一模块可见internal
4.程序的逻辑控制
顺序语句、条件语句、循环语句
条件语句关键字:if 和 when
if和java中区别:if可以有返回值,返回值是if条件中最后一行代码的返回值
fun largerNumber(num1 : Int, num2 :Int) = if (num1 > num2) num1 else num2
when:类似于switch,但是强大的多,可以有返回值,java中的switch只允许整型或短整型作为条件,1.7增加字符串,另外还需要break
fun getScore(name : String) = when (name){
"tom" -> 86
"lily" -> 70
else -> 0
}
格式: 匹配值 -> {执行;逻辑} 一行代码 {}可省略
when 还可以匹配类型,
如 is Int -> println("num is Int")
else -> println("num is not support")
when 还可以不带参数(有必要使用场景)
如 when{
name。startsWith("tom") -> 86
name == "" -> 67
else -> 0
}
== 与java 的equals类似,字符串或者对象相等可以用 == 比较
循环语句
while(和java基本一样)和for
for循环相比java改动大,for-i被舍弃,java的for-each加强变成了for-in
区间 是java所没有的东西 var range = 0 .. 10 与数学表达式[0,10]意思一样;var range = 0 util 10
和数学[0, 10);step 来代表步长 step 2和java中的i = i + 2类似;downTo表示降序 i in 10 downTo 0相当于[10, 0]
for(i in 0..10 step 2){
}
5.面向对象编程
关键字和java一样class,创建类不用new,去掉了new关键字 var person = Persion()
继承
kotlin中任何一个非抽象类默认是不可以被继承的,相当于java中类加final关键字,open关键字使的类可以被继承;继承关键字java中的extends变成了:
class Student : Person(){
}
主构造函数:
继承自父类比java多加了(),kotlin中分为主构造函数和次构造函数,主构造函数是最常用的函数,每个类默认有一个不带参的主构造函数,也可以显示指定参数,特点是无函数体,直接定义在类名的后面即可,如class Student(val sno : String, val grade : Int,name:String,age:Int) : Person(name, age){},在主构造参数中赋值可以声明为val,并且声明成val或者var的参数将自动成为该类的字段,所以如果Student中想要通过主构造函数给Person传参数name和age,这两个参数不能声明为var或者val。若果想在主构造函数中编写逻辑可以使用init结构体init{}
子类继承父类必须要在构造方法中调用父类的构造方法(java和kotlin),因此在定义类的时候通过继承父类带括号表示子类在构造方法中调用父类的某个构造方法
次构造函数(可用kotlin提供的给函数设定默认值的功能代替)
次构造函数必须直接或者间接继承自主构造函数,如:
class Student(val sno:String,val grade:Int,name :String, age:Int):Person(name, age){
constructor(name:String, age:Int):this("", 0,name,age){
}
}
kotlin中中有种特殊情况,只有次构造函数没有主构造函数,如
class Student :Person[
constructor(name:String,age:Int):super(name,age)
匿名内部类采用关键字object
接口
interface Study{
fun readBookks()
}
class Student(name:String,age:Int):Person(name,age),Study{
override fun readBooks
}
接口允许有默认实现,java8以后也允许,如
interface Study{
fun read{
doSomeThing()
}
}
数据类与单例类(kotlin特有)
java中的数据类要重写equals,hashCode、toString,kotlin中用data关键字修饰class可由kotlin自动根据主构造函数中的参数生成这些工作代码
如:data class Cellphone(val brand:String),如果类中没有任何处理可以省略大括号
object代替关键字class表示单例,如:object SingLeton{
},使用方法:
SingleLeton.方法
6.Lambda编程
集合的创建与遍历
val list = ArrayList<String>()
list.add("apple") --> val list = listOf("apple"),listOf添加的集合不可变,只可以读取,不可以增删改;mutableListOf创建的是可变的,Set也有这两种setOf,mutableSetOf
for(friut in list){
}
val map = HashMap<String,Int>()
map.put("apple", 1),这是java式的写法,kotlin不建议采用put和get方法,建议使用下标方式
添加数据如map["apple"] = 1,获取数据 val number = map["apple"]
val map = mapOf("apple" to 1),to是一个infix函数
for((fruir, number) in map){
println()
}
函数式API,Lambda语法
语法:{参数名1:类型,参数名2 :类型 -> 函数体},函数体重最后一行代码会作为Lambda的返回值返回
Lambda是一小段可以作为参数传递的代码,如val maxLegnth = list.maxBy{it.length}
其原型应该为var lambda = {fruit : String -> fruit.length}
var maxlength = maxBy(lambda)
简化步骤:
1.可以不用专门定义lambda变量,直接将lambda表达式作为参数传入
2.当lambda表达式是函数的最后一个参数时,可以将lambda表达式移到函数的括号外面
3.如果lambda表达式是函数的唯一参数的话,还可以把括号去掉
4.由于kotlin有出色的类型推导机制,因此lambda表达式的参数列表其实在大多数情况下是不用声明参数类型的
5.当lambda表达式中只有一个参数时,也不用声明参数名,而是可以用it来代替
最终简化为:var maxlength = maxBy{it.lenght}
集合中比较常用的函数式api:
maxBy:遍历集合时将每次遍历的值作为参数传给lambda表达式可,lambda表达式的函数体作为条件
map:将集合中的元素映射成另一个值,映射的规则在lambda中指定
var newList = list.map{it.uoUpperCase()}
filter :可以联合map一起使用
var newList = list.filter{it.length <= 5}.map{it.toUpperCase()}
any:判断集合中是否至少一个满足条件的,list.any{it.lenght <= 5}
all:判断集合中是否所有元素都满足条件,list.all{it.lenght <= 5}
java中单抽象接口方法也可以使用函数式api
限定于kotlin调用java,因为kotlin中有高阶函数来实现更加强大的自定义函数式api,而不需要像java一样借助单抽象方法接口来实现
匿名内部类采用关键字object
Thread(object : Runnable){
override fun run(){
}
}).start()
简化步骤:
1.Runnable只有一个抽象方法,可以去掉显示的重写run方法
2.当lambda表达式是函数最后一个参数时,可以移到括号外
简化后为:Thread{
}.start()
7.空指针检查
kotlin默认所有的参数和变量都不可为空
Int?和String?表示可为空的类型
判空辅助工具:?. ?: let !!
if(a != null){
a.doSomething()
}-> a?.doSomething()
var c = if(a != null){
a
}else{
b
} -> var c = a ?: b 左边表达式不为空则返回左边表达式的结果,否则返回右边的结果
fun getTextLength(text:String) = text?.length?: 0
在main函数中判断过content不为空,自信content不为空,可以使用非空断言工具!!
fun printUpperCase(){
var upperCase = content!!.toUpperCase()
}
let是kotlin的标准函数,会将原始对象作为参数传递给lambda表达式中,调用obj.let{obj2-> 函数体},函数体会立即执行,obj和obj2是一个对象
study?.let{stu->
stu.readBooks(0
stu.doHomeWork()
}
let可以处理全局变量的判空,如:
var study :Study? = null
fun doStudy(){
if(study != null){即使判空也会报错,因为随时会被其他线程修改
study.readBooks()
study.doHomework()
}
}
可以改为study?.let{
it.readBooks
it.doHomework
}
8.字符串内嵌表达式
语法${},当只有一个变量时可以省略掉括号
CellPhone(brand = $ brand,price = $price)
9.标准函数和静态方法
标准函数是指Standard.kt中定义的函数,任何kotlin代码都可以自由的调用任何标准函数
with、run、apply、let(辅助判空)repeat
with
var result = with(obj){有两个参数,第一个参数时任何对象,第二个lambda表达式中拥有第一个参数的上下文,并且使用lambda表达式的最后一行作为返回值返回
}
如:var result = with(StringBuilder()){
append(“”)
append(“apple”)
toString()
}
run
和with类似作用,不过不能不能像with一样直接调用,参数只有一个lambda表达式,只能obj.run{
}
如:StringBuilder().run{
append("")
append("123")
toString()
}
apply
apply和run类似,唯一不同是不是返回值,而是返回对象本身
val intent = Intent(context,SecondActivity::class.java){
putExtra()
putExtra()
}
startActivity(intent)
repeat(n,lambda)
会将lambda中表达式执行n遍
静态方法
java中只需要加上static关键字即可
kotlin弱化了静态方法这个概念,kotlin提供了比静态方法更好的方式来实现,那就是单例类
如:object Util{
fun doAction(){
}
},调用和静态方法一样,Util.doAction()
单例类会将所有方法变成类似于静态方法调用的方式,但是我们有时候需要把单例类中某些方法变成类似于静态方法,这个时候需要用到companion object
class Util{
fun doAction1(){//创建实例才能调用
}
companion object{
fun doAction2(){//类似于静态方法的调用
}
}
}
companion object这个关键字会在Util的内部创建一个伴生类,kotlin会保证Util只有一个伴生类
kotlin要想实现真正的静态方法,可以采用注解和顶层方法,单例类和companion object都不是真正的静态方法,在java中以静态方法形式调用会报错,注解@JvmStatic,kotlin会将注解方法编译成真正的java静态方法,但是@JvmStatic只能将注解加载单例的方法或者companion object的方法时候上,在普通方法上会报错;顶层方法是指没有定义在任何类中的方法,kotlin会将所有的顶层方法全部编译成静态方法,顶层方法不管包名和路径,可以在任意位置调用,在java中没法直接顶层方法调用,但是可以用file.顶层方法()这种方式调用。因为java没有顶层方法的概念,但是会把创建的Helper.kt的file编译生成HelperKt.java,方法以静态方法上的形式定义在HelperKt.java的文件中
10.延迟初始化和密封类
延迟初始化
如:
private var adapter :MyAdapter ? = null
override fun onCreate(saveInstanceState : Bundle){
adapter = MyAdapter(list)
}
override fun onClick(view : View?){
adapter?.notifyItemInserted()
}
这种情况很确信adpater在使用前不为空,所以可以使用lateinit修饰adapter,延迟初始化,这样就不用多做非空的判断了,当然lateinit会有风险,如果用错可能会报错UninitializedPropertyAccessException,另外可以用::adapter.isInitialized来判断是否已经初始化过,避免重复初始化
密封类
如:Result是接口,Success和Failure实现Result
fun getResultMsg(result : Result) = when(result){
is Success -> result.msg
is Failure-> result.error.msg
else -> throw IllegalArugmentException()//实际上有时候只需要两个分支,但是kotlin编译器要求必须要写else
}
密封类关键字:sealed class
sealed calss Result
class Success(val msg :String) : Result()
calss Failure(val error : Exception):Result()
这个时候就可以去掉when语句的else了,因为当when语句传入密封类作为条件时,kotlin编译器会自动检查该密封类有哪些子类,并且强制要求将每一个子类所对应的条件全部处理。这样就可以保证即使没有else也能保证不会出现分支的遗漏。
注意:密封类及其子类只能定义在同一个文件的顶层位置,不能嵌套在其他类中,这是由底层实现机制决定。
11.扩展函数和运算符重载
扩展函数
扩展函数表示即使在不修改某个类的源码的基础上,仍然可以打开该类,向该类中添加新的函数
扩展函数的语法结构:
fun ClassName.methodName(param 1: Int, param2:Int ):Int{
return 0
}
如果想向String添加一个扩展函数,建议建个String.kt文件,当然扩展函数也可以定义在任何一个类中,但是通常来说最好定义为顶层方法,这样就拥有全局的访问域
kotlin对String的扩展函数有reverse和capitalize还有repeat
运算符重载
kotlin允许我们将所欲的运算符甚至其他的关键字进行重载,从而拓展这些运算符个关键字的用法,运算符重载使用关键字operator,只要在指定函数的前面加上关键字operator,就可以实现运算符重载的功能了
class obj{
operator fun plus(o : Obj) : Obj{
return Obj(this.value + o.value)
}
operator fun plus(newValue : Int) : Obj{
return tihs.value +newValue
}
}
使用方式: val obj1 = Obj(0
val obj2 = Obj() val obj3 = obj1 + obj2 val obj4 = obj3 + 20
kotlin编译器会帮我们把这几行代码编译成obj1.plus(obj2),kotlin允许我们对同一个运算符进行多重重载
可重载运算符和关键字:
a+b a.plus(b)
a-b a.minus(b)
a* b a.times(b)
a/b a.div(b)
a%b a.rem(b)
a++ ia.nc()
a-- a.dec()
+a a.unaryPlus
(-a a.unaryMinus
!a a.not
a ==b a.equal(b)
a>b < >= <= a. compareTo()b
a..b a.rangeTo(b)
a[b] a.get(b)
a[b] = c a.set(b,c)
a in b b.contains(b)
12.高阶函数
定义高阶函数
定义:如果一个函数接收另一个函数作为参数,或者返回值的类型为另一个函数,那么这个函数就是高阶函数
kotlin中添加了函数类型,函数类型的语法规则:(String, Int)-> Unit,左边表示该函数接收什么类型的参数,无就只写一堆(),右边表示该函数类型的返回值,无就写Unit(相当于java中的void)
如:fun example(func : (String, Int)-> Unit){
func("hello", 123)
}
fun num1AndNum2(num1 : Int, num2:Int, operation : (Int, Int)-> Int):Int{
operation(num1, num2)
}
fun plus(num1 :Int, num2:Int) : Int{
return num1 + num2
}
fun minus(num1 : Int, num2 : Int) : Int{
return num1 - num2
}
num1AndNum2(5, 2, ::plus ) ::plus表示对函数的引用
num1AndNum2(5, 2 minus)
像以上这种函数引用的写法虽然能够正常工作,但是如果每次调用高阶函数,还得先定义一个和其参数类型匹配的函数,这有点复杂,因此kotlin还支持许多其他方式来调用高阶函数,如lambda、匿名函数、成员引用等,其中lambda是最常用的
如:val result = num1AndNum2(5, 2){n1, n2 -> n1 + n2
}
fun StringBuilder.build(block : StringBuilder.() -> unit) : StringBuilder{
block()
return this
}
函数类型参数,在函数类型前面加上ClassName.,表示该函数定义在哪个类中,会自动拥有该类的上下文,定义StringBuilder.的好处是当我们调用build函数,传入lambda表达式中自动拥有StringBuilder的上下文,同时这也是apply函数的实现方式,但是目前这个函数只能作用在StringBuilder中,而apply函数是可以作用在所有类上的,要想是实现apply,需要使用泛型
内联函数的作用
kotlin的代码最终还是要编译成Java字节码,但是java中没有高阶函数的概念,kotlin编译器要把高阶函数编译成java支持的语法结构
上节代码会被编译成:(代码做了调整好理解,但是并不是严格对应了kotlin转换为java的代码)
public static int num1AndNum2(int num1, int num2, Function operation){
return operation.invoke(num1, num2);
}
调用:int result = num1AndNum2(num1, num2, new Function(){
public Integer invoke(int num1, int num2){
return num1 + num2;
}
})
以上就是kotlin背后的实现原理,这就表明,每次调用lambda,都会创建一个新的匿名实例,会增加额外的内存和性能开销,为了解决这个问题,kotlin提供了内联函数的功能,内联函数在高阶函数前加关键字inline即可,内敛函数的原理是每次编译的时候把内联函数中的代码自动替换到调用它的地方
noinline和crossline
当一个高阶函数有多个函数型参数,如果给高阶函数加上inline,则会将所有引用的Lambda表达式关联,如果只需要内联一个,则需要关键字noinline
如:inline fun inlineTest(block1 : ()-> unit,, noinline block2: ()-> unit){},该高阶函数只对block所引用的Lambda表达式进行内联
内联函数类型参数和非内联函数类型参数的区别
1.了解到内联函数的好处,为什么还需要noinline,这是因为内联的函数在编译的时候会被替换,没有真正的参数属性,非内联的函数类型参数可以自由传递给其他任何函数,因为它就是一个真实的参数
2.它俩还有一个重要区别,就是内联函数可以用return 返回,而非内联只能进行局部返回
如: fun printString(str:String, block : String - > unit){
xxx
block()
xxx
}
fun main(){
xxx
var str = ""
printString(str){str->
prinitln("lambda start")
if(str.isEmpty()) return@prinitString
println(s)
xxx
}
非内联函数只能进行局部返回,不能用return,只能使用return@printString表示局部返回,如果是内联函数,则表示返回的地方是调用高阶函数的函数返回
将高级函数声明为inline函数是良好习惯,但是有时候声明为inline会报错,如下:
inline fun runRunnable(block: () -> unit){
var runnable = Runnable{
block()//由于内联函数所引用的lambda允许使用return返回,但是由于我们是在匿名内部类中调用的函数型参数,此时是不可能进行外层函数返回的,做多只能在匿名内部类返回,所以会报错
}
runnable.run()
}
以上报错情况可以用crossinline解决,。在函数类型参数前面加关键字crossinline,之所以报错是因为内联函数的lambda表达式中可以使用return关键字,但是匿名内部类当中的不允许用return关键字有冲突,而crossinline则就像一个契约,保证内联函数的lambda表达式中不使用return关键字,当加上crossinline后,除了不能在lambda中使用return,可以使用return@runRunnable进行局部返回
7.kotlin内置函数
max(num1, num2)
8.kotlin语法糖
当函数体只有一行代码时,可以不必编写函数体,直接将代码写在函数尾部用=连接,同时由于类型推导机制,可以省略返回值的类型Int,由于 = 还可以省略掉 return
如:fun largerNumber(num1 : Int, num2 : Int) = max(num1, num2)
9.其他
1.kotlin-android-extensions在ListView的适配器中无法工作
2.定义常量的关键字是const,注意只有在单例类、companion object或者顶层方法中才可以使用const
3.kotlin中类型强制转换用as关键字 val activity = context as Activity
4.BaseActivity::class.java表示获取BaseActivity的class对象
5.BroacastReceiver不允许开启线程,不允许做耗时操作