目录
一、前言:
最近由于工作需要用到kotlin语言,所以学习了相关的语法知识,现在将学习到的东西进行一个总结。用于方便后续的使用。主要从以下几个方面来记录
1、变量的定义和使用
2、函数的定义和使用
3、流程控制关键字的使用
4、类和对象的使用
5、接口的使用
6、权限修饰符
7、数据类
8、lambda的使用
9、函数式API的使用
10、let函数的使用
11、内嵌表达式的使用
在这边说明一下kotlin基本的使用原则
kotlin每个语句可以以;结尾,也可以不加。但是java必须要加。
如果不以kotlin结尾的时候,需要确保你的语句要有换行,如果没有换行则必须要加;
二、变量的定义和使用
1、定义:
kotlin的变量的定义使用var和val来进行定义,并且分为灵活定义和非灵活定义。
所谓的灵活定义就是变量声明的时候没有指定类型,而是通过赋值的初始值来决定类型,例如var a = 1。这种方式类似于js中的变量定义,但是跟js不一样的是,在kotlin中如果一个变量被赋初始值之后,那么这个变量的类型也就随之确定了。后续也不能再赋值成其他类型,而在js中这种操作是支持的
例如var a = 1 a = "test" //这种操作是不允许的
所谓的非灵活定义就是在声明的时候就已经指明这个是什么类型,格式就是 name : type。
其中name就是变量的名字,type是变量的类型
例如
var a : Int = 1 //正确
var a : Int = "test" //错误,已经明确指明是int类型了,无法再赋值成string类型
var和val的区别就是var定义了之后可以再次修改,val定义了之后无法再修改。可以认为是java的final类型的变量
2、使用:
使用的话与java没有什么区别,就是通过变量名进行操作,还可以通过美元符$配合使用,后面细说
三、函数的定义和使用
1、定义:
函数的定义与java有着一些不一样,java的函数定义的格式如下:
type name(type arg1, type arg2, ...) {
function body
}
而在kotlin中函数的定义的标准格式如下:
fun name(arg1 : type, arg2: type,....) : returnType {
function body
}
其中 fun关键字、name是必须的。参数和返回值不是必须的。如果没有定义参数那调用的时候就不用传参,同样的如果没有定义了返回值类型,那就直接有函数体就够了,需要把:returnType一起去掉,如果没有定义返回值类型的话,那么默认返回的是unit类型,也就是java中的void类型,只是在kotlin中可以省略不写
2、简化定义:
上面写的是函数的一个标准的定义的过程。kotlin还支持一种简化版本的定义。格式如下:
fun name(arg1: type...) : returnType = fun body。这种格式下需要遵守以下条件:
a、函数体内部不能使用return语句,默认最后一句当作返回值
b、如果函数体内部有if的话,必须要有else,即使最后一句有返回值(这边留个疑问点?这么规定的原因是什么?理论上我最后一句有返回值就可以了)
c、如果有returnType的话,那么返回值类型必须要跟returnType类型一致,否则报错
d、如果函数体有用{}包含的话,不能规定返回值类型,因为此时返回的不是具体的类型
下面的具体的例子:
fun myAge() = 10 //正确,没有定义函数返回值,默认以最后一句作为返回值
fun myAge() : Int = 10 //正确,明确要求返回值是Int类型,实际返回也是Int类型
fun myAge() : String = 10 //错误,明确要求返回的是String 类型,实际返回的确是Int类型
fun myAge(name : String) = if(name == "Tom") "10" else 20 //正确,没有规定返回值,按照默认的最后一句返回,如果参数是Tom的话,则返回字符串10,如果不是Tom的话,返回数字20
fun myAge(name: String) = return 10 //错误,以=命名的情况下,不允许使用return语句,必须按照默认最后一句当作返回值
fun myAge(name: String) = {return 10} //错误,以=命名的情况下,不允许使用return语句,必须按照默认最后一句当作返回值
fun myAge(): Int = {10} //错误,加了{}的情况下返回的是kotlin.Int,而不是Int类型
3、函数的默认值:
kotlin的函数的定义允许使用默认值,具体的格式如下
fun name(arg1 type = xxx....) {body}
例如 fun myAge(name: String = "Tom") {}
如果有多个参数的默认值,正常情况下从有到左连续的都有默认值,如果没有的话,那再调用的时候就要指定参数
fun myPrint(name: String = "Tom", age : Int, score: Int = 100) {
println("name is $name, age is $age, score is $score")
}
fun main() {
myPrint(10) //错误,这个时候按照默认的第一个参数,要求是string,但是传递的是int
myPrint("Jimmy", 10) //正确,按照默认的顺序传递参数,第三个参数使用默认值
myPrint(10, "Jimmy") //错误,按照默认的顺序传递参数,类型不匹配
myPrint(age = 10) //正确,指定参数赋值
myPrint(age = 10, name = "Jimmy") //正确,指定参数名赋值,不使用默认的顺序
}
四、流程控制
在kotlin中,流程的控制主要用到了if、when、循环这几个
1、if:
if的使用于java中基本上类似,有一个特别注意的地方,就是在函数简化定义的时候,if后面必须要跟else,否则会报错
2、when:
when的使用有点类似与java的switch/case。主要区别点如下:
a、switch必须要带一个参数,而when带不带参数都行(其实不带参数的时候就是外面有一个作用域更大的参数可以直接使用)。
b、case使用的是:,而在when里面使用->。case和when里面都可以使用复杂操作
c、case只能单纯的判断相等,而when可以执行复杂的判断,例如判断数据类型等
d、如果是在简便的函数定义内部使用when,并且这个when是最后一个流程。那么必须要保证有结束分支(使用else或者在when外部有返回)
例子如下:
1、//正确,返回值是Unit
fun myAge(name: String) {
when {
name == "Tom" -> 111
}
}
2、//错误,没有else分支,导致数据类型缺失
fun myAge(name: String) = {
when {
name == "Tom" -> 111
}
}
3、//正确,返回的是kotlin.Any
fun myAge(name: String) = {
when {
name == "Tom" -> 111
else -> "222"
}
}
4、//正确,返回的是kotlin.String
fun myAge(name: String) = {
when {
name == "Tom" -> 111
}
"2222"
}
上面的第2个和第4个例子,大家可以对比一下,为什么第四个没有else也是正确的?因为这个时候when没有在最后一个流程,最后一个是"2222",我们上面说过,简便形式的函数定义是以最后一句当作返回值的,如果这个最后一句是when或者if的话,那就必须要有else
3、循环
在kotlin中循环通过两种方式,一个是while,一种是for-in。while跟java一样,这里不讲解。for-in的话跟java也是大同小异,只不过java中基本上用的是各种集合,kotlin可能区间用的更多。这边顺便讲解一下kotlin的区间的定义以及for-in的示例
//升序闭合区间使用..定义
val range = 0 ..10
//升序左闭右开区间使用until定义
val range2 = 0 until 10
//降序区间使用downTo定义
val range3 = 10 downTo 0
//for-in默认步长的使用方式
for(i in range) {.....}
//for-in 指定步长的使用方式
for(i in range step 2) {......}
五、类和对象
1、类定义:
kotlin中类定义跟java中类似,使用class关键字,其中成员变量的声明也是参照var和val的区别定义
成员函数的定义也跟普通的函数定义一样
2、对象创建
kotlin中的对象的创建与java不太一样,他直接摒弃了new关键字。例如
class Test
var t = Test();
3、类的继承
kotlin中类的继承主要使用:来继承,在:后面需要跟上父类的构造函数,跟上构造函数主要是为了后续实例化传参
另外有一个注意的点就是如果按照普通的class声明的方式的类是不可被继承的,因为那样被声明成了final的class。需要在class前面加上open关键字,这样才能被继承
4、构造函数
kotlin中类的构造函数分为主构造函数和次构造函数。
主构造函数,就是直接在类的声明的时候使用括号进行声明,并且在主构造函数里面可以添加init函数体进行参数的初始化处理,init不能带括号,因为他不是一个正常的函数
open class Person {
fun dump() {
println("this is person dump")
}
}
class Student(score:Int) : Person() {
init{
println("init score: $score") //正确,在构造函数里面的访问,正常
}
fun dumpS() {
println("this is student dump")
println("this is student dump $score") //错误,没有成员变量score,无法访问
}
}
次构造函数,通过constructor关键字来定义,如果在次构造函数里面想调用父类的构造函数的话,可以使用super,如果调用子类的其他构造函数的话,可以使用this,这里要注意的一个点是,如果定义了主函数,那么这个主函数必须要被调用到,其他的次构造函数需要间接或者直接的调用到主构造函数
fun myAge1(name: String) = {10}
fun myAge(name: String) = {
when {
name == "Tom" -> 111
}
"222"
}
fun myPrint(name : String = "Tom", age: Int, score: Int = 100) {
if (name == "Tom")
println("name is $name, age is $age, score: $score");
}
open class Person(name: String, age: Int) {
init {
println("person name : $name, age: $age");
}
fun dump() {
println("this is person dump")
}
}
class Student(score:Int) : Person("Tom",20) {
init{
println("init score: $score")
}
//正确,通过主构造函数再进行父类构造函数的初始化
constructor(score: Int, name: String, age: Int) : this(score) {
}
//错误,没有简介的调用主构造函数
//constructor(score: Int, name: String, age: Int): super(name ,age) {
}
fun dumpS() {
println("this is student dump")
}
}
fun main() {
//println("Hello, World: " + myAge("Tom"))
//myPrint(age = 10, name = "Tom")
//var p = Person()
//p.dump()
var s = Student(100,"Jimmy", 30)
s.dumpS()
}
5、成员变量:
我们声明一个类的成员变量可以在类的里面使用var或者val声明,但是也可以在构造函数里面使用val或者var来声明,这样可以在传递参数的时候默认声明这个类包含这个成员变量,同时还可以用权限修饰符来修饰
class Student(name: String) {
init{ //正确,在init里面可以访问构造函数的参数
println("init student name $name")
}
fun printFun() {
println("on print function name $name") //错误,没有name这个成员变量
}
}
class Student(var name: String) {
init{ //正确,在init里面可以访问构造函数的参数
println("init student name $name")
}
fun printFun() {
println("on print function name $name") //正确,此时name这个成员变量是public的
}
}
var student = Student("Tom")
println("name is " + student.name) //正确,public成员可以访问
class Student(private var name: String){}
var student = Student("Tom")
println("student name " + student.name) //错误,private成员不可直接访问
6、单例类
在java中单例模式通常是在类的内部定义一个static成员,在kotlin中,直接使用object关键字替代class来声明一个单例类,其他成员变量和成员函数的定义和普通的类一样,这边主要记录一下注意点:
object单例类不能像普通的类一样进行实例化,只能通过类名调用
由于不能进行实例化,所以声明的时候不允许有构造函数,也就是声明的时候不允许带()
可以通过普通类的声明方式来声明一些成员变量,然后进行一些其他操作
疑问点:这边有遇到一个问题,就是如果单例类声明了一个变量name,那么我如果声明一个setName的函数的话,编译器就会提示报错,具体原因待研究
六、接口
kotlin中的接口与java中一样,都是使用interface进行定义,然后再实现这个接口的时候用“,”定义,然后在类里面实现对应的接口函数就可以了,另外就是接口包含已经实现的函数,这个与jdk1.8的功能一样
注意:如果一个类只是单纯的实现一个接口的话,用":"实现,如果继承了一个父类,那么后面用","跟着表示实现这个接口。如果没有继承任何父类直接用,实现接口的话,会出现编译异常
open class Person(name: String, age: Int) {
var name = ""
var age = 10
init {
println("person name : $name, age: $age");
this.name = name
this.age = age
}
fun dump() {
println("this is person dump")
}
}
interface study {
fun learnChinese()
fun learEnglish()
fun defaultMethod() {
println("this is defaultMethod")
}
}
class Student : Person, study{
init{
println("init score")
}
constructor(score: Int, name: String, age: Int) : super(name,age) {
}
fun dumpS() {
println("this is student dump")
}
override fun learnChinese() {
println("$name is learning chinese")
}
override fun learEnglish() {
println("$name is learning english")
}
}
七、权限修饰符
kotlin中的权限修饰符与java中的差别不大,主要还是public、protect、private三个为主,唯一的区别是kotlin中增加了internal关键字来代替java中的default,另外i就是kotlin中的成员默认是public的,而java中默认是private的
八、数据类
在java中经常会出现一种场景,定义一个类,只用来保存数据,然后我们需要写一堆的get和set。在kotlin中有一种简便的使用方法,这就是数据类,数据类的定义就是在class前面使用data关键字进行声明
data class userBean(val arg1: Int, val arg2: String)
var user = userBean(111, "222")
println("user arg1: "+ user.component1())
九、lambda的使用
所谓的lambda,其实可以理解成c里面的函数指针,java里面的函数参数的传递只允许传递基本类型和对象类型,不允许传递函数指针,而kotlin是支持这个特性的,也就是lambda的使用。lambda的格式如下:
{参数名1:参数类型,参数名2:参数类型->函数体}
其实这种语法有点像groovy里面的闭包,
其实说白了就是可以指定某种函数,然后在另外一个函数内部可以调用这个函数。整体的使用可以参照groovy的闭包使用的方式
上面只是说明了lambda的声明,但是在实际场景中最终还是要应用到函数中,那么如果lambda作为函数参数的时候又要如何声明呢?我们知道普通的参数是name:type的形式,lambda也可以参照这个格式,只不过他的type比较特殊,需要按照函数的类型来定义,需要有形参的类型和个数以及返回值,格式如下
fun testFun(callbackName: (arg1: type, arg2: type) -> returnType) {}
其中callbackName跟普通的函数形参名字一样,arg1、arg2、type也是跟普通的函数一样,returnType就是定义普通的函数返回值类型,如果没有返回的话就指定为Unit
调用的话就直接将整个lambda表达式当作参数传进去,这里可以选择直接传递或者先声明赋值给某个变量后再传递。下面将lambda的使用总结一下:
a、lambda参数可以直接传递也可以声明成一个变量后再传递
b、如果lambda表达式是最后一个参数的话,可以把这个表达式放到()外面,但是这种情况只适用于直接传递表达式的情况
c、如果整个函数只有一个lambda参数,并且这个参数是直接整个表达式传递进去的,那么可以将这个()去除
d、如果lambda表达式只有一个参数的话,那么在函数体内部可以用it代替这个参数
fun testPrint(callback : (name: String) -> Unit) {
callback("testName")
}
fun testPrint2(name: String, callback:(name:String)->Unit) {
callback(name)
}
//正确,直接当作参数传递
testPrint({name:String -> println("testcallback name: $name)})
//正确,先声明参数后再传递
var testLambda = {name:String -> println("testcallback name: $name")}
testPrint(testLambda)
//正确,当lambda参数是最后一个的时候,可以选择放到括号外面
testPrint2("Tom"){name:String -> println("testcallback name: $name")}
//错误,只有在使用表达式当作参数的时候才能移到外面
testPrint2("Tom")testLambda
//正确,当labmda是唯一的一个参数的时候,可以省略括号
testPrint{name:String -> println("testcallback name: $name")}
十、函数式API的使用
所谓的函数式API其实也就是标准方式的函数调用,至于为什么要单独拎出来讲是因为使用条件限制比较特殊。当kotlin调用java接口的时候,如果这个接口是单抽象方法接口的时候,那么就可以使用函数式API,所谓的单抽象的方法接口就是只有一个方法的接口。
下面用一个创建多线程的例子来说明函数式API的使用演变
1、对比一下正常的java和kotlin的使用。由于kotlin没有new关键字,所以要使用内部类的话就必须要使用object关键字
//java调用
new Thread(new Runnable() {
@override
public void run() {
System.out.println("test");
}
}).start();
//kotlin调用
Thread(object:Runnable{
override fun run() {
println("test")
}
}).start()
2、因为Runnable是单抽象接口,所以可以将runnable对象的声明和实例化去除,代码可以简化成如下两种
Thread({
println("test")
}).start()
Thread(Runnable {
println("test")
}).start()
3、又因为只有runnable一个参数,根据上面lambda的使用所讲的,可以将()去除,所以又演变了以下方式
Thread {
println("test")
}.start()
以上就是函数式API的使用,记住只适合于单抽象接口的方法
十一、let函数的使用
这个let函数可以理解成原子操作。使用这个let函数之后,不会因为线程调度等引发多线程的问题。格式如下:
obj.let {
it->... //其中it就是这个obj。这个也是lambda的使用
}
举个例子
//由于线程调度的关系,在main函数中执行test1的时候,传进来的参数是不为空的
//但是当判断结束后切换到另外一个线程B。这个线程会将student置为空,这个时候再切换回主线程
//就会出现崩溃问题
fun test1(student : Student) {
if (student != null) {
student.xxx();
}
}
//这种情况下,如果main函数调用test2的时候就不会出现上述的那个问题,因为他在判断非空的时候会立即执行let函数里面的东西
fun test2(student : Student) {
student?.let{
it.xxx()
}
}
var student = Student()
fun main() {
test1(student);
Thread {student = null}.start()
}
其中上面例子里面的?表示的是不为空,这个比较简单,就不做详细解释了
十二、内嵌表达式
所谓的内嵌表达,就是在一个表达式中,可以引用其他的变量,跟shell语言引用变量的方式类似,shell中也是通过$来引用变量,这边也是
//正常情况下的打印
println("name is : " + name + ", age is: " + age)
//使用内嵌表达式的打印,其实就是引用变量
println("name is $name, age is $age")
//可以使用复杂的表达式来判断
//println("name is ${if(age > 10) "Tom" else "Jim"} age is $age")
以上就是关于kotlin语言学习的一些总结了