Kotlin教学(一)基础篇

Kotlin教学(一)基础篇

标签 : Kotlin 基础


本文摘要:

  • 函数、变量、类、枚举和属性
  • 流程控制
  • “聪明转换”
  • 抛出和处理异常

  • Kotlin教学一基础篇
    • 1-基本元素函数和变量
      • 1-函数
      • 2-变量
        • 不变量和变量
      • 3-字符串模板
    • 2-类和属性
      • 1-成员属性
      • 2-自定义访问器
      • 3-Kotlin源码布局目录和包
    • 3-处理选择枚举和when
      • 1-枚举类
      • 2-使用when处理枚举类
      • 3-用于任意对象的when
      • 4-无参数的when
      • 5-Smart casts组合类型确认和类型转换
      • 6-重构用when替换if
      • 7-if和when的代码块分支
    • 4-遍历while和for循环
      • 1-While
      • 2-遍历数字范围和累进
      • 3-遍历maps
      • 4-使用in进行判断
    • 5-Kotlin中的异常
      • 1-trycatchfinally
      • 2-try作为表达式
    • 6-总结

本文主要介绍Kotlin的基础部分,会先讲述如何声明函数、变量、类、枚举和属性。函数就是Java中的方法,属性就是Java中的成员变量,其余部分和Java都差不多;流程控制就是for、while、if、when等内容,Kotlin中与Java还是有部分不同的地方;“聪明转换”的本质就是将类型确认和类型转换融合到了一起;异常和Java中很相似。废话不多说,直接开始正文。

1-基本元素:函数和变量

Kotlin基础和Java有不少相似与不同,这节主要介绍函数和变量。Kotlin致力于让开发者忽略类型声明,并且鼓励我们是用常量而不是变量。

1-函数

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进行类型分析。

2-变量

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运行变量改变其数值,但是类型却是固定的。

3-字符串模板

举一个简单例子:

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!

2-类和属性

和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非常简洁。

1-成员属性

类的目的是将数据和代码封装到一个实体中。在Java中,数据存储在fields字段中——通常是私有的,你需要通过getter和setter方法去访问数据。在Kotin中,将字段和访问器组成属性properties,类中的属性和声明变量的方式相同:使用valvar,前者只读,后者可变。

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中不会有前缀。

2-自定义访问器

假设我们有一个长方形类,当你想通过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的属性哪个更好?

两者性能和实现上没有多少区别,关键在于易读性,如果是类的一种特征,最好声明为属性。

3-Kotlin源码布局:目录和包

Kotlin和Java一样都采用package包的概念。区别在于Kotlin中导入类和方法是没有区别的,都是直接通过name来import类或者方法。

Java代码中文件名需要和内部类名一致,而且一个文件只能有一个类。Kotlin中就没有这种限制:文件中可以有多个类,并且文件名随意取。

即使Kotlin中没有严格的目录结构上的限制,但是采用Java中目录安排的方法也是比较好的习惯。当然,在你的多个类代码短且关联比较大,也应该毫不犹豫的放在一个文件内。

3-处理选择:枚举和‘when’

本节介绍when的结构,能替代Java中的switch但是功能更强强大并且使用广泛。此外会介绍Kotlin中的枚举类。

1-枚举类

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语法中唯一使分号的地方。

2-使用“when”处理枚举类

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类,是不允许直接使用里面的常量。

3-用于任意对象的“when”

Kotlin中“when”不仅可以用于常量,也可以用于任何对象:

fun mix(c1: Color, c2: Color) =
        when(setOf(c1, c2)){
            setOf(RED, GREEN) -> "红绿"
            else -> "其他颜色组合"
        }

setOf()用于创建set——set是Kotlin中的集合,用于包含数据却不关心顺序。上例中,就是判断颜色组合,是否为红绿。无论RED和GREEN的顺序如何,都能判断出红绿的结果。

4-无参数的“when”

“when”也可以不使用参数,相比如上例,可以不创建对象而提高效率,但是可读性会有损失:

fun mixEfficient(c1: Color, c2: Color) =
        when{
            (c1 == RED && c2 == GREEN) || (c1 == GREEN && c2 == RED) -> "红绿"
            else -> "其他颜色组合"
        }

对于无参数“when”语句,分支条件必须为任何布尔值的表达式。

5-Smart casts:组合类型确认和类型转换

假设通过下列代码计算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的属性
        }
//...

6-重构:用“when”替换“if”

Kotlin中因为if表达式和when表达式都可以得到一个值,因此eval表达式可以分别用ifwhen重构。

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都能做到并且更加简洁。

7-“if”和“when”的代码块分支

ifwhen分支都可以使用代码块,并且将代码块的最后一行代码作为代码块的返回值:

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语句去返回值。

4-遍历:“while”和“for”循环

1-While

Kotlin中while和do-while循环和Java相比没有新特性,就不多介绍了。

2-遍历数字:范围和累进

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")
    }

3-遍历maps

我们这里实现一个效果:有一个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")
}

4-使用‘in’进行判断

判断是否在一个范围内:

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"))

5-Kotlin中的异常

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") //表达式

1-try,catch,finally

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() //关闭流
    }
}

2-‘try’作为表达式

类似于if和when,try可以作为表达式:

fun readNumber(reader: BufferedReader) {

    val number = try {
        Integer.parseInt(reader.readLine())
    } catch (e: NumberFormatException) {
        return
    }

    println(number)
}

6-总结

  • fun用于声明函数。valvar分别声明只读和可改变的变量。
  • 字符串模板帮助你避免无意义的字符串拼接。在变量名前缀一个$或者使用${}包含一个表达式用于将其值转为字符串
  • 数值相关的类,可以在Kotlin中有非常简约的表达形式
  • if可以作为表达式,用于返回值
  • when相比于java中switch有更强大的能力
  • smart cast会自动在类型判断后进行类型转换
  • while,do-while和Java中非常相似,for在基础上提供更方便的功能
  • ..提供了范围,可以用于for循环,in或者!in操作符等处
  • Kotlin中异常处理和Java非常相似,特别是Kotlin中不需要声明需要被方法抛出的异常

你可能感兴趣的:(Kotlin,kotlin)