Kotlin 入门(一):基本语法
前言:
在android studio中已经支持kotlin,只需要在android studio中下载kotlin的插件,就可以使用kotlin进行app的开发了,本系列主要讲一些kotlin的语法规范,案例及使用场景。
基本类型
在 Kotlin 中,所有东西都是对象,在这个意义上讲所以我们可以在任何变量上调用成员函数和属性。有些类型是内置的,因为他们的实现是优化过的。但是用户看起来他们就像普通的类
数字
Kotlin 处理数字在某种程度上接近 Java,但是并不完全相同。例如,对于数字没有隐式拓宽转换(如 Java 中 int 可以隐式转换为 long)
Kotlin 提供了如下的内置类型来表示数字(与 Java 很相近):
注意在 Kotlin 中字符不是数字,kotlin支持十进制(123),十六进制(0x0F),二进制(0b00001011),不支持八进制
从kotlin1.1起,支持使用下划线使数字常量更易读
val oneMillion = 1_000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010
在 Java 平台数字是物理存储为 JVM 的原生类型,除非我们需要一个可空的引用(如 Int? )或泛型。 后者情况下会把数字装箱
注意数字装箱不必保留同一性:
val a: Int = 10000
print(a === a) // 输出“true”
val boxedA: Int? = a
val anotherBoxedA: Int? = a
print(boxedA === anotherBoxedA) // !!!输出“false”!!!
另一方面,它保留了相等性:
val a: Int = 10000
print(a == a) // 输出“true”
val boxedA: Int? = a
val anotherBoxedA: Int? = a
print(boxedA == anotherBoxedA) // 输出“true”
因此较小的类型不能隐式转换为较大的类型。 这意味着在不进行显式转换的情况下我们不能 把 Byte 型值赋给一个 Int 变量。
val b: Byte = 1 // OK, 字面值是静态检测的
val i: Int = b // 错误
我们可以显式转换来拓宽数字
val i: Int = b.toInt() // OK: 显式拓宽
基本语法
1.包的声明
和java包的生命相同,都处于源文件顶部
package abc.demo
import java.util.*
import net.println.kotlin.simple.A as B //给A类起别名叫B
2.定义函数
使用关键字fun,返回写在函数声明的右边,以:连接,且kotlin的函数声明和java有些不同,kotlin可以将表达式作为函数体、返回值类型自动推断的函数,例如下面定义sum函数两种方式结果是一样的
//普通的写法
fun sum(a: Int, b: Int): Int {
return a + b
}
//简写
fun sum(a: Int, b: Int) = a + b
//kotlin的main函数
fun main(args: Array<String>) {
print("sum of 3 and 5 is ")
println(sum(3, 5))
}
kotlin函数没有返回值,可以返回Unit,相当于java的void,也可以省略不写
fun printSum(a: Int, b: Int): Unit {
println("sum of $a and $b is ${a + b}")
}
//不写Unit
fun printSum(a: Int, b: Int) {
println("sum of $a and $b is ${a + b}")
}
kotlin支持函数的默认参数
fun foo(a: Int = 0, b: String = "") { …… }
3.定义常量/变量
kotlin提供两种定义变量的方式,分别使用关键字val和var,val定义只读常量,相当于java中使用final修饰的属性,var定义变量
fun main(args: Array<String>) {
//val
val a: Int = 1 // 立即赋值
val b = 2 // 自动推断出 `Int` 类型
val c: Int // 如果没有初始值类型不能省略
c = 3 // 明确赋值
//var
var x = 5 // 自动推断出 `Int` 类型
x += 1
}
4.关于注释
与java相同,唯一的区别在于,Kotlin 的块注释可以嵌套
5.字符串模板
kotlin提供了相对java更灵活的字符串处理方式,字符串模版,在字符串中使用$a符直接输出字符串a的值,如果是表达式,可以使用${a+1}的方式
val a=10
val s1 = "a is $a"
val s2="a+1 is ${a+1}"
println(s1)
println(s2)
6.条件表达式
基本使用与java相同,不同在于,kotlin的条件表达式可以充当返回值,例如:
fun maxOf(a: Int, b: Int) = if (a > b) a else b
7.可空类型检测
kotlin可以大幅度减少空指针错误的发生,当某个变量的值可以为 null 的时候,必须在声明处的类型后添加 ? 来标识该引用可为
空,不为null的时候,才会调用对象的方法
val student:Student?=null
student=Student("xiao ming")
val name:String=student?.name?:""
//相当于
val name:String=if(student!=null){
student.name
}else{
""
}
//可以用于函数返回值
fun parseInt(str: String): Int? {
// ……
}
8.类型检测及自动类型转换
is 运算符检测一个表达式是否某类型的一个实例。 如果一个不可变的局部变量或属性已经判断出为某类型,那么检测后的分支中可以直接当作该类型使用,无需显式转换:
//Any相当于java的Object
fun getStringLength(obj: Any): Int? {
if (obj is String) {
// `obj` 在该条件分支内自动转换成 `String`
return obj.length
}
// 在离开类型检测分支后,`obj` 仍然是 `Any` 类型
return null
}
9.循环结构
for循环
fun main(args: Array<String>) {
val items = listOf("apple", "banana", "kiwi") //kotlin的一个函数
val map=mapOf("a" to "A", "b" to "B","c" to "C") //to 只是用来定义map的键值对
for (item in items) { //in是kotlin的一个关键字,用来检测某个数字是否在指定区间内
println(item)
}
//遍历map的key和value,k 、 v 可以改成任意名字,后面会讲到原理
for ((k, v) in map) {
println("$k -> $v")
}
}
Break 和 Continue 标签:
loop@ for(i in 1..100) {
for (j in 1..100) {
if (……) break@loop
}
}
while循环
fun main(args: Array<String>) {
val items = listOf("apple", "banana", "kiwi")
var index = 0
while (index < items.size) {
println("item at $index is ${items[index]}")
index++
}
}
10.when分支结构表达式
与java中的switch case类似,不同点在于when中可以是任意类型参数,java中只能是整数类型,而且when中内容可以省略,判断放在分支中,比java更加灵活,而且when表达式跟if表达式一样,可以直接当返回值
fun describe(obj: Any): String =
when (obj) {
1 -> "One"
"Hello" -> "Greeting"
is Long -> "Long"
!is String -> "Not a string"
else -> "Unknown"
}
fun main(args: Array<String>) {
println(describe(1))
println(describe("Hello"))
println(describe(1000L))
println(describe(2))
println(describe("other"))
}
is表达式用在分支结构
when (x) {
is Foo //-> ……
is Bar //-> ……
else //-> ……
}
11.区间(range)
上面在for循环的时候用到过in关键字,它的作用是检测某个数字是否在指定区内,后面通常跟一个具体的容器类,或者和..,until,搭配使用
检测某个数字是否在指定区间:
fun main(args: Array<String>) {
val x = 10
val y = 9
if (x in 1..y+1) {
println("fits in range $x")
}
}
区间迭代:
fun main(args: Array<String>) {
for (x in 1..5) {
print(x)
}
}
数列迭代:
fun main(args: Array<String>) {
for (x in 1..10 step 2) { //step用于指定步长
print(x)
}
for (x in 9 downTo 0 step 3) { //downTo 用于指定递减
print(x)
}
}
在分支结构中使用in:
fun main(args: Array<String>) {
val items = setOf("apple", "banana", "kiwi")
when { //when中内容省略了
"orange" in items -> println("juicy")
"apple" in items -> println("apple is fine too")
}
}
until:与..类似,但是..表示闭区间,而until表示左闭右开区间
for (i in 1 until 100) { …… }// 半开区间:不包含 100
12.“try/catch”表达式
fun test() {
val result = try {
count()
} catch (e: ArithmeticException) {
throw IllegalStateException(e)
}
}
13.运算
Kotlin支持数字运算的标准集,运算被定义为相应的类成员(但编译器会将函数调用优化为相 应的指令).
对于位运算,没有特殊字符来表示,而只可用中缀方式调用命名函数,例如:
val x = (1 shl 2) and 0x000FF000
这是完整的位运算列表(只用于 Int 和 Long ):
shl(bits) – 有符号左移 (Java 的 << )
shr(bits) – 有符号右移 (Java 的 >> )
ushr(bits) – 无符号右移 (Java 的 >>> )
and(bits) – 位与
or(bits) – 位或
xor(bits) – 位异或
inv() – 位非
14.字符
字符用 Char 类型表示。它们不能直接当作数字
fun check(c: Char) {
if (c == 1) { // 错误:类型不兼容 // ……
} }
字符字面值用单引号括起来: '1' 。 特殊字符可以用反斜杠转义。 支持这几个转义序 列: \t 、 \b 、 \n 、 \r 、 ' 、 " 、 \ 和 $ 。 编码其他字符要用 Unicode 转义序 列语法: '\uFF00'
我们可以显式把字符转换为 Int 数字:
fun decimalDigitValue(c: Char): Int {
if (c !in '0'..'9')
throwIllegalArgumentException("Out of range")
return c.toInt() - '0'.toInt() // 显式转换为数字
}
当需要可空引用时,像数字、字符会被装箱。装箱操作不会保留同一性。
15.数组
数组在 Kotlin 中使用 Array 类来表示,我们可以使用库函数 arrayOf() 来创建一个数组并传递元素值给它,这样 arrayOf(1, 2, 3) 创建了 array [1, 2, 3]。 或者,库函数 arrayOfNulls() 可以用于创建一个指定大小、元素都 为空的数组。
注意: 与 Java 不同的是,Kotlin 中数组是不型变的(invariant)。这意味着 Kotlin 不让我们把 Array<String> 赋值给 Array<Any> ,以防止可能的运行时失败.
Kotlin 也有无装箱开销的专门的类来表示原生类型数组: ByteArray 、 ShortArray 、 IntArray 等等。这些类和 Array 并没有继承关系,但是 它们有同样的方法 属性集。它们也都有相应的工厂方法:
val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]
类和接口
class的定义
命名风格:
1.使用驼峰法命名(并避免命名含有下划线)
2.类型名以大写字母开头
3.方法和属性以小写字母开头
4.使用 4 个空格缩进
5.公有函数应撰写函数文档,这样这些文档才会出现在 Kotlin Doc 中
kotlin类的定义比java要简洁很多,有少数几个参数的类可以写成一行:
class Person(id: Int, name: String)
具有较长类头的类应该格式化,以使每个主构造函数参数位于带有缩进的单独一行中。 此外,右括号应该另起一行。如果我们使用继承,那么超类构造函数调用或者实现接口列表 应位于与括号相同的行上:
class Person(
id: Int,
name: String,
surname: String
) : Human(id, name) {
// ……
}
对于多个接口,应首先放置超类构造函数调用,然后每个接口应位于不同的行中:
class Person(
id: Int,
name: String,
surname: String
) : Human(id, name),
KotlinMaker {
// ……
}
对于Java用户:Kotlin 中外部类不能访问内部类的 private 成员。
如果你覆盖一个 protected 成员并且没有显式指定其可见性,该成员还会是 protected 可 见性。
构造函数
在 Kotlin 中的一个类可以有一个主构造函数和一个或多个次构造函,如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor 关键字。
要指定一个类的的主构造函数的可见性,使用以下语法(注意你需要添加一个 显式 constructor 关键字):
class C private constructor(a: Int) { …… }
这里的构造函数是私有的。默认情况下,所有构造函数都是 public ,这实际上 等于类可见 的地方它就可见(即 一个 internal 类的构造函数只能 在相同模块内可见).
class Customer(name: String) {
init{logger.info("Customer initialized with value ${name}")}
}
主构造函数中声明的属性可以是 可变的( var )或只读的( val )。 如果构造函数有注解或可见性修饰符,这个 constructor 关键字是必需的,并且 这些修饰符 在它前面:
主构造函数不能包含任何的代码。初始化的代码可以放 到以 init 关键字作为前缀的初始化 块(initializer blocks)中:
class Customer public @Inject constructor(name: String) { …… }
次构造函数
类也可以声明前缀有 constructor 的次构造函数:
class Person {
constructor(parent: Person) {
parent.children.add(this)
}
}
如果类有一个主构造函数,每个次构造函数需要委托给主构造函数, 可以直接委托或者通过 别的次构造函数间接委托。委托到同一个类的另一个构造函数 用 this 关键字即可:
class Person(val name: String) {
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
} }
继承
在 Kotlin 中所有类都有一个共同的超类 Any ,这对于没有超类型声明的类是默认超类, Any 不是 java.lang.Object ;尤其是,它除了equals() 、 hashCode() 和 toString() 外没 有任何成员。
默认情况下,在 Kotlin 中所有的类都是final, open 标注允许其他类 从这个类继承。 如果函数没有标注 open ,则子类中不允许定义相同签名的函数.
如果一个类从它的直接超类继承相同成员的多个实 现, 它必须覆盖这个成员并提供其自己的实现(也许用继承来的其中之一)。 为了表示采用 从哪个超类型继承的实现,我们使用由尖括号中超类型名限定的 super ,如 super<Base> :
抽象类
类和其中的某些成员可以声明为abstract ,不需要用 open标注一个抽象类或者函数——因为这不言而喻。
伴生对象
与 Java 或 C# 不同,在 Kotlin 中类没有静态方法。在大多数情况下,它建议简单地使用 包级 函数。如果在你的类内声明了一个伴生对象, 你就可以使用像在 Java/C# 中调用静态 方法相同的语法来调用其成员,只使用类名 作为限定符。
属性和字段
Kotlin的类可以有属性。 属性可以用关键字 var 声明为可变的,否则使用只读关键字 val 。
Getters 和 Setters:
var <propertyName>[:<PropertyType>] [=<property_initializer>]
[<getter>]
[<setter>]
var stringRepresentation: String
get() = this.toString()
set(value) { setDataFromString(value) // 解析字符串并赋值给其他属性 }
幕后字段:
Kotlin 中类不能有字段。然而,当使用自定义访问器时,有时有一个幕后字段(backing field)有时是必要的。为此 Kotlin 提供 一个自动幕后字段,它可通过使用 field 标识符访 问。
var counter = 0
// 此初始器值直接写入到幕后字段
set(value){
if (value >= 0)
field = value
}
幕后属性:
private var _table: Map<String, Int>?=null
public val table: Map<String, Int>
get() {
if (_table == null) {
_table = HashMap()
// 类型参数已推断出
}
return _table ?: throw AssertionError("Set to null by another thread")
}
编译期常量:
已知值的属性可以使用 const 修饰符标记为 编译期常量。 这些属性需要满足以下要求:
位于顶层或者是 object 的一个成员
用 String 或原生类型 值初始化
没有自定义 getter
惰性初始化属性:
一般地,属性声明为非空类型必须在构造函数中初始化。 然而,这经常不方便。例如:属性 可以通过依赖注入来初始化,为处理这种情况,你可以用 lateinit 修饰符标记该属性:
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject() }
@Test fun test() {
subject.method() // 直接解引用 } }
该修饰符只能用于在类体中(不是在主构造函数中)声明的 var 属性,并且仅 当该属性没 有自定义 getter 或 setter 时。该属性必须是非空类型,并且不能是 原生类型
模块
可见性修饰符 internal 意味着该成员只在相同模块内可见。更具体地说, 一个模块是编译 在一起的一套 Kotlin 文件:
一个 IntelliJ IDEA 模块;
一个 Maven 或者 Gradle 项目;
一次 <kotlinc> Ant 任务执行所编译的一套文件。