第一章 Kotlin基础语法
一、常量与变量(val,var)
- 1.什么是常量?
1 .val = value ,值类型;
2.类似Java的final;
3.不能重复赋值;
4.举例:
运行时常量:val x = getX(); 也就是程序运行时才赋值;
编译期常量:const val x = 2 ;也就是在编译时期就赋值了;
- 2.什么是变量?
1.var=variable ;
2.可以再次赋值;
3.举例:
var x = "HelloWorld"//定义变量
x = "HiWorld"//再次赋值
示例如下:
/**
* 常量
* 带const为编译期的常量
* 不带const的val为运行期常量
* 不能再次赋值
*/
const val FINAL_HELLO_WORLD:String = "Hello world"
//变量 能够再次赋值
var helloworld:String = FINAL_HELLO_WORLD
二、类型推导
val string = "Hello"//推导出String类型
val int = 5 //Int类型
var x = getString()+5 //String类型,加号在这里是连接符
三、函数--单一原则:
1.任何函数都是以fun开头的,紧接着的是方法名字,紧接着的是参数列表,紧接着的是返回值,紧接着是函数体;
fun <MethodName>(<ParamsName>:<ParamsType>):<returnValue>{}
2.如果无返回值,可以写:Unit或者在参数列表后什么都写;Unit类型Java中的void;
3.如果参数有多个,以逗号分开;
4.如果一个函数只是返回一个函数的表达式的值,直接可以等于表达式即可;
5.函数的名字不是必须的,如果有个变量接受返回值即可编译通过;--请看下面的例子
6.函数作为类型的一种出现的,可以被用来赋值与传递的;
/**
* 函数
* methodName
* param1,param2是参数名
* String Int 是参数类型
* 备注:
* 1.任何函数都是以fun开头的;
* 2.如果参数有多个,以逗号分开;
* 3没有返回值在参数后添加:Unit,也可以不写,这个Unit相当于java里面的void;
* 4如果一个函数只是返回一个函数的表达式的值,直接可以等于表达式即可;
*/
fun methodName(param1:String,param2:Int):Unit {
}
//带返回值的函数
fun testMethod(args:String):String{
return args
}
/**
* 如果一个函数只是返回一个函数的表达式的值,直接可以等于表达式即可
*/
fun returnFun0(param1: Int, param2: Int):Int {
return param1+param2
}
//上面的等价于下面的写法:
fun returnFun(param1:Int,param2:Int) = param1+param2
/**
* 函数可以没有名字的,如果有一个变量来接收的话即可
*/
val resultValue = fun(x: Int):Int {
return x
}
四、Lambda表达式
1.也就是匿名函数;函数作为类型的一种出现的,可以被用来赋值与传递的;
2.写法:{[参数列表] ->[函数体,最后一行是返回值]}
3.() -> Unit
--无参,返回值为Unit;
4.(Int) -> Int
--有参-传入Int类型,返回一个整数;
5.(String,(String) -> String) -> Boolean
--传入字符串、Lambda表达式,返回Boolean;
- 1.Lambda表达式的调用
1.用括号()来表示方法的调用,是Kotlin中的运算符,等价于invoke();这个invoke其实是运算符重载的方法
2.Lambda表达式如果没有参数,箭头 (->)就不用写了;
3.Lambda表达式不是只有一行的,它的返回值是它表达式的最后一行的值;
val sum = {a:Int,b:Int ->a+b}
//用括号()来调用,等价于invoke();
sum(2,3)
或者使用:sum.invoke(2,3),二者完全等价;
//Lambda表达式没有参数,箭头可以不写,如下:
val printHello = {
println("hello")//相当于函数的函数体
}
//Lambda表达式不是只有一行的,它的返回值是它表达式的最后一行的值
val sum = {a:Int,b:Int ->
println("$a + $b = ${a+b}")//这一行也是表达式,不过返回的是Unit类型
a+b//这一行最为Lambda的最后一行,它的值也是Lambda的返回值;
}
- 2.Lambda表达式的简化
- 1.函数参数调用时最后一个Lambda可以移出去;也就是说如果一个函数调用时,最后一个参数是Lambda表达式,我们传入参数时,那么这个参数可以被移出去;
- 2.函数的参数只有一个Lambda,调用时小括号可以省略;
- 3.如果一个Lambda只有一个参数,可以不写,可默认为it;
- 4.入参、返回值与形参一致的函数可以用函数引用的方式作为实参传入;
val sum = {a:Int,b:Int ->a+b}
//Lambda表达式在没有参数时,箭头就不用写了,如下:
//如下面的写法,因为没有参数,箭头就不用写了:
val printHello = {
println("hello")//相当于函数的函数体
}
//看下面的数组遍历:
arg.forEach{element -> println(element) }
//如果一个Lambda传入的参数只有一个,可以不写,可默认为it;上面的等价于:
arg.forEach{ println(it) }
//函数的参数只有一个Lambda表达式时,调用时可以将大括号移到小括号外面;
//上面的等价于下面的:
arg.forEach(){println(it)}
//这个小括号没有什么用,可以把这个小括号省略
arg.forEach{println(it)}
//如果函数的参数和返回值与action的参数和返回类型一致,就可以用函数引用的方式,如下:
arg.forEach(::println)
五、循环语句
for循环如下:
for (i in args) {
println("输出的是:$i")
}
for ((index, value) in args.withIndex()) {
println("$index -> $value")
}
for (indexValue in args.withIndex()) {
println("${indexValue.index}---->${indexValue.value}")
}
//或者使用下面的方式
args.forEach{
println(it)
}
}
//看下面的一个方法,return会把函数截断,无法执行到最后一行
fun main(args: Array<String>) {
args.forEach {
if (it == "c") return
println(it)
}
//你会发现如果上面的if条件触发了 ,将跳出for循环,并且执行不到下面的代码,原因是:
//上面的是表达式,而不是一个简单的遍历,return会调出整个函数的执行
println("The End")
}
//如果想只跳出it=="c"这个条件,循环继续进行,整个函数执行完毕,可以如下:
fun main(args: Array<String>) {
args.forEach ForEach@ {
if (it == "c") return@ForEach
println(it)
}
println("The End")
}
while循环
var int = 5
while (int > 0) {
println("int---->$int")
int--
}
do {
println("int---->$int")
int--
}while (int>5)
如何跳出 或 跳过循环?
跳出或终止循环:break
跳过循环:continue
多层嵌套循环的终止需要结合标签来实现
Outter@ for (i in args) {
Inner@while (i.toInt() > 0) {
break@Outter //这里只是跳出外层的for循环 break和@Outter之间不能有空格
}
}
六、异常捕获(try catch finally)
kotlin里面的异常捕获和Java中的异常捕获用法一样,最后都会调用finally;但Kotlin中的try、catch也是表达式,这点与Java中的不一样,它可以有返回值,返回值是表达式的最后一行的值;
- catch分支匹配异常类型;
- 它是一个表达式,可以用来赋值,与if else when 表达式是完全类似的;
- finally 无论try catch 是否抛出异常都会执行到;
注意此行代码代表的含义:
return try{ x/y } catch(e:Exception){ 0 } finally{...}
代表:先执行finally里面的代码,再返回;如果无异常,返回x/y;如果有异常,返回0;
//kotlin中的try catch也是表达式,可以有返回值;返回值是表达式的最后一行的值;
val result = try {
args[0]
} catch (e: Exception) {
println("jinlaile")
0
} finally {
println("进入finally代码块")
}
println(result)
输出结果:
jinlaile
进入finally代码块
0
七、成员方法、成员变量
- 属性:或者说是成员变量,类范围内的变量;构造方法中val/var修饰的都是属性;类内部也是可以定义属性的的;
/**
* aField和anotherField是类Hello的两个属性;
* 而notField不是属性,只是普通构造方法的参数;
*/
class Hello(val aField: Int, notField: Int){
val anotherField:String = "hello"
}
- 方法:或者说是成员函数,类范围内的函数;
在kotlin的类中,自动为var修饰成员变量实现了get、set方法,val修饰的只有get方法,因为val修饰的是不可变的;如果想要在get、set方法中注入部分逻辑,需要我们重写其get、set方法,如下;
/**
* Created by serenitynanian on 2017/6/27.
*/
class ClassMemberAndField {
//默认访问控制是public
var b = 0
get() {
println("执行打印逻辑")
//这个filed其实是上面b后真正的值0,这个filed只有在get、set方法中才能访问的到
return field
}
/**
* 类型java中的set方法
*
* public void setB(int b){
*
* this.b = b ;
* }
*/
set(value) {
println("执行打印逻辑")
field = value
}
}
如果想要控制get、set的访问权限,可以在get、set方法前加访问限制符,比如protected
protected set(value) {
println("logic")
field = value
}
如果说get、set中没有部分逻辑,只是想要修改访问控制符,只需要如下写即可:
class ClassMemberAndField {
//默认访问控制是public
protected var b = 0
// get() {
// //这个filed其实是上面b后真正的值0,这个filed只有在get、set方法中才能访问的到
// return field
// }
// /**
// * 类型java中的set方法
// *
// * public void setB(int b){
// *
// * this.b = b ;
// * }
// */
// protected set(value) {
// println("logic")
// field = value
// }
//如果只想要控制访问权限,只需要这样写即可:
protected get
protected set
}
在kotlin中声明成员变量时,编译器提醒你必须初始化,否则报错;与java不同的时,java会自动给你初始化初始值;但初始值也占内存的,所以有时我们只需要在使用时才想初始化,这样怎么办呢?
只需要在声明成员变量时指定一个lateinit特殊字符就行了;但是在使用之前必须进行初始化,否则报错;
var age:Int = 0
//如果不指定lateinit 编译器会提醒必须初始化或者abstract,
//指定后就是告诉编译器,我后面会进行初始化;
//如果你在初始化它之前就使用这个变量,会报错的;
lateinit var name:String
但是lateinit只能放在var修饰的可变变量前面,不能放在val修饰的变量前面;如果想要达到延迟初始化必须使用delegate,如下:
/**
* lazy需要传入一个lambda表达式,一个无参的表达式返回一个T类型,
* 这个T类型就是下面的String类型;
*/
val address:String by lazy {
"dsfa"
}
总结下属性访问控制:
var修饰的有get、set方法;
val修饰的只有get方法,没有set方法,因为val修饰的变量是不可变的;
总结下属性初始化:
属性的初始化尽量在构造方法中进行初始化完成;
无法在构造方法中初始化,尝试降级为局部变量;
var用lateinit延迟初始化,val用lazy;
可空类型谨慎使用null直接初始化;
八、表达式(中缀表达式、分支表达式、when表达式)
- 中缀表达式
- 只有一个参数,且用infix修饰的函数;
class Book {
infix fun on(place: String) {
}
}
fun main(args: Array<String>) {
//函数前没有使用infix关键字,必须像平常调用函数一样使用
Book().on("my desk")
//如果函数前面使用了infix,不用在方法前加. 不用在方法后加括号
Book() on "my dest"
}
-
2.使用了中缀表达式后,在调用时,可以直接使用[对象] <函数名> [参数对象] 如上示例;
-
分支表达式(if),而不只是分支语句
分支表达式是可以有返回值的,返回值是分支语句中最后一行的值 ;
private const val DEBUG = 1
private const val USER = 0
fun main(args: Array<String>) {
// var mode = USER
// if (args.isNotEmpty() && args[0] == "1") {
// mode = DEBUG
// }
/**
* 从下面可以看出来if可不只是一个简单的分支语句,
* 它还是一个表达式,它是有返回值的,返回值是是分支表达式中最后一行的值;
* 在上面的分支语句中,只能定义var的mode,因为后面要修改;
* 如果使用分支表达式,就直接可以使用val修饰的mode了,使用如下:
*/
val mode =
if (args.isNotEmpty() && args[0] == "1") {
DEBUG
}else{
USER
}
println("请输入用户名:")
val username = readLine()
println("请输入密码:")
val password = readLine()
if (mode == DEBUG && username == USERNAME && password == PASSWORD) {
println("超管登录成功")
} else if (username == USERNAME && password == PASSWORD) {
println("普通用户登录成功")
} else {
println("登录失败")
}
}
- when表达式
类似Java中的switch语句,由于case语句中只能判断String,enum,byte,int等,并且在每个分支语句中都要写break;在kotlin中没有了switch语句了,使用了when表达式来代替了;
public void pause() {
switch (state) {
case BUFFERING:
case PLAYING:
doPause();
break;
default:
//什么都不做
}
}
//下面是kotlin中的when表达式
fun pause() {
when (state) {
PlayerKt.State.BUFFERING, PlayerKt.State.PLAYING -> doPause()
else -> {
}
}
}
when分支表达式只要第一个分支语句执行了,不用使用break,后面的分支语句就不会执行了
fun main(args: Array<String>) {
var x = 5
//when表达式只要第一个分支语句执行了,不用使用break,后面的就不会执行了
when (x) {
is Int -> println("Hello $x")
in 1..100 -> println("$x is in 1..100")//x的值是否在[1,100]
!in 1..100 -> println("$x is not in 1..100")//x的值不在[1,100]
in 1 until 5 -> println("$x is in[0,5)")//x的值是否在[0,5)之间,取不到5
args[0].toInt() -> println("x == args[0]")//这个是说args[0]是否与x一样
}
}
//when与if一样,也有返回值,都是每一个分支最后一行表达式的值;
val mode = when {
args.isNotEmpty()&& args[0] == "1" -> 1
else -> 0
}
表达式总结:
- if ... else ...这个用法基本和Java中一致;
- 表达式必须具备完整性,如果只有if没有else编译报错;
var b = if (mode == 0) {
0
}
//如果没有下面的分支语句,编译报错
//赋值时,分支必须完备
else {
1
}
- when表达式,加强版的switch,支持任意类型,也不用写break;
支持纯表达式条件分支(类似if)
val mode = when {
args.isNotEmpty()&& args[0] == "1" -> 1
else -> 0
}
必须具备完备性;
九、具名参数,变长参数,默认参数
- 具名参数:就是在给函数传入实参的时候,把形参也赋值上,也就是下面在调用sum函数时,在传入实参的时候,把arg1 和 arg2也附带上(arg1 = 1,arg2 = 2);
具名参数的顺序可以不是固定的;
class ForDemo {
fun sum(arg1:Int,arg2:Int) = arg1 + arg2
}
fun main(args: Array<String>) {
var forDemo = ForDemo()
/**
* 在使用函数传入实参的时候,把形参也赋上;
* 因为是具名参数,所以形参的顺序可以不固定
* 也就是明确告诉编译器这个2是给arg2的,1是给arg1的
*/
forDemo.sum(arg2 = 2, arg1 = 1)
}
- 变长参数
某个参数可以接收多个值;
变长参数可以不为最后一个参数;但是传入实参时,可以放在最后用具名参数表示即可;
如果传参有歧义,需要使用具名参数指定某个特定参数;
最常见的就是main函数,它里面是个数组,我们可以将它改变为变长参数;它的使用和数组完全一样
var array = intArrayOf(1,23,43,25,56)
hello(3.0,1,123,321,2,string = "hello")
//*:Spread Operator这个符号现在只支持变长参数,且只支持array,不支持list
hello(3.0,*array,string = "hello")
fun hello(double:Double,vararg ints: Int,string:String) {
ints.forEach { ::println }
}
在Java中变长参数只能是函数的最后一个参数,否则编译器无法识别的;
在kotlin中因为存在具名函数,因此它可以放在任意位置;在传入实参的时候可以用具名函数放在最后一个位置;
*:Spread Operator 这个不是一般的运算符;
只支持展开Array;
只用于变长参数列表的实参;
不能重载;
- 默认参数
1.就是在函数编写时,可以给函数任意位置的形参一个默认值;在实际调用函数时,这个参数可以不传;
2.但是如果该默认参数不是最后一个,其他参数使用具名参数即可,不用写默认的;
3.传参出现歧义时,也就是编译器报错,需要使用具名函数,具体请看第二条;
hello(3.0,1,123,321,2,string = "hello")
//* 这个符号现在只支持变长参数,且只支持array,不支持list
hello(ints = *array,string = "hello")
fun hello(double:Double = 3.0 ,vararg ints: Int,string:String) {
ints.forEach { ::println }
}