Kotlin基础语法(笔记)
一、类型声明
变量声明示例:
val a : String = "i am a person"
小结:Kotlin中声明的类型名通常跟在变量名的后面,并且Kotlin中的所有类型都是大写。
类型推导示例:
val string = "i am kotlin"
val int = 123
val long = 123L
val float = 12.34f
val double = 12.3
val double2 = 10.1e6
小结:Kotlin中大写的才是类型,通过等号给变量赋值可以直接进行类型推导。
函数返回值类型示例
fun sum(x: Int,y: Int): Int {return x + y} //返回类型为Int
小结:sum函数返回x + y
为Int类型,如果去掉返回值类型,函数会默认返回Unit类型,然而实际返回类型是Int,此时就会报错。
表达式函数体示例:
fun sum(x: Int,y: Int) = x + y
小结:以上的写法称为表达式函数体
,普通的函数声明称为代码块函数体
。
fun foo(n: Int) = if (n == 0) 1 else n * foo(n - 1)
//该函数会报错,说明: 在一些诸如递归的复杂情况下,即使使用表达式定义函数,也必须显式声明类型
// 正确写法 fun foo(n: Int): Int = if (n == 0) 1 else n * foo(n - 1)
总结:
- 作为函数参数时,必须显式声明类型
- 作为非表达式函数,除了函数返回Unit,其它情况必须显式声明类型
- 如果时递归函数,必须显式声明类型
二、val和var
var:代表变量
val:代表常量,即通过val定义的变量具有Java中final关键字的效果,也就是引用不可变
val 示例:
val x = intArrayOf(1,2,3)
x = intArrayOf(2,3,4) //报错,val声明,引用不可变
//虽然x引用不可变,但是它的值可以改变
x[0] = 2
小结:val声明的变量是只读变量,它的引用不可更改,但并不代表其引用对象也不可更改。
三、高阶函数和Lambda
1、函数作为参数
实例:Shaw因为旅游喜欢上了地理,作为一名程序员,他设计了一个App用来对国家数据进行操作。Shaw偏好欧洲的国家,于是它就设计了一个程序来获取欧洲的所有国家。
data class Country( //数据类,小括号括起来
val name: String,
val continiet: String,
val population: Int)
class CountryAPP {
fun filterCountries(countries: List<Country>): List<Country> {
val res = mutableListOf<Country>()
for (c in countries) {
if (c.continiet == "EU") { //获取欧洲国家
res.add(c)
}
}
return res
}
}
后来,Shaw对非洲也产生了兴趣,于是又改进了原来的设计,支持通过集体的州来筛选国家
fun filterCountries(countries: List<Country>, continiet: String): List<Country> {
val res = mutableListOf<Country>()
for (c in countries) {
if(c.continiet == continiet) { //通过continiet来筛选州
res.add(c)
}
}
return res
}
然而,Shaw的地理知识越来越丰富了,他想对国家的特点做一些研究,比如筛选具有一定人口规模的国家,因此他又将程序重新设计了
fun filterCountries(countries: List<Country>, continiet: String, population: Int): List<Country> {
val res = mutableListOf<Country>()
for(c in countries) {
if (c.population == population && c.continiet == continiet) { //通过popualtion和continiet来筛选州
res.add(c)
}
}
return res
}
如果按照现有的设计继续开发,更多的筛选条件会作为方法的参数,然后业务逻辑会更难理清。这时,Shaw了解了Kotlin是支持高阶函数的,理论上是可以将筛选的逻辑当坐参数传入的,于是他立马写了一个测试类
class CountryTest{
fun isBigEuropeanCountry(country:Country): Boolean {
return country.continiet == "EU" && country.population > 10000
}
}
调用isBigEuropeanCountry()方法就可以实现筛选功能,然而要怎么样才能将这个方法变成filterCountries 的一个参数呢?
我们先了解一下Kotlin中函数的类型:
栗子:
(Int) -> Unit //函数的参数是Int类型,返回值是Unit类似于Java中的Void
注意:
- 通过->来组织参数类型和返回值类型,左边是参数类型,右边是返回值类型
- 必须用一个括号来包裹参数类型
- 返回值类型即使是Unit,也必须显式声明
举例:
() -> Unit //没有参数的函数类型
(Int, String) -> Unit //多个参数用,间隔
(code: Int,message: String) -> Unit //为参数指定名称
(code: Int,message: String?) -> Unit // ? 表示该类型可空(null)
(Int) -> ((Int) -> Unit) //支持返回类型也是函数,可以简化为 (Int) -> Int -> Unit
在学习了Kotlin的函数类型后,Shaw重新定义了filterCountries 方法的参数声明
fun filterCountries(countries: List<Country>,test: (Country) -> Boolean): List<Country> {
//test的类型是一个函数,该函数的参数为Country,返回值为Boolean类型
val res = mutableListOf<Country>()
for (c in countries) {
if (test(c)) { //直接调用test来筛选
res.add(c)
}
}
return res
}
2、匿名函数
Shaw发现每增加一个需求都需要专门写一个筛选方法,这样不太好,于是决定使用匿名函数来优化。
countryApp.filterCountries(countries,fun(country: Country): Boolean {
return country.continient == "EU" && country.population > 10000
})
3、Lamdba表达式
采用匿名函数优化之后确实比原来更好,省去了每次都要创建筛选方法的步骤。然而Shaw发现,编译器会推到类型,也就是说匿名函数中的fun(country: Country)是没有必要的,而且return关键字也可以省略。如此,Shaw又对它的程序进行了改动。
countryApp.filterCountries(countries,{
country ->
country.continient == "EU" && country.population > 10000
})
Lambda的语法:
val sum: (Int,Int) -> Int = (x: Int,y: Int -> x + y)
//由于支持类型推导,可以简化为
val sum = {x: Int,y: Int -> x + y}
或者
val sum: (Int,Int) -> Int = {x,y -> x + y}
总结:
-
一个Lambda表达式必须通过{}包裹
-
如果Lambda声明了参数部分的类型,且返回值类型支持类型推导,那么Lambda变量就可以省略类型声明、
-
如果Lambda表达式返回的不是Unit,那么默认最后一行表达式的值类型就是返回值类型,如
val foo = {x: Int -> val y = x + 1 y //返回值 }
四、表达式编程
表达式可以是一个值、常量、变量、操作符、函数,或他们之间的组合,编程语言对其进行解释和计算,以求产生另一个值。
//通俗的讲,表达式就是可以返回值得语句
1 //单纯的字面量表达式,值为1
-1 //增加前缀操作符,值为-1
1 + 1 //加法操作符,返回2
listOf(1,2,3) //列表表达式
"kotlin".length() //值为6
{x: Int -> x + 1} //Lambda表达式,类型为(Int) -> Int
fun(x: Int) {println(x)} //匿名函数表达式,类型为(Int) -> Unit
if(x > 1) x else 1 //if-else表达式,类型为Int
Kotlin中的表达式都是有返回值类型的,即使返回值为空,也是一种类型,即Unit类型
Unit类型:之所以不能说Java中的函数调用皆是表达式,是因为存在特例void
。Java中如果函数没有返回值,那么他就需要用void来修饰。由于函数没有值和类型信息,所以就不能算作一个表达式。而Kotlin引入了Unit
,Unit其实与Int一样,都是一种类型,然而它不代表任何信息,用面向对象的术语来表示就是一个单例,它的实例只有一个,可写为()。
复合表达式:由于Kotlin中的每个表达式都有值,因此更容易进行组合构成一个复合表达式。
val res: Int? = try{ //try表达式,返回值类型由try和catch部分决定,finally不受影响
if(result.sucess) { //if-else表达式
jsonDecode(result.response)
}else null
}catch(e: JsonDecodeException) {
null
}
//这段程序表示获取一个HTTP响应结果,对其进行json解码,最终赋值给res的过程
Kotlin中的?: 在Kotlin中?
用来表示一种类型的可空性,可以使用?:
来指定类型为空时的值
val res: Int? = null //类型可空
println(res ?: 0) //res为空时返回0
1、枚举类
示例:
enum calss Day{
MON,
TUE,
WEN,
THU,
FRI,
SAT,
SUN
}
枚举类可以拥有构造参数,以及定义额外的属性和方法
enum class DayWeek(val day: Int) {
MON(1),
TUE(2),
WEN(3),
THU(4),
FRI(5),
SAT(6),
SUN(7); //如果定义了额外的属性或方法,必须用;分割
fun getDayNumber(): Int{
return day
}
}
2、when表达式
Shaw给他的一天做了计划,如下:
- 周六打篮球
- 周日钓鱼
- 周五晚上约会
- 平常如果天晴就去图书馆看书,不然就在寝室学习
fun schedule(sunny: Boolean,day: Day) = when(day) {
Day.SAT -> basketball()
Day.SUN -> fishing()
Day.FRI -> appointment()
else -> when{
sunny -> library()
else -> study()
}
}
when表达式的返回类型是所有分支相同的返回类型,或公共的父类型。
当分支 -> 左边的部分返回Boolean时,when表达式可以省略()
when{
sunny -> library()
else -> study()
}
3、for循环和范围表达式
示例
for(i in 1..10) println(i)
//等价于
for(i in 1..10) {
println(i)
}
..
在Kotin中是范围表达式,..
操作符是按升序排列的闭区间范围即[i,j]
,也就是说该类型必须实现java.lang.Comparable
接口
//String类实现了Comparable接口,因此可以使用..来表示区间范围
//字符串的大小根据首字母在字母表中的排序进行比较,如果首字母相同则从左到右获取下一个字母
"abc".."XYZ"
另外,对整数for循环时可以定义迭代的步长
for(i in 1..10 step 2) println(i)
//for循环还支持倒序
for(i in 10 downTo 1 step 2) println(i) //使用downTo来表示倒叙
Kotlin还有一个函数unitl
来实现一个半开区间
for(i in 1 until 10) println(i)
//123456789 [1,10)
in关键字:用来检查一个元素是不是一个区间或者集合中的成员
"a" in listOf("b","c") //false
"a" !in listOf("b","c") //true 加上!就是相反的结果
//可以结合..范围表达式来表示更多含义
"kot" in "abc".."XYZ" //等价于 "kot" >= "abc" && "kot" <= "XYZ"
此外,还可以用withIndex方法,提供一个键值元组
for((index,value) in array.withIndex()) {
println("the element at $index is $value")
}
4、中缀表达式
以上的in、step、downTo、until,他们可以不通过点号而是通过中缀表达式的方法被调用,从而让语法变得更加简洁,中缀表达式形如A 中缀方法 B
。
中缀函数的定义:
-
中缀函数必须是某个类型的扩展函数或者成员方法
-
中缀函数只能有一个参数
-
虽然Kotlin的函数参数支持默认值,但是中缀函数的参数不能有默认值
-
该参数不能是可变参数
Kotlin中通过vararg关键字来定义可变参数,类似于java中的… java中的可变参数必须是最后一个参数,而Kotlin中没有限制
fun printLetters(vararg letters: String, count: Int): Unit { print("$count letters are") for (letter in letters) print(letter) } //使用方法 //一、直接传入字符串数组 printLetters("a","b","c",count = 3) //二、使用*传入已有的变量 val letters = arrayOf("a","b","c") printLetters(*letters,count = 3)
to函数:to函数也是一个中缀表达式,它的返回值是Pair的键值对结构,因此经常和map一起使用
mapOf{
1 to "one"
2 to "two"
3 to "three"
}
自定义中缀表达式:
class Person{
infix fun called(name: String) {
println("my nam is ${name}")
}
}
fun main(args: Array<String>) {
val p = Person()
p called "Shaw"
}
//my name is Shaw
//Kotlin仍然支持使用普通方法的语法调用中缀函数,如
p.called("Shaw")
五、字符串定义及操作
示例
val str = "hello world!"
str.length //12
str.subString(0,5) //hello
str + " hello korlin!" //hello world! hello kotlin!
str.replace("world","kotlin") //hello kotlin!
//由于String是一个字符序列,因此可以遍历
for(i in str.toUpperCase()) {print(i)}
//还可以访问字符序列的成员
str[0] //h
str.first() //h
str.last() //!
str[str.length - 1] //!
1、原生字符串
示例
val htmlText =""""
<html>
<body>
<p>hello kotlin!</p>
</body>
</html>
"""
上面演示的一段代码就是原生字符串,在Kotlin中使用"""
来定义原生字符串。用这种三个引号定义的字符串,最终的打印结果与代码中呈现的格式一致,而不会解释转义字符如\n,以及Unicode的转义字符如\uXXXXX。
2、字符串模板
示例
//拼接字符串
fun message(name: String, lang: String) = "Hi ${name},welcome to ${lang}"
Kotlin的字符串模板支持将变量植入字符串,使用${name}
的格式来操作。除此之外,我们还可以将表达式植入字符串
"kotlin has ${if("kotlin".length > 0) "kotlin".length else "no"} letters"
3、字符串判等
Kotlin中的判等有两种类型:
结构相等:通过==
来判断两个对象的内容是否相等
引用相等:通过===
来判断两个对象的引用是否一样,相反的操作是!==
示例
var a = "Java"
var b = "Java"
var c = "Kotlin"
var d = "Kot"
var e = "lin"
var f = d + e
a == b //true
a === b //true
c == f //true
c === f //false