Kotlin 开发Android笔记之Kotlin学习篇
var与val 变量标志
val :声明一个不可变的变量,在初始化赋值后不可再改变值,相当于Java中的final类型变量数据
var:声明一个可变的变量,在初始化赋值后还可修改值,相当于Java中的非final类型变量数据
初始化:
//隐式声明变量类型 kotlin可自动推导判断变量类型
var value = 10
//显示声明变量类型
var valueStr:String = "Kotlin"
PS:在Android中对一个变量延迟赋值时,Kotlin就无法自动推导变量类型了,此时我们就需要显示的去声明变量类型才行
在Android开发中什么时候用val,什么时候用var呢?
PS:永远优先使用val来声明一个变量,而当val没有办法满足需求时在使用var。这样设计出来的程序会更加的健壮,也更加符合高质量的编码规范。
方法简写
/**
* 判断两个值大小
*/
fun largerNumber(num1:Int,num2:Int):Int{
return max(num1,num2)
}
简写后:
/**
* 判断两个值大小
*/
fun largerNumber(num1:Int,num2:Int)= max(num1,num2)
由于max会返回Int,所以我们可以省略return和大括号已经返回值类型,kotlin会自动推导返回值类型是Int
if条件控制
/**
* 自己编写的判断大小方法
*/
fun largerNumber1(num1: Int,num2: Int):Int{
var value=0;
if (num1>num2){
value = num1
}else{
value = num2
}
return value
}
上面是自己利用if条件判断编写的判断两个数大小方法,由于value先赋初值为0,后面再赋大值,所以用var定义。此时方法和Java实现一样。
由于kotlin中的if是可以有返回值的,所以上面代码可以修改为:
/**
* 自己编写的判断大小方法
*/
fun largerNumber1(num1: Int,num2: Int):Int{
val value= if (num1>num2){
num1
}else{
num2
}
return value
}
由于kotlin中的if有返回值,所以此处value值赋值了一次,用val声明。看到这里我们发现还可以简写,不要value。
/**
* 自己编写的判断大小方法
*/
fun largerNumber1(num1: Int,num2: Int):Int{
return if (num1>num2){
num1
}else{
num2
}
}
上面的代码我们发现既然返回的值是Int,Kotlin完全可以自己推导值类型,可以去掉return了。最后就变为了:
/**
* 自己编写的判断大小方法
*/
fun largerNumber1(num1: Int,num2: Int)=if (num1>num2){
num1
}else{
num2
}
when条件控制
Kotlin中的when条件控制语句类似于Java中的switch。
格式:
/**
* when条件语句
*/
fun getScore(name:String) = when(name){
"Tom" -> 86
"Jim" -> 77
"Jack" -> 98
"Lily" -> 87
else -> 0
}
不带参数的when条件控制:
/**
* when条件语句,不带参数
* 本方法可以用来查询所有以Tom开头的人的分数
*/
fun getScore(name:String) = when{
name.startsWith("Tom") -> 86
name == "Jim" -> 77
name == "Jack" -> 98
name == "Lily" -> 87
else -> 0
}
循环语句
Kotlin中的while语句和 Java中完全一致。
for循环,kotlin完全舍弃了Java中的for-i用法,而对Java的for-each进行了加强变成了for-in。
Kotlin中的闭区间表示…:
for (i in 0..10){
print(i)
}
Kotlin的左闭右开区间until:
Kotlin的步长step:
val value = 0 until 10 //值为:0,1,2,3,4,5,6,7,8,9
for (i in value step 2){ //步长为2,输出0,2,4,6,8
println(i)
}
Kotlin的降序downTo:
for (i in 10 downTo 1){ //downTo降序[10,1],每次减1,输出10,9,8,7,6,5,4,3,2,1
println(i)
}
for (i in 10 downTo 1 step 2){ //downTo降序[10,1],指定步长为2,输出10,8,6,4,2
println(i)
}
Kotlin中类的继承规则
kotlin中默认类是不可以被继承的,类似于类默认是final的。
class Person {
var name = ""
var age = 0
fun eat(){
println("$name is eatting. He is $age year old .")
}
}
要想上面的Person类可以被继承,我们需要在class的前面加上open关键字
open class Person {
var name = ""
var age = 0
fun eat(){
println("$name is eatting. He is $age year old .")
}
}
继承实现:
class Student :Person(){
var sno = ""
var grade = 0
}
Kotlin 的主构造函数,次构造函数
主构造函数: 每个类都会默认有一个无参数的主构造函数,当然我们也可以直接显式的指明参数。主构造函数的特点是没有函数体,直接定义到类后面即可。
class Student(val sno:String,val grade:Int) :Person(){
}
上面的Student类定义了一个带参数的主构造函数,继承于Person类。在Student中,我们把类原有的类成员变量直接放到了主构造函数里。调用时必须如此:
由于主构造函数没有函数体,我们需要在主构造函数里面编写逻辑的时候可以用init结构体,所有主构造函数的逻辑都可以写到这个结构体里面
class Student(val sno:String,val grade:Int) :Person(){
//init结构体,所有主构造函数的逻辑都写在这里
init {
println("sno is $sno")
println("grade is $grade")
}
}
次级构造函数: Kotlin类中只能有一个主构造函数,但可以有多个次级构造函数,当一个类中既有主构造函数又有次级构造函数时,所有的次级构造函数都必须调用主构造函数。
class Student(val sno:String,val grade:Int,name: String,age:Int) :Person(name,age){
//init结构体,所有主构造函数的逻辑都写在这里
init {
println("sno is $sno")
println("grade is $grade")
}
//次级构造函数1,接收name与age两个参数,并通过this关键字调用了主构造函数,给sno与grade赋了初值
constructor(name: String,age: Int) : this("",0,name,age){
}
//次级构造函数2,不接收任何参数,但调用了主构造函数
constructor() : this("",0){
}
}
上面的Student类有一个主构造函数,两个次级构造函数。这样我们就有3种方式去对类实例化。
var student1 = Student() //不带参数的实例化,利用的次级构造函数2
var student2 = Student("Tom",18) //带name和age两个参数,利用的是次级构造函数1
var student3 = Student("a10001",98,"Tom",19)
当类中只有次级构造函数,没有主构造函数时:
class Student:Person{
constructor(name: String,age: Int) : super(name,age){
}
}
上面的Student类的后面没有显示的定义主构造函数,在继承Person类时,就不需要Person类后的()了。由于没有主构造函数,次级构造函数只能直接调用父类的构造函数,所以把this关键字换成了super。这里和Java类似。
Kotlin的接口
Kotlin只能继承一个类,可以实现多个接口。Kotlin接口中不需要函数体
interface Study {
fun readBooks()
fun doHomework()
}
Kotlin中实现接口用逗号隔开即可
class Student(val sno:String,val grade:Int,name: String,age:Int) :Person(name,age),Study{
//init结构体,所有主构造函数的逻辑都写在这里
init {
println("sno is $sno")
println("grade is $grade")
}
//次级构造函数1,接收name与age两个参数,并通过this关键字调用了主构造函数,给sno与grade赋了初值
constructor(name: String,age: Int) : this("",0,name,age){
}
//次级构造函数2,不接收任何参数,但调用了主构造函数
constructor() : this("",0){
}
override fun readBooks() {
println("$name is reading .")
}
override fun doHomework() {
println("$name is doing homework .")
}
}
调用:
var student3 = Student("a10001",98,"Tom",19)
doStudy(student3)
/**
* 接口调用测试
*/
fun doStudy(study: Study){
study.readBooks()
study.doHomework()
}
输出为:
Tom is reading .
Tom is doing homework .
接口的默认实现。就是在定义接口时候就给接口里面的方法添加默认的实现,这样在类实现该接口的时候,接口已经实现默认的函数就可实现可不实现。
interface Study {
fun readBooks()
/**
* 接口 方法doHomework添加了默认实现,类在实现本接口的时候doHomework不是必须的
*/
fun doHomework(){
println("do homework default implementation .")
}
}
此时Student类可以选择不去重新doHomework()方法。
class Student(val sno:String,val grade:Int,name: String,age:Int) :Person(name,age),Study{
//init结构体,所有主构造函数的逻辑都写在这里
init {
println("sno is $sno")
println("grade is $grade")
}
//次级构造函数1,接收name与age两个参数,并通过this关键字调用了主构造函数,给sno与grade赋了初值
constructor(name: String,age: Int) : this("",0,name,age){
}
//次级构造函数2,不接收任何参数,但调用了主构造函数
constructor() : this("",0){
}
override fun readBooks() {
println("$name is reading .")
}
}
输出为:
Tom is reading .
do homework default implementation .
Ktolin可见性修饰符
private: 和Java一样,只对当前类内部可见
public: 默认值,默认是public的,所有类都可见
protected: 和Java不一样,只表示对当前类和子类可见
internal: 只对同一模块下的类可见
Kotlin数据类
kotlin中标明数据类的关键字是data,当一个类利用关键字data修饰时就代表这是数据类,kotlin会自动把equals()、hashCode()、toString()等固定且无实际逻辑意义的方法自动生成。
/**
* Cellphone 数据类
* 包含品牌和价格两个字段
*/
data class Cellphone(val brand:String,val price:Double)
调用:
val cellphone1 = Cellphone("Samsung",1223.54)
val cellphone2 = Cellphone("Oppo",5999.54)
println(cellphone1)
println("cellphone1 equals cellphone2:"+(cellphone1 == cellphone2))
输出:
Cellphone(brand=Samsung, price=1223.54)
cellphone1 equals cellphone2:false
Kotlin单例模式
单例模式,可以避免创建重复的对象。Kotlin中用object来标明该类是单例类
/**
* object关键字用于类的单例模式实现,声明该类为单例类
*/
object Singleton {
/**
* 在单例类中自定义的函数,用于编写自己的实现
*/
fun singletonTest(){
println("singletonTest is called.")
}
}
调用:
输出:
Kotlin List集合
listOf,创建的为不可变的List集合,就是我们没法对该集合进行增删改查
val list = listOf("Apple","Banana","Orange","Pear","Grape") //定义集合
for(fruit in list){ //遍历集合
println(fruit)
}
mutableListOf,定义可变List集合,能对集合进行增删改查
var list1 = mutableListOf("Apple","Banana","Orange","Pear","Grape") //可变集合
list1.add("Watermelon")
for(fruit in list1){ //遍历集合
println(fruit)
}
同理创建Set集合也是如此:setof、mutableSetOf
Kotlin Map集合
var map = mutableMapOf("Apple" to 1,"Banana" to 2,"Orange" to 3,"Pear" to 4,"Grape" to 5) //定义可变集合
map["Watermelon"] = 6 //添加数据
for ((fruit,number) in map){ //遍历集合
println("fruit is $fruit ,number is $number")
}
集合的函数式API
Lambda: 是一小段可以作为参数的代码。
Lambda表达式的语法结构:
首先最外层是一对大括号,如果有参数传入我们需要声明参数列表。参数列表的结尾使用一个->符号表示参数列表的结束以及函数体的开始,函数体中可以编写任意代码,并且最后一行代码会自动作为Lambda表达式的返回值。
查找列表中水果名最长的水果:
var list1 = mutableListOf("Apple","Banana","Orange","Pear","Grape") //可变集合
list1.add("Watermelon")
val lambda = {fruit:String -> fruit.length} //根据Lambda表达式数据结构定义Lambda表达式
val maxLength = list1.maxBy(lambda)
输出:
Watermelon
继续简化:
var list1 = mutableListOf("Apple","Banana","Orange","Pear","Grape") //可变集合
list1.add("Watermelon")
val maxLength = list1.maxBy({fruit:String -> fruit.length})
如果Lambda参数是函数的最后一个参数的话,可以把Lambda表达式移到函数括号外面:
如果Lambda参数是函数的唯一一个参数的话,可以省略函数的括号:
由于Kotlin有自己的类型推导机制,所以可以省略参数类型声明:
最后当Lambda表达式的参数列表中只有一个参数时,不必声明参数名,可以用it关键字来代替
map函数: 集合中的map函数时最常用的一种函数式API,它用于将集合中的每一个元素都映射成一个另外的值,映射的规则就是Lambda表达式中指定,最终生成一个新的集合。
实例:我们把list中的水果名都变成大写:
var list1 = mutableListOf("Apple","Banana","Orange","Pear","Grape","Watermelon") //可变集合
val newList = list1.map{it.toUpperCase()} //将每个值都变为大写
for(fruit in newList){ //遍历新集合
println(fruit)
}
输出:
APPLE
BANANA
ORANGE
PEAR
GRAPE
WATERMELON
这里只是通过map转换成大写而已,还可以小写,取首字母等。只要是映射成新的集合,都可以,而转换规则自己通过Lambda来写
filter函数: 是用来过滤集合中的数据的,可以单独使用,也可以配合map函数一起使用。
实例:保留5个字母以内的水果:
var list1 = mutableListOf("Apple","Banana","Orange","Pear","Grape","Watermelon") //可变集合
val newList = list1.filter { it.length<=5 }.map{it.toUpperCase()} //只保留五个字母以内的水果,并将每个值都变为大写
for(fruit in newList){ //遍历新集合
println(fruit)
}
输出:
APPLE
PEAR
GRAPE
any和all函数: any函数用于判断集合中是否至少存在一个元素满足指定条件。all函数用于判断集合中是否所有元素都满足指定条件。
var list2 = mutableListOf("Apple","Banana","Orange","Pear","Grape","Watermelon") //可变集合
val anyResult = list2.any{it.length<=5} //列表中是否所有元素都存在5个以内的单词
val allResult = list2.all { it.length<=5 } //列表中是否所有所有单词都在5个字母以内
println ("anyResult is $anyResult , allResult is $allResult")
输出:
JAVA函数式API的使用
Kotlin调用Java函数式API的限制:接口中只有一个待实现的方法,如果接口中有多个待实现的方法,就无法使用函数式API。
如接口Runnable:
.public interface Runnable{
void run();
}
java 的调用:
new Thread(new Runnable(){
@Override
public void run(){
//................
}
}),start();
上述代码翻译成Kotlin代码实现:
Thread(object :Runnable {
override fun run(){
//,,,,
}
}).start()
上面的Runnable接口满足JAVA函数式API,下面就来简写:
因为只有一个待实现方法,即使不重写run()方法一样的。
Thread(Runnable {
//接口方法执行逻辑
}).start()
如果一个Java方法的参数列表中不存在一个以上Java单抽象方法接口参数,可以将接口名省略。
Thread({
//接口方法执行逻辑
}).start()
当Lambda表达式是方法的最后一个参数时,可以将表达式移到方法括号后面:
Thread(){
//接口方法执行逻辑
}.start()
同时,如果Lambda表达式是方法唯一的一个参数,可以省略方法括号:
Thread{
//接口方法执行逻辑
}.start()
如OnClickListener点击事件接口.。
我们就可以写成:
textView.setOnClickListener {
}
Kotlin空指针检查
判空辅助性操作符:?.
一般的判空操作:
/**
* 接口调用测试
*/
fun doStudy(study : Study?){
if (study!=null) {
study.readBooks()
study.doHomework()
}
}
使用?.操作符后,就可以省略if条件判断了:
/**
* 接口调用测试
*/
fun doStudy(study : Study?){
study?.readBooks()
study?.doHomework()
}
判空辅助性操作符:?:
这个操作符的左右两边都接收一个表达式,如果左边表达式的结果不为空,就返回左边表达式的结果,否则就返回右边表达式的结果。
val c =if (a!=null){
a
}else{
b
}
上面代码用?:操作符就是:
val c =a ?: b
实例:获取文本的长度
/**
* 计算文本长度
*/
fun getTextLength(text: String?): Int{
if (text!=null){
return text.length
}
return 0
}
使用?,和?:操作符后:
/**
* 计算文本长度
*/
fun getTextLength(text: String?): Int{
return text?.length ?: 0
}
非空断言操作符 !! 。需要慎用
判空函数let:
表达式:
obj.let{
obj2 ->
//编写业务逻辑
}
上面的obj和obj2 是同一个对象,这里只是作为区分而已。obj调用了let函数,然后Lambda表达式中的代码就是立即执行,并且obj对象还会作为参数传递到Lambda表达式中。如:
/**
* 接口调用测试
*/
fun doStudy(study : Study?){
study?.readBooks()
study?.doHomework()
}
换成let函数就是:
/**
* 接口调用测试
*/
fun doStudy(study : Study?){
study?.let { stu ->
stu.readBooks()
stu.doHomework()
}
}
当study通过?.操作符判断不为空时,就会立即执行Lambda表达式里面的逻辑,这样就不用每次都去判断当前对象是否为空,并且对全局对象也适用。由于Lambda表达式只有一个参数,可以不用声明参数名,并用it代替:
/**
* 接口调用测试
*/
fun doStudy(study : Study?){
study?.let {
it.readBooks()
it.doHomework()
}
}
函数的参数默认值
在定义函数时,给函数的参数设定一个默认值,这样在调用的时候对已经设定默认值的参数选择可传值,也可不传值:
/**
* 定义函数时,给函数的参数设定一个默认值
*/
fun printText(num:Int,str:String="Hello"){
println("num is $num , str is $str")
}
定义printText函数,给第二个参数设定了一个默认值,调用:
输出:
num is 123 , str is Hello
键值对传值
/**
* 定义函数时,给函数的第一个参数设定一个默认值,第二个参数不设默认值,
* 此时就要在调用时通过键值对的形式传值,这样数据就不会错乱
*/
fun printText(num:Int=100,str:String){
println("num is $num , str is $str")
}
正确的调用
错误的调用:
//会默认把值赋给第一个参数,这样就是导致问题
printText("Hello")
标准函数with、run和apply
with函数: 接收两个参数,第一个参数是任意类型的对象,第二个参数是Lambda表达式。Lambda表达式的最后一行作为返回值
val result = with(obj){
//这里是obj的上下文
"value" //这里是返回值
}
作用: 连续调用同一个对象的多个方法,精简代码
例如:遍历拼接水果列表
val list = listOf("Apple","Banana","Orange","Pear","Grape") //定义集合
val builder = StringBuilder()
builder.append("Start eating fruits. \n")
for (fruit in list){
builder.append("$fruit \n")
}
builder.append("Ate all fruits.")
val result = builder.toString()
println(result)
输出:
Start eating fruits.
Apple
Banana
Orange
Pear
Grape
Ate all fruits.
利用with函数简化代码:
val list = listOf("Apple","Banana","Orange","Pear","Grape") //定义集合
val result = with(StringBuilder()){
append("Start eating fruits. \n")
for (fruit in list){
append("$fruit \n")
}
append("Ate all fruits.")
toString()
}
println(result)
上面的代码减少了对builder对象的多次调用,输出:
Start eating fruits.
Apple
Banana
Orange
Pear
Grape
Ate all fruits.
run函数:作用和with函数类似,不过语法改变了:
val result = obj.run{
//这里是obj的上下文
"value" //这里是返回值
}
上面的实例用run函数写就是这样
val result = StringBuilder().run{
append("Start eating fruits. \n")
for (fruit in list){
append("$fruit \n")
}
append("Ate all fruits.")
toString()
}
println(result)
apply函数: 和run函数类似,只不过无法指定返回值
val result = obj.apply{
//这里是obj的上下文
}
上面的实例修改为:
val result = StringBuilder().apply{
append("Start eating fruits. \n")
for (fruit in list){
append("$fruit \n")
}
append("Ate all fruits.")
}
println(result.toString())
上面的三个函数可以用于页面跳转时的数据传递:
val intent = Intent(this,FirstActivity::class.java)
intent.putExtra("name","Tom")
intent.putExtra("age",18)
startActivity(intent)
上面的代码可以修改为:
val intent = Intent(this,FirstActivity::class.java).apply {
putExtra("name","Tom")
putExtra("age",18)
}
startActivity(intent)