我爱编程

Kotlin 快速入门

2018-05-20  本文已影响26人  灯不利多

这篇文章是给有过 Java 或其他面向对象编程语言开发经验的朋友看的。省略了对很多面向对象编程的解释,希望大家能用最少的时间了解 Kotlin 的基础知识。你可以用 Intellij 、 Android Studio 或 Try Kotlin 运行文中的代码。

本文内容:

变量与常量

Kotlin 有两个声明变量和常量的数据类型,分别是 val 和 var。

var 变量能直接赋值,也能先声明后赋值:

 // 无类型声明
 var age = 12

 // 显式声明变量类型
 var name1: String = "老王"

 // 重新赋值
 var name2 = "kotlin"

val 常量相当于是 Java 中的 final 变量,必须在创建时初始化,且不能重新赋值:

val name = "kotlin"

// 编译时异常
name = "changed kotlin" 

类型推断

在 Kotlin 中,声明变量时不需要声明具体类型,比如上面代码中的 age 变量。这种机制称为类型推断(type inference)。

基本类型

Kotlin 没有 int 和 Integer 等 Java 中的基本类型和包装类。但在 Kotlin 中有和 Java 包装类相似的装箱类(boxed type)。

数值类型

下面是 Kotlin 中内置的数值装箱类

类型 字节
Long 8
Int 4
Short 2
Byte 1
Double 8
Float 4

创建数值常量:

val int = 123
val long = 123456L  // 长整型结尾为 L
val double = 12.34
val float = 12.34F  // 浮点型结尾为 F
val hexadecimal = 0xAB  // 十六进制开头为 0x 
val binary = 0b010101  // 二进制开头为 0b

Kotlin 中数值类型的相互转换可以通过类型转换函数:

val int = 123
val long = int.toLong()

val float = 3.0F
val double = float.toDouble()

Kotlin 的类型转换函数:

下面是 Kotlin 的按位运算操作,按位取反在 Kotlin 是一元运算符,因此用 inv() 函数来进行运算:

// 左移
val leftShift = 1 shl 2 

// 右移
val rightShift = 1 shr 2  

// 无符号右移
val unsignedRightShift = 1 ushr 2  

// 按位与
val and = 1 and 0x00001111 

// 按位或
val or = 1 or 0x00001111  

// 按位异或
val xor = 1 xor 0x00001111  

// 按位取反
val inv1 = 1.inv()  

// 编译时异常,要用 inv()
val inv2 = ~1

字符类型

Kotlin 中的字符型变量不能用作数值,因为 Kotlin 中对该运算符的定义:

// 编译时异常
var a = 'a' == 10

// 编译通过
var b = 'a'.toInt() == 10

字符串类型

在 Kotlin 中可以用三个双引号 “”“ 将字符串括起来,这种字符串也叫原始字符串(raw string)。当字符串要换行的时候,不需要再用加号:

val rawString = """
strings that span many lines 
strings that span many lines 
"""
println(rawString)

数组类型

在 Kotlin 中可以通过 arrayOf() 函数来创建数组,还可以通过数组长度和字面函数来创建数组,函数计算值将会赋值到每一个元素中:

val array = arrayOf(1, 2, 3)

// 0、1、4、9、16、...、81
val perfectSquares = Array(10, {k -> k * k})

Kotlin 中的数组不是特殊数据类型,因此它有 get 、set 方法,但是一般不推荐使用:

val array = arrayOf(1, 2, 3)
var element1 = array[0]
var element2 = array.get(1)
array[2] = 5

因为装箱类是对象,开销比基本类型要大。当你对性能要求高,可以使用 Kotlin 提供的几种数组类型,它们会被转换成基本类型 :

导包重命名

当导入的类同名时,可以用 as 运算符对导入的包进行重命名:

import com.packt.myproject.Foo
import com.packt.otherproject.Foo as Foo2

fun doubleFoo() {
  val foo1 = Foo()
  val foo2 = Foo2()
}

字符串模板

Koltin 的字符串模板(template)允许你在字符串中插入多个变量,而不需要用加号拼接它们,使用字符串模板只需要在变量前面加美元符号($):

val name1 = "Sam"
val name2 = "Alex"
val str = "$name1, $name2"

// Sam, Alex
println(str)

如果插入的变量需要进行运算,比如访问某个对象的属性,则需要用花括号 { } 将它括起来:

val name = "Sam"
val str = "${name.length}"

// 3
println(str)

值域

Kotlin 中的值域(range)是由区间定义的。任何可比较的数据类型(如 Int、Long、Char)都能用于创建值域,创建值域可以用两点 .. 或 until 运算符:

val aToZ = "a".."z"
val oneToNine = 1..9

// until 适用于遍历数组和列表等集合时
// 10...19
val zeroToNine =  0 until 10
for(i in zeroToNine) println(i)

用 in 运算符判断某个值是否在值域内:

val aToZ = "a".."z"
val isTrue = "c" in aToZ

val oneToNine = 1..9
val isFalse = 11 in oneToNine

值域从该值往下和从该值往上分别是 downTo() 和 rangeTo() :

// 0...100
val countingDown = 100.downTo(0)

// 10...20
val rangeTo = 10.rangeTo(20)

step() 函数可以创建不同步长的值域:

val oneToTwenty = 1..20

// 1、3、5、...、19
val oddNumbers = oneToTwenty.step(2)

用 reversed() 函数将值域的顺序颠倒:

// 100、98、96、...、2
val down2 = (2..100).step(2).reversed()

循环

Kotlin 的 for 循环能够遍历任何实现了 Iterator 接口的类中的元素,如数组、List、Set、Map :

var array = listOf(1, 2, 3)
for (i in array) println(i)

val list = listOf(2, 3, 4)
for (k in list) println(k)

var map = mapOf("a" to 1, "b" to 2)

// key: a, value: 1
// key: b, value: 2 
for((key, value) in map) 
  println("key: $key, value: $value")

在 Kotlin 的 String 类中,Kotlin 也为它提供了一个 Iterator 扩展函数,因此 String 也能被遍历:

val string = "print my characters"
for (char in string) println(char)

Kotlin 的数组有一个 indices 扩展函数,它可以用于遍历整个数组的下标:

val array = arrayOf("a", "b", "c")

// 0 1 2
for (i in array.indices) 
  println(i)

异常处理

在 Kotlin 中异常都是非强制处理的(unchecked):

    fun error() {
        throw FileNotFoundException()
    }

error()

类的实例化

在 Kotlin 中实例化一个类不需要 new :

val date = BigDecimal(100)

引用等价与结构等价

引用等价指的是两个对象是否为同一个引用,也就是两个对象是否在同一块内存中,结构等价指的是对象结构是否相同,比如 List 的长度以及每个元素是否相同:

var a = listOf(1, 2)
var b = listOf(1, 2)

// false, 非引用等价
println(a === b)  

// true,结构等价
println(a == b)   

this 表达式

当 Kotlin 的 this 语句在扩展函数或字面函数(Function Literals)中时,this 指的是该扩展函数或字面函数。可以用 @ 加标签来修饰 this 指向的对象:

class A {  

  inner class B {
    
    var foo = 100.foo()
    
    fun Int.foo() {
      
      // 用标签指向类 A 的实例 
      var a = this@A
      
      // 用标签指向类 B 的实例 
      var b = this@B
      
      // 当前扩展函数实例
      var c = this
     
      println(a)
      println(b)
      println(c)
    }
  }
}

可见度修饰符

private

使用 private 修饰的顶层函数、顶层类或接口只能在同一个文件中被访问。

在一个类、接口或对象,任何 private 函数或属性只对其他该类的成员变量、接口或对象可见:

class A {
  private var a = 10
  
  private fun b() { }
  
  fun c() {
    var c = a
    var d = b()
  }
}

class B {
  // 编译时异常,a 为私有变量
  var e = A().a
  
  // 编译时异常,a 为私有函数
  var f = A().b()
}

Protected

顶层函数、类、接口或对象不能声明为 protected 。任何在类或接口中声明为 protected 的函数或属性只对该类或接口的成员变量以及子类可见。

open class A {
  protected var a = 10
}

class B {
  // 编译时异常,a 为 protected
  var a = A().a
}

class C: A() {
  var c = a
}

Internal

同模块的类可见。这里的模块指的是 Intellij 中定义的 Maven 或 Gradle 模块。

控制流语句

在 Kotlin 中,if...else 和 try...catch 代码块是表达式,这些代码块的运算结果可以赋值给变量、用作函数的返回值或当做参数传给一个函数:

var a = 10
val b = if (a == 10) true else false

val success = try {
  true
} catch (e: IOException) {
  false
}

if...else 作为返回值和参数:

fun isZero(x: Int): Boolean {
  return if (x == 0) true else false
}

isZero(if (a == 10) 0 else 10)

Kotlin 中没有定义三元运算符:

// 编译时异常,不支持三元运算符
var b = a == 10 ? 5 : 0

当使用 if 作为表达式时,要包含 else 语句 :

// 编译时异常
var a = if (1 == 2) true

// 编译通过
var b = if (1 == 2) true else false

空值处理

一个能赋为空值变量需要在变量类型后加上问号:

// 编译时异常
var str1: String = null

// 编译通过
var str2: String? = null

类型检查与转换

Kotlin 中可以用 is 运算符替换 Java 的 instnaceof 运算符,在经过 is 运算符类型检查通过后编译器都会帮我们自动转换类型:

fun isString(any: Any) {  
  var str = ""
  // 经过 is 检查后编译器会帮我们将 Any 转成 String 
  str = if (any is String) any else "b"
}

如果要进行转换的是布尔值那么 if...else 都可以省了:

fun isEmptyString(any: Any) : Boolean {
  return any is String && any.length == 0
}

在这个例子中,函数测试我们没有一个字符串,或者我们有,然后必须为空

显式转换

若要显式地进行类型转换,可以用 as 运算符,转换不成功时会抛出一个类型转换异常:

val str1 = "123" as String

// elvis 安全转换,转换不成功会返回 null 字符串
val str2 = 123 as? String

// 类型转换异常
val str3 = 123 as String

when 表达式

Kotlin 的 when 表达式有两种形式。第一种类似于 Java 的 switch 表达式。第二种用一系列条件判断语句条件进行语句切换。

when(value)

类似于 switch ,需要一个常量:

fun foo(x: Int) {
  when (x) {
    0 -> println("x is zero")
    1 -> println("x is 1")
    else -> println("x is neither 0 or 1")
  }
}

Kotlin 中的 when 表达式类似于 if...else 代码块可以赋值给变量、作为参数或返回值:

fun isMinOrMax(x: Int): Boolean {
  val isZero = when (x) {
    Int.MIN_VALUE -> true
    Int.MAX_VALUE -> false
    else -> false
  }
}

当两个分支的代码是一样时,可以用逗号将它们分隔开以执行同一个分支:

fun isZeroOrOne(x: Int) : Boolean {
  return when (x) {
    0, 1 -> true
    else -> false
  }
}

没有参数的 when

没有参数的 when 代码块,用条件语句进行切换:

fun foo (x: Int, y: Int) {
  when {
    x < y -> println("x 小于 y")
    x > y -> println("x 大于 y")
    else -> println("x 等于 y0")
  }  
}

函数返回值

在 Kotlin 中,当函数返回值不为空时,需要声明函数返回值的类型,当允许返回空值时,要加问号表明:

fun foo1() {
  // 编译时异常,没声明返回值类型
  return ""
}

fun foo2(): String {
  // 编译时异常,没有加问号
  return null
}

fun foo3(a: Int, b: Int) : Int {
  // 编译成功
  return a + b
}

如果从闭包中返回一个值,需要用标签修饰返回类型,否则返回的是外部函数,标签可以是显式也可以是隐式的:

fun printUtilStop1() {
  val list = listOf("a", "b")
  list.forEach stop@ {
    // 显式标签
    if (it == "stop") return@stop
    else println(it)
  }
}

fun printUtilStop2() {
  val list = listOf("a", "b")
  list.forEach {
    // 隐式标签
    if (it == "stop") return@forEach
    else println(it)
  }
}

类型层级

在 Kotlin 的 Any 类型相当于 Java 的 Object,它是所有对象的父类。Any 中定义了 toString,hashCode 以及 equal 等函数。它也定义了apply 、let 和 to 等扩展方法。

Kotlin 的 Unit 类型相当于 Java 中的 void,Unit 与 void 的区别在于 Unit 是一个类而且是单例对象。

Kotlin 中的 Nothing 类型是所有类型的子类。同时它也是 elvis 运算符和 emptyList() 函数的实现。当函数没有返回值时可以返回
Nothing 类型:

fun reportError(): Nothing = throw RuntimeException()

fun a (x: Int): String{
  if (x > 10)
    return "OK"
  // 编译时异常,没有返回值
}

fun b (x: Int): String {
  if (x > 10)
    return "OK"
  
  // 自动返回 Nothing
  reportError()
}

参考资料

Kotlin 官方文档

Programming Kotlin

上一篇下一篇

猜你喜欢

热点阅读