标签 : Kotlin 基础
本文摘要:
- 函数、变量、类、枚举和属性
- 流程控制
- “聪明转换”
- 抛出和处理异常
本文主要介绍Kotlin的基础部分,会先讲述如何声明函数、变量、类、枚举和属性。函数就是Java中的方法,属性就是Java中的成员变量,其余部分和Java都差不多;流程控制就是for、while、if、when等内容,Kotlin中与Java还是有部分不同的地方;“聪明转换”的本质就是将类型确认和类型转换融合到了一起;异常和Java中很相似。废话不多说,直接开始正文。
Kotlin基础和Java有不少相似与不同,这节主要介绍函数和变量。Kotlin致力于让开发者忽略类型声明,并且鼓励我们是用常量而不是变量。
fun main(args: Array<String>){
println("Hello, world!")
}
关键字:fun用于声明一个函数。
args:是参数名,后面的Array表示args的类型是String数组。这与Java中的顺序相反。
一般程序中有比较大小的函数,这里就以max为例,讲解:
fun max(a: Int, b: Int): Int{ //:Int是return typ
return if(a > b) a else b
}
因为if可以用表达式形式,函数返回类型可以根据表达式推导,可以如下简化:
fun max(a: Int, b: Int) = if(a > b) a else b
Kotlin在很多地方可以通过类型推导type inference
进行类型分析。
Kotlin中变量如下:
val name = "Kotlin"
val age: Int = 7
可以看到Kotlin中变量可以指明或者不指明类型,因为Kotlin本身能推导出类型。
如果一个变量没有直接初始化,就必须显式提供类型:
val name: String //显式指明
name = "Kotlin"
Kotlin中两种关键字:
val
来自value: 不变量,一旦初始化后就不能改变值,对应于Java中final
变量。
var
来自variable:变量,值能够随时改变。
建议使用时,最好所有变量都使用val
关键字,只有在需要的时候使用var
。使用变量var
就有可能产生副作用。
val
变量的初始化也可以根据条件进行选择:
val language = "Kotlin"
val message = if(language == "Kotlin") "Sucess" else "Failed"
注意:即使
val
引用不能被改变,但是其指向的对象却是可以改变的:
val language = arrayListOf("Java")
language.add("Kotlin")
language初始化为包含“Java”的数组,后面改变了val
指向的对象。
var
运行变量改变其数值,但是类型却是固定的。
举一个简单例子:
val name = "Kotlin"
println("I'm $name") //字符串模板
这种字符串模板相比Java中的方式显得更加高效,字符串模板是静态确定的,如果访问的变量无效会导致编译失败。
$
字符时,需要通过转义字符\$
来实现"I'm ${args[0]}"
——这样会先获得args[0]的值,然后通过$在字符串中显示。复杂表达式会比想象的更加强大:
println("I'm ${if (name == "Java") "Java" else "Not Java!"}") //如果是Java,显示Java,否则显示Not Java!
和Java一样,Kotlin也有类和属性(成员变量)。
声明一个Person类:
class Person (val name: String, val age: Int)
val man = Person("小王", 10) //Person对象(不需要new)
println("Name:${man.name} Age:${man.age}")
可以看到仅仅是保存数据的类,相比于Java非常简洁。
类的目的是将数据和代码封装到一个实体中。在Java中,数据存储在fields字段中——通常是私有的,你需要通过getter和setter方法去访问数据。在Kotin中,将字段和访问器组成属性properties
,类中的属性和声明变量的方式相同:使用val
和var
,前者只读,后者可变。
class Person (
val name: String, //只读
val age: Int, //只读
var isMarried: Boolean //可变
)
在声明属性时,会默认给val
属性添加getter访问器,给var
会同时添加getter和setter。
如果在Java中使用该类,可通过如下代码(Kotlin和Java的100%兼容):
//Java中调用Kotlin类
person.getName(); //name属性,自动添加getName方法
person.isMarried(); //Kotlin中is开头的属性,在Java中不会有前缀。
假设我们有一个长方形类,当你想通过isSquare
属性来判断该长方形是否是正方形时,不需要额外增加什么内容,可以给该属性增加一个getter就能自己获取数值,代码如下:
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() { //isSquare的getter,自动获取该值
return height == width
}
}
isSquare
属性不需要字段(field)来存储数值,而仅仅通过提供的getter访问器来获得——该数值被使用时,才会计算得到该值。
Kotlin中属性和Java中成员变量(field)的区别,个人认为在于Kotlin的属性是可以通过访问器动态获得,而不需要一个field区域去存储。
问题1:使用无参数的函数和使用具有自定义getter的属性哪个更好?
两者性能和实现上没有多少区别,关键在于易读性,如果是类的一种特征,最好声明为属性。
Kotlin和Java一样都采用package包的概念。区别在于Kotlin中导入类和方法是没有区别的,都是直接通过name来import类或者方法。
Java代码中文件名需要和内部类名一致,而且一个文件只能有一个类。Kotlin中就没有这种限制:文件中可以有多个类,并且文件名随意取。
即使Kotlin中没有严格的目录结构上的限制,但是采用Java中目录安排的方法也是比较好的习惯。当然,在你的多个类代码短且关联比较大,也应该毫不犹豫的放在一个文件内。
本节介绍when
的结构,能替代Java中的switch但是功能更强强大并且使用广泛。此外会介绍Kotlin中的枚举类。
enum class Color{
RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}
Kotlin中用enum class
来声明枚举类,其中enum
是软关键字,意味着跟class组合时代表“枚举类”。但是在其他地方可以将属性声明为enum,而class关键字是不允许被使用的。
Kotlin中枚举不仅仅是数值的列表:可以在枚举类中声明属性和方法:
enum class Color(val r: Int, val g: Int, val b: Int){
RED(255, 0, 0), GREEN(0, 255, 0), BLUE(0, 0, 255); //属性(结尾分号)
fun rgb() = (r * 256 + g) * 256 + b;
}
枚举类的属性和方法和一般类没有任何区别,但是在属性结尾处使用了分号,这是Kotlin语法中唯一使分号的地方。
Kotlin中when可以替代Java中的switch,而且有更强大的功能。when代码块可以作为表达式,将结果最为when代码块的值,或者就是单纯的流程控制类似于if-else。先看一个实例:
fun getColorString(color: Color) =
when(color){
Color.RED -> "Red"
Color.GREEN -> "Green"
Color.BLUE -> "Blue"
}
这里将when代码块作为表达式,将判断后得到的字符串作为函数getColorString的返回值。此外Kotlin中when语句不需要break语句。
“when”语句也可以多个条件放在一个分支中(使用逗号分隔):
fun getColorString(color: Color) =
when(color){
Color.RED, Color.GREEN -> "warm"
Color.BLUE -> "cold"
}
这里是通过全名使用枚举类中的常量,也可以引用Color类的常量,用于简化操作:
import Color.*
fun getColorString(color: Color) =
when(color){
RED, GREEN -> "warm"
BLUE -> "cold"
}
import Color.*
引用了Color中所有常量,就可以直接使用。如果是import Color
这仅仅是引用Color类,是不允许直接使用里面的常量。
Kotlin中“when”不仅可以用于常量,也可以用于任何对象:
fun mix(c1: Color, c2: Color) =
when(setOf(c1, c2)){
setOf(RED, GREEN) -> "红绿"
else -> "其他颜色组合"
}
setOf()用于创建set
——set是Kotlin中的集合,用于包含数据却不关心顺序。上例中,就是判断颜色组合,是否为红绿。无论RED和GREEN的顺序如何,都能判断出红绿的结果。
“when”也可以不使用参数,相比如上例,可以不创建对象而提高效率,但是可读性会有损失:
fun mixEfficient(c1: Color, c2: Color) =
when{
(c1 == RED && c2 == GREEN) || (c1 == GREEN && c2 == RED) -> "红绿"
else -> "其他颜色组合"
}
对于无参数“when”语句,分支条件必须为任何布尔值的表达式。
假设通过下列代码计算1+3+5的值:
interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr
fun eval(e: Expr): Int{
when{
e is Num -> return e.value
e is Sum -> return eval(e.left) + eval(e.right)
}
throw IllegalArgumentException("Unkown expression")
}
这类似于组合模式,函数eval的参数e可以是数字Num也可以是数值之和Sum,而Sum的参数也是如此。在函数eval内部,通过e is Num
来判断参数e的类型,然而并没有进行类型转换而直接使用了所属类的属性return e.value
, 这里就是Kotlin编译器完成的smart cast,自动在类型判断后进行了类型转换。当然也可以使用显式转换,代码如下:
//...
e is Num -> {
val n = e as Num //将e转换为类型Num
return e.value //使用Num的属性
}
//...
Kotlin中因为if表达式和when表达式都可以得到一个值,因此eval
表达式可以分别用if和when重构。
if:
fun evalIf(e: Expr): Int =
if(e is Num){
e.value
}else if(e is Sum){
eval(e.left) + eval(e.right)
}else{
throw IllegalArgumentException("Unkown expression")
}
when:
fun evalWhen(e: Expr): Int =
when(e){
is Num -> e.value
is Sum -> eval(e.left) + eval(e.right)
else -> throw IllegalArgumentException("Unkown expression")
}
可以看出if的功能,when都能做到并且更加简洁。
if
和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("Unkown expression")
}
结果(符合预期):
num: 1
num: 3
sum: 1 + 3
num: 5
sum: 4 + 5
eval=9
“代码块的最后一行表达式就是代码块的结果”在任何代码块中都生效。在函数的代码块中是无法生效的,因为函数必须通过return语句去返回值。
Kotlin中while和do-while循环和Java相比没有新特性,就不多介绍了。
Kotlin中没有传统Java中的for循环。Kotlin中使用ranges
范围的概念,并通过..
操作符来使用:
val oneToTen = 1..10 //声明范围变量
Kotlin中范围是左闭右闭的,因此两个值都在范围之内。
如果可以遍历范围中的所有值,该范围被称为progression
(累进)。
从1到100遍历一个范围:
for(i in 1..100){
//...
}
从100到1遍历,并且只遍历偶数(步数为2):
for(i in 100 downTo 1 step 2){
println("for: $i")
}
遍历一个半闭范围(不包括尾数)——使用until:
for(i in 1 until 100){
println("for: $i")
}
我们这里实现一个效果:有一个map保存了字符‘A’到‘F’的二进制,大致代码如下:
val binaryReps = TreeMap()
for(c in 'A'..'F'){
val binary = Integer.toBinaryString(c.toInt()) //获得字符对应的数字的二进制,并且转为字符串
binaryReps[c] = binary //Kotlin中集合存储的一种形式,等效于put和get
}
for((letter, binary) in binaryReps){
println("$letter = $binary")
}
上面的第一个循环可以发现,在Kotlin中..范围可以用于数字和字符。
第二个循环使用了for循环中获取集合元素的特殊方法,从而获得了Char和String
通过index获得集合的元素:
val list = arrayListOf("10", "11", "1001")
for ((index, element) in list.withIndex()){
println("$index: $element")
}
判断是否在一个范围内:
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z'
不在一个范围内:
fun isNotDigit(c: Char) = c !in '0'..'9'
in
和!in
也可以用于when
表达式。
范围不仅限于字符,如果任何类实现了java.lang.Comparable接口
就可以使用范围:
能够通过in判断“Kotlin”是否在范围之内:
println("Kotlin" in "Java".."Scala")
字符串的比较是按照字符顺序依次比较的。
此外,in
判断可以作用于组合:
println("Kotlin" in setOf("Java", "Scala"))
Kotlin中异常和Java等大多数语言一样,但是不需要使用new
关键字创建异常实例。
不像Java,Kotlin中throw
可以作为表达式的一部分:
val number = 35
val percentage =
if (number in 0..100)
number
else
throw IllegalArgumentException(
"A percentage value must be between 0 and 100: $number") //表达式
try,catch,finally与Java中没有区别,但是throws是不一样的,Kotlin中的函数声明后不需要显式写throws IOException
等确定的异常,事实证明Java中大量无意义的rethrow或者忽略异常并不能保护你免于可能发生的错误。
fun readNumber(reader: BufferedReader): Int? {
try {
val line = reader.readLine() //可能产生异常
return Integer.parseInt(line)
} catch (e: NumberFormatException) {
return null
} finally {
reader.close() //关闭流
}
}
类似于if和when,try可以作为表达式:
fun readNumber(reader: BufferedReader) {
val number = try {
Integer.parseInt(reader.readLine())
} catch (e: NumberFormatException) {
return
}
println(number)
}
fun
用于声明函数。val
和var
分别声明只读和可改变的变量。$
或者使用${}
包含一个表达式用于将其值转为字符串if
可以作为表达式,用于返回值when
相比于java中switch有更强大的能力smart cast
会自动在类型判断后进行类型转换while,do-while
和Java中非常相似,for
在基础上提供更方便的功能..
提供了范围,可以用于for
循环,in
或者!in
操作符等处