用到是IDEA免费版本。新建一个Kotlin项目。
取个名字。
最后新建一个.kt文件,就可以开始使用kotlin了。
选择创建File。之后就会生成一个.kt的文件。
HelloWorld程序:
fun main() {
var str:String="Hello"
println(str)
}
手动编译运行:
执行下面的代码会生成一个叫HelloKt.class的字节码文件,非常奇怪的是,命令行并没有提供什么参数来修改这个生成的文件名字。
kotlinc Hello.kt
通过kotlin命令行来执行字节码文件:
kotlin HelloKt.class
也可以通过下面的代码生成jar文件,然后通过java -jar来运行
kotlinc Hello.kt -d hello.jar
java -jar hello.jar
下面的代码声明了一个String类型的变量,通过var关键字来声明变量,和java相比,这个写法好像非常的麻烦,多了var关键字还多了冒号,而且写的顺序也不一样,类型声明在后面,据说这个语法是从TypeScript那里学过来的,对TypeScript(JS)不熟。既然设计成这样,一定是有他的好处的。实际上,类型可以省略,kotlin会自动帮我们推导出来。
fun main() {
var str:String="Hello"
println(str)
}
省略后的写法:
也就是不管什么类型,都可以声明为var类型。对于开发者来说,还是节省了一点时间的,虽然牺牲了一点效率,因为倒推类型肯定需要额外的内部操作。
fun main() {
var str="Hello"
println(str)
}
下面的代码声明了一个String类型的变量,但是编译器会给一个警告,说"Hello"是一个只读变量,可以用val声明。注意是最后一个字母是L而不是R,var和val是两个关键字。var可以用来声明一般的变量,val用来声明只读变量,类似于java的final关键字。
var str="Hello"
用val声明:
val str="Hello"
注意这个只读并不等于常量,常量是用另一个关键字const来修饰的。
常量只能声明在方法外部,在方法内部是不能声明常量的。
const val MAX=1024
fun main() {
println(MAX)
}
在kotlin中,const相对于java的static final。我们可以通过双击shitf,输入kotlin bytecode查看字节码信息。
public final static I MAX = 1024
我们知道Java有引用类型和基本数据类型这两种类型。引用类型放在堆里面,一般是java对象。而kotlin只有一种类型,也就是引用类型,但是在编译的时候,还是会转化为基本类型。
val age=10
在字节码里面的LOCALVARIABLE I代表int类型。也就是会转化为基本数据类型。字节码内容了解一下就行。
LOCALVARIABLE age I L1 L6 0
和Java的用法一样,就不写了。
字面意思就是范围表达式,用到的关键字是in。下面这个例子很好的说明了使用的in关键字的用法。不光可以指定数字,还可以指定数组或者集合。
val score=66
if (score in 0..60){
println("bad")
}else if (score in 61..80){
println("not bad")
}else{
println("good")
}
相对于Java的switch表达式,但更加简洁,推荐将所有的if else表达式都转化成when的形式,when表达式在kotlin中是非常常用的。
下面的代码对比Java的switch语句少了case和default关键字看起来简洁了非常多。
而且可以返回多种数据类型,这里返回了String类型的“男”,“女”,和int类型的-1。
返回类似是Any类型,这个有点类似Java的Object类型。->实际上是一个lambda表达式。如果只有单行可以直接返回结果,不用写return;
val gender=3;
val result:Any=when(gender){
1->"男"
2->"女"
else-> -1
}
println(result)
如果lambda表达式存在多行,那么需要加一个大括号{}。
val gender=3;
val result=when(gender){
1->"男"
2->"女"
else-> {
println("未知")
println("程序退出")
}
}
println(result)
程序最后输出如下,kotlin.Unit类似于Java的void,因为println并没有返回值。
未知
程序退出
kotlin.Unit
String模板就是String的拼接。在Java中,我们用++拼接字符串,这个实际上是非常麻烦的。很多语言都支持用 符 号 拼 接 内 容 , 用 符号拼接内容,用 符号拼接内容,用{}拼接表达式。
用$拼接内容:
val animal="dog"
val color="yellow"
println("The $animal's color is $color")
输出内容:
The dog's color is yellow
用${}拼接表达式:
val animal="dog"
val color="yellow"
val age=10
println("The $animal's is $color and ${if(age>5) "old" else "young"}")
输出内容:
The dog's is yellow and old
kotlin的函数头和Java还是差别非常大的,priavate表示权限,默认是oublic,fun是函数声明关键字,括号里面是参数列表,最后的:Boolean表示返回类型。
可以看到参数列表和返回值的顺序和Java是相反的,这实际上是更加符合逻辑的,先传输入再确定输出类型,也就是输入输出。只是Java用习惯了,也就习以为常了。
private fun doSomething(i:Int ,s:String):String{
return "resultCode";
}
函数调用
直接调用就可以,不需要创建对象。
fun main() {
val flag = doSomething(5, "abc")
println(flag)
}
参数还可以给默认值,只是Java所没有的,Java有固定的默认值。kotlin支持指定默认值。
private fun doSomething2(a:Int=2,s:String="null"):String{
return "$a $s";
}
fun main() {
//没有传任何参数
val result = doSomething2()
println(result)
}
输出结果:
2 null
具名的意思就是在传入参数的时候可以指定参数的名字,这样的目的是增加可读性,因为光传参数,不知道这个参数是干嘛用的,可能传错位置,可读性也很差。
例如下面的代码,代码一的位置,写了参数的名字是age,和name,这样就不会搞错参数位置了,可读性非常强。但是,IDEA是支持参数类型提示的,可以直接显示参数类型,这相对于是IDEA已经实现的功能,可能就是为了弥补Java没有这个功能而设计的。
fun main() {
//代码一
var flag=doSomething(age=5,name="abc")
}
private fun doSomething(age:Int,name:String):Boolean{
return true;
}
在Java中没有返回类型就返回void类型,但在泛型的时候,这个概念就有点矛盾。kotlin用kotlin.Unit这个类型表示没有返回类型。在kotlin中,如果没有返回类型,那么函数的返回类型可以不用写任何类型,也不用写void,kotlin根本就没有void这个关键字。
fun main() {
println(testUnit(5,"abc"))
}
private fun testUnit(age:Int,name:String) {
}
输出:
kotlin.Unit
在函数名上面加一个反引号,kotlin支持这种写法,这样做的主要目的是为了和Java的交互性。Java和Kotlin是可以无缝调用的,但因为Java和Kotlin的关键字不同,可能一些方法名在Java是合法的,但到了Kotlin就变成非法,这时候就可以加反引号解决。
例如下面的Java代码,声明了一个is方法,这在Java里面是合法的,但如果在kotlin里面要调用这个方法就不行了,因为is在Kotlin里面是一个关键字。
public class Data {
public static void is(){
}
}
这时候在函数名上加一个反引号就可以解决这个问题。这样is就不被当作是关键字而可以在kotlin里面使用了。
//在kotlin中调用
Data.`is`()
有趣的是,加了反引号之后,方法名甚至可以写空格例如下面的代码。
private fun `first we calculate add`(a: Int,b:Int):Int{
return a+b;
}
private fun `second we calculate sub`(a: Int,b:Int):Int{
return a-b;
}
fun main() {
val add= `first we calculate add`(1,2)
val sub = `second we calculate sub`(1, 2)
println("$add $sub")
}
用自然语言编程不再是梦。当然这个应该是用不太上,主要还是为了解决和Java的交换问题,写类似注释的方法名还是交给注释比较好。
我们举String的count方法为例,count方法有两个重载,第一个无参函数非常好理解,第二个重载声明如下,要求我们传入一个lambda表达式,这个表达式传入char,返回boolean(判断条件),最后整个函数返回一个int,也就是我们指定的char字符有多少个。
public inline fun CharSequence.count(predicate: (Char) -> Boolean): Int
var count="Kotlining".count()
var countI = "Kotlining".count({letter -> letter == 'i'} )
println(count)
println(countI)
输出
9
2
这个和C语言的函数指针是非常像似的,但java是移除了这些特性的。
我们看下面的代码,第一次看这个代码可能非常的奇怪,看着像lambda表达式,又不完全是,其实这里的关键就是理解()->String的含义。这个语法是lambda表达式,但这里的真正含义是匿名内部类类型,并且这个匿名内部类返回String类型。顺理成章的,我们定义的变量result的类型也就变成了String类型。注意是一个类型声明,而不是lambda表达式实体,lambda表达式的实体是{}里面的内容。
var result:()->String={
val animal="dog"
"This is $animal"
}
看下和lambda表达式的对比:
下面是java的lambda表达式.一个非常明显的区别就是kotlin的匿名内部类没有return,而且在里面也是不可以写return的,这是因为他默认返回类型是表达式的最后一行。为了实现这个功能,就必须显示的声明类型,因为可能返回一个类对象。
()->{
val animal="dog"
return "This is $animal"
}
当然也是可以带参数的。在()里面声明参数类型,在{}开头可以写具体名字。写法还是非常飘逸的。
val result2:(String,Int)->String={
name,age->
val animal="dog"
"This is $animal , name is $name , age is $age"
}
println(result2("tom",5))
输出:
This is dog , name is tom , age is 5
还有一种类型推断的写法,如下,参数我们还是要指定类型的,但返回值可以推断出来。感觉这种写法更简洁一点,前面的写法更标准一些。
val result4 = { name: String, age: Int ->
val animal = "dog"
"This is $animal , name is $name , age is $age"
}
接着上小节,如果参数只有一个的时候,我们可以不写具体的参数名字,kotlin默认为我们提供了一个it关键字,可以直接使用这个关键字当参数变量名称。
val result3:(String)->String={
val animal="dog"
"This is a $animal , name is $it."
}
println(result3("tom"))
输出:
This is a dog , name is tom.
下面这个函数的功能是显示学生信息,包括学生的姓名和身份证,第二个参数是一个函数类型,没错可以将一个函数做为参数传进来。这个函数可以合成学生的身份证,身份证由邮政编码,生日,编号构成。
private fun showStudentInfo(name: String, getId: (Int,Int,Int) -> String): String {
//必须用大括号
return "$name's id is ${getId(300027,20001212,1001)}"
}
var getId = { code: Int, birth: Int, num: Int ->
"$code$birth$num"
}
println(showStudentInfo("Tom", getId))
输出:
Tom's id is 300027200012121001
其实这个写法非常的奇怪。声明一个函数然后又去调用,那干嘛不直接写一个函数呢?其实,上面的写法是不合理的,一般不会这么写,问题就出在var getId这个变量上,我们写了这个变量来引用函数类型,这是没有必要的,因为我们用函数类型的目的就是为了写匿名函数,取名字完全是多此一举。可以简化写成下面的样子。
println(showStudentInfo("Tom", { code, birth, num ->
"$code$birth$num"
}))
这样看起来就是非常简洁了。而且,kotlin有规定,如果只有一个函数类型,或者函数类型是最后一个,()可以省略。
函数类型是最后一个的情况:
println(showStudentInfo("Tom") { code, birth, num ->
"$code$birth$num"
})
只有一个函数类型做参数的时候
println(showStudentInfo { code, birth, num ->
"$code$birth$num"
})
这种写法一定要掌握,因为这个写法在kotlin里面是非常常见的。
这种写法一定要掌握,因为这个写法在kotlin里面是非常常见的。
这种写法一定要掌握,因为这个写法在kotlin里面是非常常见的。
内联函数的主要用途就是为匿名函数提高性能。在调用下面的有函数类型做为参数的方法时,会使用到匿名内部类。第二个参数要求我们传入匿名内部类,对于编译器来说,匿名内部类的创建和普通类是一样的,但既然是匿名的,效率应该要更高写,如果也是和普通类一样创建,效率就比较低了。内联函数就是为了解决这个性能问题,那么是怎么解决的呢?答案是通过宏替换,如果你学过C语言,你就知道C语言的宏替换,内联函数也是一样的,通过宏替换,我们直接把匿名内部类的代码直接复制到调用的函数里面,这样就不需要创建类了,不需要创建类当然性能就提高了。
private fun showStudentInfo(name: String, getId: (Int,Int,Int) -> String): String {
return xxx
}
使用方法就是直接添加一个inline关键字就可以了。
private inline fun showStudentInfo(name: String, getId: (Int,Int,Int) -> String): String {
return xxx
}
在调用含有函数类型的方法时,不光可以传入匿名内部类,也可以传通常的方法,这时候就需要用到函数引用。
例如下面的代码,foo方法的参数列表和showStudentInfo里面的getId函数类型的参数列表是一样的,这个时候可以把foo直接传给showStudentInfo。使用方法就是在::加普通函数名称。
private fun showStudentInfo( name:String, getId: (Int,Int,Int) -> String): String {
//必须用大括号
return "$name's id is ${getId(300027,20001212,1001)}"
}
private fun foo( a:Int,b:Int,c:Int ): String {
return "$a$b$c"
}
println(showStudentInfo("Tom", ::foo))
输出:
id is 300027200012121001
简单来说,就是一个函数返回另一个函数。什么?这有什么意义?如果有多个函数进行嵌套调用,那么就会形成函数之间的层层包含关系。这样的好处就是子函数可以共享父函数的变量,这种写法在脚本语言中是非常常见的,例如JavaScript。这样做的原因是脚本语言存在一个很大的问题,就是即使在不同的文件里面定义相同的变量名也会报错。因为脚本语言并没有像java这样有package和class的概念。kotlin也可以做为脚本语言。我们只能举个简单的例子来说明一下她的形式,理解的比较深还是需要大量的实践的。
看下面的代码,返回值是一个函数类型,传入一个String返回String。
private fun foo(a:Int):(String)->String{
return {
"$it"
}
}
在main函数中执行,返回函数类型的变量bar,可以调用,传入"abc"返回"abc"
var bar = foo(1)
println(bar.invoke("abc"))
输出:
abc