文章目录
- 1 函数和变量
- 1.1 基本程序
- 1.2 函数
- 1.3 变量
- 1.3.1 变量的类型推导
- 1.3.2 可变变量和不可变量
- 1.3.3 变量使用规则
- 1.4 字符串模板
- 2 类和属性
- 2.1 属性
- 2.2 自定义访问器
- 2.3 目录和包
- 2.3.1 同包访问
- 2.3.2 不同包导入
- 2.3.3 包名类名定义规则
- 3 枚举和“when”
- 3.1 声明枚举类
- 3.2 when结构使用
- 3.3 when结构包含对象
- 3.4 使用不带参数的“when”
- 3.5 合并类型检查和转换
- 3.6 用“when”代替“if”
- 3.7 代码块作为“when”的分支
- 4 循环语句
- 4.1 while循环
- 4.2 for循环
- 4.3 迭代map
- 4.4 使用“in”检查集合和区间成员
- 5 异常
- 5.1 抛异常和捕获异常
- try作为表达式
1 函数和变量
1.1 基本程序
fun main(args: Array<String>) {
println("Hello World!")
}
程序从“Hello World”打印开始,从这个简单的程序中,包含Kotlin语言的以下特性:
- 函数使用关键字fun声明
- 主函数依然为main函数
- Kotlin中没有Java中数组类型,数组就是Array类
- 函数可以定义在文件的最外层,可以不用放到类中
- 参数类型在参数的后面
- Java中的很多语句Kotlin做了更简易的封装,比如println
- 语句结尾不需要分号
1.2 函数
fun max(a: Int, b: Int): Int {
return if (a > b) a else b
}
函数仍然由函数名,参数列表和返回值类型组成。不同的是函数使用fun
定义,参数类型和返回值类型在后面
- Kotlin不支持三元运算符(?😃
- Kotlin中很多逻辑运算是表达式,可以直接返回值
上面的可以简化为:
fun max(a: Int, b: Int): Int = if (a > b) a else b
这样的函数拥有表达式体,上面有大括号的函数拥有代码块体。
还可以进一步简化为:
fun max(a: Int, b: Int) = if (a > b) a else b
对于拥有表达式体的函数,可以省略其返回值类型,因为Kotlin具有类型推导能力。
1.3 变量
Java中声明变量从类型开始,而Kotlin中很多时候不需要声明变量的类型,因为Kotlin有强大的类型推导功能,能够根据上下文推导出具体的类型。
1.3.1 变量的类型推导
val a = 10
val b = 12.3
val c = "test"
a被推导为Int类型
b被推导为Double类型
c被推导为String类型
Kotlin中没有Java中的基本数据类型,所有类型都是类对象
如果要显示指定数据的类型,可以在变量后面加变量的类型
val d: Byte = 10
1.3.2 可变变量和不可变量
Kotlin中通常有两个变量修饰符
val(value)
:不能在初始化后再次赋值,对应于Java中使用final修饰的变量var(variable)
:可变变量,对应于Java中的非final修饰符
默认情况下,应该首先使用val
修饰符,当变量确实需要改变时,才替换成var
修饰符
1.3.3 变量使用规则
val类型赋值之后不可改变,否则编译不过
如果变量不在定义的时候初始化,则需要确定其类型,因为Kotlin虽然可以类型推导,但还是静态类型的语言,每个变量都有确定的类型。不在定义的时候确定其类型,编译器无法推导,无法编译通过
val b: Int
if (a > 5) {
b = 1
} else {
b = 0
}
val变量只能赋值一次,如果编译器能够保证只执行一条初始化语句,则可以编译通过
val list = listOf(1, 3, 4)
list.addLast(5)
val变量定义的引用不可变,但是其引用对象的内容可变。list指向一个List对象,不能改变其引用直系指向,但是该List对象可以改变其中存储的元素
对于var可变类型变量,不能改变其数据类型,当a定义的时候,类型推导为Int类型,此时该变量的类型就已经被确定,不能改变其类型,否则编译失败
1.4 字符串模板
开发中很多时候涉及到字符串拼接,Java中拼接如下
"Hello, " + name + "!"
在Kotlin中可以使用更简便的写法,就是字符串模板
"Hello, $name!"
字符串模板不止可以引用简单变量,还可以引用复杂的变量
val names = listOf("World", "Kotlin")
println("Hello, ${names[0]}!")
这里可以使用大括号引用字符串数组的第一个元素
println("Hello, ${if (args.size > 0) args[0] else "World"}!")
大括号中还可以嵌套表达式
2 类和属性
Kotlin也是面向对象的语言,是JVM语言,在Java虚拟机上运行,所以也是有类和属性一说
Java中有一个JavaBean
的概念,表达的是一种Java中规范类的写法
public class Person {
private final String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
通常这些构造函数和get
方法都是比较重复的代码,与具体的业务逻辑无关
可以将上面的代码拷贝到kotlin文件中,能够自动将Java代码转换成kotlin代码
class Person(val name: String)
Kotlin中只需要一行代码就能够表示上面的一段Java代码
public类修饰符消失了,因为在Kotlin中public是默认的可见性
2.1 属性
类的概念是把数据和处理数据的代码封装成一个单一的实体
类包含属性和行为,在Java中字段和访问器(setter
和getter
)的组合常常被叫做属性
Kotlin中,属性是头等的语言特性,完全替代了字段和访问器方法
在类中声明一个属性和声明一个变量一样:使用val和var关键字,当声明属性的时候,就默认声明了对应的访问器。声明成val的属性是只读的,默认会提供对应的get
方法,而声明成var
的属性是可变的,默认会提供get
和set
方法
class Person(val name: String, var isMarried: Boolean)
这里声明了两个属性,一个是只读属性,只提供getter
方法,另一个是可变属性,提供getter
和setter
方法,使用Java访问Kotlin声明的类
public class Test {
public static void main(String[] args) {
Person person = new Person("zhangsan", false);
person.setMarried(true);
System.out.println(person.getName());
System.out.println(person.isMarried());
}
}
输出
zhangsan
true
Kotlin声明的属性,是一个类的字段都是私有的类,在Java代码中可以访问Kotlin声明的类,可以看到,声明为val
的属性,只有getter
方法,声明为var
的属性,有getter
和setter
方法。
如果属性声明为XXX
,默认提供的是setXXX
和getXXX
方法,其中有一个例外是isXXX
开头的属性,这种属性,默认提供的是setXXX
和isXXX
方法,需要注意。
上面是Java中访问Kotlin中声明的类,下面代码为Kotlin中访问
fun main(args: Array<String>) {
val person = Person("zhangsan", false)
person.isMarried = true //1
println(person.name) //2
println(person.isMarried)
}
上面的Kotlin代码中,看起来像是直接访问的定义的属性。但是上面说过,Kotlin中声明的属性变量,默认是private
的,Kotlin中也没有改变类中private
变量无法从类外访问的特性。Kotlin中,不需要显示调用getter
和setter
方法,而是像访问public
属性一样调用访问器。注释1处默认调用的是person
对象的isMarried
类变量的setter
方法,而注释2处则是调用的getter
方法。
2.2 自定义访问器
上面属性默认的访问器都是编译器生成的,而如果要实现自己的逻辑,就需要自己自定义访问器
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() {
return height == width
}
}
Rectangle类定义了一个属性isSquare
,该属性用val修饰,只提供getter
方法,而这个getter
方法没有使用默认的getter
,而是使用自定义的getter
方法。自定义访问器就是对属性创建一个get函数或者set函数来自定义getter
方法和setter
方法。
2.3 目录和包
kotlin文件中以package
语句开头,文件中定义的所有声明(类、函数、属性)都在该包中,如果其他文件中定义了相同的包,则他们属于同一包中,可以直接使用,如果包不同,则需要导入,导入使用import
关键字。
2.3.1 同包访问
TestOne
package com.test
class TestOne(val num: Number)
fun TestOnePrint() {
println("TestOnePrint")
}
TestTwo
package com.test
class TestTwo(){
}
fun main() {
val testOne = TestOne(3)
TestOnePrint()
}
TestTwo
文件中的main
函数可以直接访问TestOne
文件中。
与Java不同的是Java中包路径与项目的实际路径对应,一个点是一个文件夹,而在Kotlin中,包名可以不与文件路径相对应,可以将该文件放到项目中的任意位置,但是他们仍处于相同的包下,且不会导致编译错误。
2.3.2 不同包导入
Kotlin中import可以导入任何声明
导入顶层函数
import com.test.TestOnePrint
导入类
import com.test.TestOne
导入包中所有元素
import com.test.*
2.3.3 包名类名定义规则
类名:
首先来看Java,Java中一个类文件可以包含多个类定义,但是只能有一个public定义的,且必须和类文件相同。
而对于Kotlin来说,则没有这么严格的显示,同样的,一个文件中可以定义多个类,类文件文件名可以任意选择。
包名:
对于包名来说,虽然Kotlin没有限制,但是建议参考Java中的实现方式,能够使代码结构更加清晰
3 枚举和“when”
3.1 声明枚举类
enum class Color {
RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}
kotlin中创建枚举类比Java多一个关键字,需要enum class
一起使用,enum
是一个软关键字,只有放在class
前面才有意义。
3.2 when结构使用
fun printColor(color: Color) {
when (color) {
Color.RED -> println("red")
Color.BLUE -> println("blue")
Color.VIOLET -> println("violet")
else -> println("no such color")
}
}
kotlin中没有switch...case
,kotlin中多重判断使用when结构。
fun getWarmth(color: Color) = when (color) {
Color.RED, Color.ORANGE, Color.YELLOW -> "warm"
Color.GREEN -> "neutral"
Color.BLUE, Color.INDIGO, Color.VIOLET -> "cold"
}
when
结构也可以合并处理
需要注意的是when
结构使用枚举类的时候,需要对所有变量进行定义,或者定义else
,否则无法编译通过
3.3 when结构包含对象
when
结构比switch
更加强大,switch
只能使用常量(枚举常量、字符串常量或者数字字面量),而when
结构可以使用任何类型对象
fun max(c1: Color, c2: Color) = when (setOf(c1, c2)) {
setOf(Color.RED, Color.YELLOW) -> Color.ORANGE
setOf(Color.YELLOW, Color.BLUE) -> Color.GREEN
setOf(Color.BLUE, Color.VIOLET) -> Color.INDIGO
else -> throw Exception("Dirty color")
}
这里使用when
结构,包含的是一个set
对象
3.4 使用不带参数的“when”
when结构可以不带参数
fun mixOptimized(c1: Color, c2: Color) {
when {
(c1 == Color.RED && c2 == Color.YELLOW) ||
(c1 == Color.YELLOW && c2 == Color.RED) ->
Color.ORANGE
(c1 == Color.BLUE && c2 == Color.YELLOW) ||
(c1 == Color.YELLOW && c2 == Color.BLUE) ->
Color.GREEN
(c1 == Color.BLUE && c2 == Color.VIOLET) ||
(c1 == Color.VIOLET && c2 == Color.BLUE) ->
Color.INDIGO
else -> throw Exception("Dirty color")
}
}
如果没有提供when
的参数,分支条件就是任意的布尔表达式
3.5 合并类型检查和转换
Java中,用于判断对象是否是一个类的实例,需要使用instanceof
关键字。通常传入的参数是父类类型时,需要转换成子类类型,还需要使用强转。
interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr
fun eval(e: Expr): Int {
if (e is Num) {
val n = e as Num
return n.value
}
if (e is Sum) {
return eval(e.left) + eval(e.right)
}
throw IllegalArgumentException("Unknown expression")
}
上面是类似于Java中的写法,is
关键字用于判断对象是否是Num
类对象,相当于instanceof
。as关键字用于强转。
但是Kotlin中合并了类型检查和转换
val n = e as Num
这一行是不需要的,因为is
关键字在检查的时候,如果返回true,就自动的将e
转换成了Num
对象
3.6 用“when”代替“if”
上面的例子可以使用when
结构进行改造
fun eval(e: Expr): Int =
when (e) {
is Num -> e.value
is Sum -> eval(e.left) + eval(e.right)
else -> throw IllegalArgumentException("Unknown expression")
}
直接返回when
运算的值,这里的when
结构中,用于判断对象类型。
3.7 代码块作为“when”的分支
fun evalWithLogging(e: Expr): Int =
when (e) {
is Num -> {
println("num : ${e.value}")
e.value
}
is Sum -> {
val left = evalWithLogging(e.left)
val right = evalWithLogging(e.right)
println("sum: $left + $right")
left + right
}
else -> throw IllegalArgumentException("Unknown expression")
}
when
结构的分支中可以是代码块。这里由于when
直接充当返回值,所以各个分支的最后一行就是返回值
4 循环语句
Kotlin支持两种循环:while
循环和for
循环
4.1 while循环
语法
while (condition) {
...
}
do {
...
} while (condition)
4.2 for循环
Kotlin中的for
循环和Java中不太一样,使用的是区间和数列
区间:
表示两个范围内的数,使用..
运算符
val oneToTen = 1..10
这个区间是一个闭合的区间,两边临界都能取到
for (i in 0..10) {
println(i)
}
使用for
循环遍历0到10,i不需要提前定义,这里使用的时候就直接定义了
for (i in 0..10 step 2) {
println(i)
}
可以使用step
跳过其中的一些值
for (i in 10 downTo 0 step 2) {
println(i)
}
可以使用downTo
做倒序的遍历
for (i in 0 until 10) {
println(i)
}
for (i in 0 ..<10) {
println(i)
}
可以使用until
关键字或者..<
来做一个左闭右开的区间
4.3 迭代map
循环常用的就是用来遍历集合
val binaryReps = TreeMap<Char, String>()
for (c in 'A'..'F') {
val binary = Integer.toBinaryString(c.code)
binaryReps[c] = binary
}
for ((letter, binary) in binaryReps) {
println("$letter = $binary")
}
map
添加元素可以使用使用赋值这种简写方式,遍历的时候可以将key-value展开到两个变量中
val list = arrayListOf("10", "12", "2343")
for ((index, element) in list.withIndex()) {
println("$index = $element")
}
遍历的时候也可以不用定义索引值,而是通过withIndex获取
4.4 使用“in”检查集合和区间成员
使用in
检查元素是否在其中或者!in
检查元素是否不在其中
fun isLetter(c: Char) = c in 'a' .. 'z' || c in 'A' .. 'Z'
fun inNotDigit(c: Char) = c !in '0' .. '9'
这两个判断也适用于when表达式
fun recognize(c: Char) = when (c) {
in '0'..'9' -> "It's a digit"
in 'a'..'z', in 'A'..'Z' -> "It's a letter"
else -> "I don't know"
}
5 异常
5.1 抛异常和捕获异常
Kotlin中的异常和Java中相似,一个函数可以正常结束,也可以抛出异常。异常可以处理,也可以向上抛出。
抛出异常:
if (percentage !in 0 .. 100) {
throw IllegalArgumentException("Invalid percentage")
}
捕获异常
fun readNumber(reader: BufferedReader): Int? {
try {
val line = reader.readLine()
return Integer.parseInt(line)
} catch (e: NumberFormatException) {
return null
} finally {
reader.close()
}
}
同样使用try...catch...finally
结构
try作为表达式
Kotlin中try
可以作为表达式
fun readNumber(reader: BufferedReader): Int? =
try {
val line = reader.readLine()
Integer.parseInt(line)
} catch (e: NumberFormatException) {
null
} finally {
reader.close()
}
try是一个表达式,可以直接返回,运算中的最后一行就是返回值