什么是kotlin?kotlin由JetBrains开发的一套多平台应用开发语言,它可以编译成java字节码,也可以编译成javascript, 方便在JVM 设备上运行,kotlin并支持android平台开发。大家可能在想,why?又要学习新语言?哈哈,万物皆有交替,从
C
toC++
toJava
toHTML
toJS
toLua
toObject-c
toSwift
toDart
都写过来了,一句老话,技多不压身。来看看官方对Kotlin的评价。
使用 Kotlin 更快速地编写更棒的 Android 应用。Kotlin 是一种现代的静态设置类型编程语言,可以提高开发者的工作效率,并提升开发者的工作愉悦度。
(确实很愉悦.....愉悦到爆....哈哈)
Kotlin 的现代语言功能让您可以专注于表达自己的想法,少编写样板代码。编写的代码越少,也就意味着需要测试和维护的代码越少。
(对焦虑者来说,能适当减轻一定的心里压力.....)
使用 Kotlin 提升您的应用品质。@Nullable 和 @NonNull 类型已纳入到 Kotlin 的类型系统中,以帮助您避免出现 NullPointerExceptions。Kotlin 还有许多其他语言功能,可帮助您避免常见的编程错误。
(可以啊,小子,越来越健壮了)
通过 Kotlin 代码调用 Java 代码,或者通过 Java 代码调用 Kotlin 代码。Kotlin 与 Java 编程语言之间完全可以互操作,因此您可以视需要在项目中添加任意数量的 Kotlin 代码。
(这个调用相比oc与swift还是简便很多啊....)
废话不多说,来,开始我们的kotlin代码之旅。
Kotlin 基础语法
Kotlin 文件以 .kt 为后缀。
package com.runoob.main
import java.util.*
fun test() {
//coding
}
class Runoob {
//coding
}
kotlin源文件不需要相匹配的目录和包,源文件可以放在任何文件目录。
以上例中 test() 的全名是 com.runoob.main.test、Runoob 的全名是 com.runoob.main.Runoob。
如果没有指定包,默认为 default 包。
函数定义
- 函数定义使用关键字 fun,参数格式为:参数 : 类型
fun sum(a: Int, b: Int): Int {
// Int 参数,返回值 Int
return a + b
}
- 表达式作为函数体,返回类型自动推断:
fun sum(a: Int, b: Int) = a + b
// public 方法则必须明确写出返回类型
public fun sum(a: Int, b: Int): Int = a + b
- 无返回值的函数(类似Java中的void):
fun printSum(a: Int, b: Int): Unit {
print(a + b)
}
// 如果是返回 Unit类型,则可以省略(对于public方法也是这样):
public fun printSum(a: Int, b: Int) {
print(a + b)
}
- 可变长参数函数,函数的变长参数可以用 vararg 关键字进行标识:
fun vars(vararg v:Int){
for(vt in v){
print(vt)
}
}
// 测试
fun main(args: Array) {
vars(1,2,3,4,5) // 输出12345
}
- lambda(匿名函数):
// 测试
fun main(args: Array) {
val sumLambda: (Int, Int) -> Int = {
x,y -> x+y
}
println(sumLambda(1,2)) // 输出 3
}
定义常量与变量
可变变量定义:var 关键字
var <标识符> : <类型> = <初始化值>
不可变变量定义:val 关键字,只能赋值一次的变量(类似Java中final修饰的变量)
val <标识符> : <类型> = <初始化值>
常量与变量都可以没有初始化值,但是在引用前必须初始化
编译器支持自动类型判断,即声明时可以不指定类型,由编译器判断。
val a: Int = 1
val b = 1 // 系统自动推断变量类型为Int
val c: Int // 如果不在声明时初始化则必须提供变量类型
c = 1 // 明确赋值
var x = 5 // 系统自动推断变量类型为Int
x += 1 // 变量可修改
字符串模板
$ 表示一个变量名或者变量值
$varName 表示变量值
${varName.fun()} 表示变量的方法返回值:
var a = 1
// 模板中的简单名称:
val s1 = "a is $a" a = 2
// 模板中的任意表达式:
val s2 = "${s1.replace("is", "was")}, but now is $a"
NULL检查机制
Kotlin的空安全设计对于声明可为空的参数,在使用时要进行空判断处理,有两种处理方式,字段后加!!像Java一样抛出空异常,另一种字段后加?可不做处理返回值为 null或配合?:做空判断处理
//类型后面加?表示可为空
var age: String? = "23"
//抛出空指针异常
val ages = age!!.toInt()
//不做处理返回 null
val ages1 = age?.toInt()
//age为空返回-1
val ages2 = age?.toInt() ?: -1
当一个引用可能为 null 值时, 对应的类型声明必须明确地标记为可为 null。
当 str 中的字符串内容不是一个整数时, 返回 null:
fun parseInt(str: String): Int? {
// ...
}
- 以下实例演示如何使用一个返回值可为 null 的函数:
fun main(args: Array) {
if (args.size < 2) {
print("Two integers expected")
return
}
val x = parseInt(args[0])
val y = parseInt(args[1])
// 直接使用 `x * y` 会导致错误, 因为它们可能为 null.
if (x != null && y != null) {
// 在进行过 null 值检查之后, x 和 y 的类型会被自动转换为非null 变量
print(x * y)
}
}
类型检测及自动类型转换
我们可以使用 is 运算符检测一个表达式是否某类型的一个实例(类似于Java中的instanceof关键字)。
fun getStringLength(obj: Any): Int? {
if (obj is String) {
// 做过类型判断以后,obj会被系统自动转换为String类型
return obj.length
}
//在这里还有一种方法,与Java中instanceof不同,使用!is
// if (obj !is String){
// // XXX
// }
// 这里的obj仍然是Any类型的引用
return null
}
或者
fun getStringLength(obj: Any): Int? {
if (obj !is String)
return null
// 在这个分支中, `obj` 的类型会被自动转换为 `String`
return obj.length
}
甚至还可以
fun getStringLength(obj: Any): Int? {
//在 && 运算符的右侧, obj 的类型会被自动转换为 String
if (obj is String && obj.length > 0)
return obj.length
return null
}
区间
区间表达式由具有操作符形式 .. 的 rangeTo 函数辅以 in 和 !in 形成。
区间是为任何可比较类型定义的,但对于整型原生类型,它有一个优化的实现。以下是使用区间的一些示例:
for (i in 1..4) print(i) //输出1234
for (i in 4..1) print(i) // 什么都不输出
if (i in 1..10) {
//等同于 1 <= i && i <= 10
println(i)
}
// 使用 step 指定步长
for (i in 1..4 step 2) print(i) // 输出“13”
for (i in 4 downTo 1 step 2) print(i) // 输出“42”
// 使用 until 函数排除结束元素
for (i in 1 until 10) {
// i in [1, 10) 排除了 10
println(i)
}
实例测试
fun main(args: Array) {
print("循环输出:")
for (i in 1..4) print(i) // 输出“1234”
println("\n----------------")
print("设置步长:")
for (i in 1..4 step 2) print(i) // 输出“13”
println("\n----------------")
print("使用 downTo:")
for (i in 4 downTo 1 step 2) print(i)
// 输出“42”
println("\n----------------")
print("使用 until:")
// 使用 until 函数排除结束元素
for (i in 1 until 4) {
// i in [1, 4) 排除了 4
print(i)
}
println("\n----------------")
}
输出结果:
循环输出:1234
----------------
设置步长:13
----------------
使用 downTo:42
----------------
使用 until:123
----------------
Kotlin 基本数据类型
Kotlin 的基本数值类型包括 Byte、Short、Int、Long、Float、Double 等。不同于Java的是,字符不属于数值类型,是一个独立的数据类型。
字面常量
下面是所有类型的字面常量:
- 十进制:123
- 长整型以大写的 L 结尾:123L
- 16 进制以 0x 开头:0x0F
- 2 进制以 0b 开头:0b00001011
- 注意:8进制不支持
Kotlin 同时也支持传统符号表示的浮点数值:
- Doubles 默认写法: 123.5, 123.5e10
- Floats 使用 f 或者 F 后缀:123.5f
你可以使用下划线使数字常量更易读:
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
比较两个数字
Kotlin 中没有基础数据类型,只有封装的数字类型,你每定义的一个变量,其实 Kotlin 帮你封装了一个对象,这样可以保证不会出现空指针。数字类型也一样,所有在比较两个数字的时候,就有比较数据大小和比较两个对象是否相同的区别了。
在 Kotlin 中,三个等号 === 表示比较对象地址,两个 == 表示比较两个值大小。
fun main(args: Array) {
val a: Int = 10000
println(a === a) // true,值相等,对象地址相等
//经过了装箱,创建了两个不同的对象
val boxedA: Int? = a
val anotherBoxedA: Int? = a
//虽然经过了装箱,但是值是相等的,都是10000
println(boxedA === anotherBoxedA) // false,值相等,对象地址不一样
println(boxedA == anotherBoxedA) // true,值相等
}
类型转换
由于不同的表示方式,较小类型并不是较大类型的子类型,较小的类型不能隐式转换为较大的类型。 这意味着在不进行显式转换的情况下我们不能把 Byte 型值赋给一个 Int 变量。
val b: Byte = 1 // OK, 字面值是静态检测的
val i: Int = b // 错误
我们可以代用其toInt()方法。
val b: Byte = 1 // OK, 字面值是静态检测的
val i: Int = b.toInt() // OK
每种数据类型都有下面的这些方法,可以转化为其它的类型:
toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char
有些情况下也是可以使用自动类型转化的,前提是可以根据上下文环境推断出正确的数据类型而且数学操作符会做相应的重载。例如下面是正确的:
val l = 1L + 3 // Long + Int => Long
位操作符
对于Int和Long类型,还有一系列的位操作符可以使用,分别是:
shl(bits) – 左移位 (Java’s <<)
shr(bits) – 右移位 (Java’s >>)
ushr(bits) – 无符号右移位 (Java’s >>>)
and(bits) – 与
or(bits) – 或
xor(bits) – 异或
inv() – 反向
字符
和 Java 不一样,Kotlin 中的 Char 不能直接和数字操作,Char 必需是单引号 ' 包含起来的。比如普通字符 '0','a'。
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')
throw IllegalArgumentException("Out of range")
return c.toInt() - '0'.toInt() // 显式转换为数字
}
当需要可空引用时,像数字、字符会被装箱。装箱操作不会保留同一性。
数组
数组用类 Array 实现,并且还有一个 size 属性及 get 和 set 方法,由于使用 [] 重载了 get 和 set 方法,所以我们可以通过下标很方便的获取或者设置数组对应位置的值。
数组的创建两种方式:一种是使用函数arrayOf();另外一种是使用工厂函数。如下所示,我们分别是两种方式创建了两个数组:
fun main(args: Array) {
//[1,2,3]
val a = arrayOf(1, 2, 3)
//[0,2,4]
val b = Array(3, { i -> (i * 2) })
//读取数组内容
println(a[0]) // 输出结果:1
println(b[1]) // 输出结果:2
}
如上所述,[] 运算符代表调用成员函数 get() 和 set()。
注意: 与 Java 不同的是,Kotlin 中数组是不型变的(invariant)。
除了类Array,还有ByteArray, ShortArray, IntArray,用来表示各个类型的数组,省去了装箱操作,因此效率更高,其用法同Array一样:
val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]
字符串
和 Java 一样,String 是不可变的。方括号 [] 语法可以很方便的获取字符串中的某个字符,也可以通过 for 循环来遍历:
for (c in str) {
println(c)
}
Kotlin 支持三个引号 """ 扩起来的字符串,支持多行字符串,比如:
fun main(args: Array) {
val text = """ 多行字符串 多行字符串 """
println(text) // 输出有一些前置空格
}
String 可以通过 trimMargin() 方法来删除多余的空白。
fun main(args: Array) {
val text = """ |多行字符串 |菜鸟教程 |多行字符串 |Runoob """.trimMargin()
println(text) // 前置空格删除了
}
默认 | 用作边界前缀,但你可以选择其他字符并作为参数传入,比如 trimMargin(">")。
字符串模板
字符串可以包含模板表达式 ,即一些小段代码,会求值并把结果合并到字符串中。 模板表达式以美元符($)开头,由一个简单的名字构成:
fun main(args: Array) {
val i = 10
val s = "i = $i"
// 求值结果为 "i = 10"
println(s)
}
或者用花括号扩起来的任意表达式:
fun main(args: Array) {
val s = "runoob"
val str = "$s.length is ${s.length}"
// 求值结果为 "runoob.length is 6"
println(str)
}
原生字符串和转义字符串内部都支持模板。 如果你需要在原生字符串中表示字面值 $ 字符(它不支持反斜杠转义),你可以用下列语法:
fun main(args: Array) {
val price = """ ${'$'}9.99 """
println(price) // 求值结果为 $9.99
}
Kotlin 类和对象
类定义
Kotlin 类可以包含:构造函数和初始化代码块、函数、属性、内部类、对象声明。
Kotlin 中使用关键字 class 声明类,后面紧跟类名:
class Runoob {
// 类名为 Runoob
// 大括号内是类体构成
}
我们也可以定义一个空类:
class Empty
可以在类中定义成员函数:
class Runoob() {
fun foo() {
print("Foo")
} // 成员函数
}
类的属性
属性定义
类的属性可以用关键字 var 声明为可变的,否则使用只读关键字 val 声明为不可变。
class Runoob {
var name: String = ……
var url: String = ……
var city: String = ……
}
我们可以像使用普通函数那样使用构造函数创建类实例:
val site = Runoob() // Kotlin 中没有 new 关键字
要使用一个属性,只要用名称引用它即可
site.name // 使用 . 号来引用
site.url
Koltin 中的类可以有一个 主构造器,以及一个或多个次构造器,主构造器是类头部的一部分,位于类名称之后:
class Person constructor(firstName: String) {
}
如果主构造器没有任何注解,也没有任何可见度修饰符,那么constructor关键字可以省略。
class Person(firstName: String) {
}
getter 和 setter
属性声明的完整语法:
var [: ] [= ]
[]
[]
getter 和 setter 都是可选
如果属性类型可以从初始化语句或者类的成员函数中推断出来,那就可以省去类型,val不允许设置setter函数,因为它是只读的。
var allByDefault: Int? // 错误: 需要一个初始化语句, 默认实现了 getter 和 setter 方法
var initialized = 1 // 类型为 Int, 默认实现了 getter 和 setter
val simple: Int? // 类型为 Int ,默认实现 getter ,但必须在构造函数中初始化
val inferredType = 1 // 类型为 Int 类型,默认实现 getter
主构造器
主构造器中不能包含任何代码,初始化代码可以放在初始化代码段中,初始化代码段使用 init 关键字作为前缀。
class Person constructor(firstName: String) {
init {
println("FirstName is $firstName")
}
}
注意:主构造器的参数可以在初始化代码段中使用,也可以在类主体n定义的属性初始化代码中使用。 一种简洁语法,可以通过主构造器来定义属性并初始化属性值(可以是var或val):
class People(val firstName: String, val lastName: String) {
//...
}
如果构造器有注解,或者有可见度修饰符,这时constructor关键字是必须的,注解和修饰符要放在它之前。
次构造函数
类也可以有二级构造函数,需要加前缀 constructor:
class Person {
constructor(parent: Person) {
parent.children.add(this)
}
}
如果类有主构造函数,每个次构造函数都要,或直接或间接通过另一个次构造函数代理主构造函数。在同一个类中代理另一个构造函数使用 this 关键字:
class Person(val name: String) {
constructor (name: String, age:Int) : this(name) {
// 初始化...
}
}
如果一个非抽象类没有声明构造函数(主构造函数或次构造函数),它会产生一个没有参数的构造函数。构造函数是 public 。如果你不想你的类有公共的构造函数,你就得声明一个空的主构造函数:
class DontCreateMe private constructor () {
}
注意:在 JVM 虚拟机中,如果主构造函数的所有参数都有默认值,编译器会生成一个附加的无参的构造函数,这个构造函数会直接使用默认值。这使得 Kotlin 可以更简单的使用像 Jackson 或者 JPA 这样使用无参构造函数来创建类实例的库。
class Customer(val customerName: String = "")
抽象类
抽象是面向对象编程的特征之一,类本身,或类中的部分成员,都可以声明为abstract的。抽象成员在类中不存在具体的实现。
注意:无需对抽象类或抽象成员标注open注解。
open class Base {
open fun f() {
//coding
}
}
abstract class Derived : Base() {
override abstract fun f()
}
kotlin 委托
委托模式是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。
Kotlin 直接支持委托模式,更加优雅,简洁。Kotlin 通过关键字 by 实现委托。
类委托
类的委托即一个类中定义的方法实际是调用另一个类的对象的方法来实现的。
以下实例中派生类 Derived 继承了接口 Base 所有方法,并且委托一个传入的 Base 类的对象来执行这些方法。
// 创建接口
interface Base {
fun print()
}
// 实现此接口的被委托的类
class BaseImpl(val x: Int) : Base {
override fun print() {
print(x)
}
}
// 通过关键字 by 建立委托类
class Derived(b: Base) : Base by b
fun main(args: Array) {
val b = BaseImpl(10)
Derived(b).print() // 输出 10
}
在 Derived 声明中,by 子句表示,将 b 保存在 Derived 的对象实例内部,而且编译器将会生成继承自 Base 接口的所有方法, 并将调用转发给 b。
属性委托
属性委托指的是一个类的某个属性值不是在类中直接进行定义,而是将其托付给一个代理类,从而实现对该类的属性统一管理。
属性委托语法格式:
val/var <属性名>: <类型> by <表达式>
- var/val:属性类型(可变/只读)
- 属性名:属性名称
- 类型:属性的数据类型
- 表达式:委托代理类
by 关键字之后的表达式就是委托, 属性的 get() 方法(以及set() 方法)将被委托给这个对象的 getValue() 和 setValue() 方法。属性委托不必实现任何接口, 但必须提供 getValue() 函数(对于 var属性,还需要 setValue() 函数)。
定义一个被委托的类
该类需要包含 getValue() 方法和 setValue() 方法,且参数 thisRef 为进行委托的类的对象,prop 为进行委托的属性的对象。
import kotlin.reflect.KProperty
// 定义包含属性委托的类
class Example {
var p: String by Delegate()
}
// 委托的类
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, 这里委托了 ${property.name} 属性"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$thisRef 的 ${property.name} 属性赋值为 $value")
}
}
fun main(args: Array) {
val e = Example() println(e.p) // 访问该属性,调用 getValue() 函数 e.p = "Runoob"
// 调用 setValue() 函数
println(e.p)
}
输出结果为:
Example@433c675d, 这里委托了 p 属性
Example@433c675d 的 p 属性赋值为 Runoob
Example@433c675d, 这里委托了 p 属性
标准委托
Kotlin 的标准库中已经内置了很多工厂方法来实现属性的委托。
延迟属性 Lazy
lazy() 是一个函数, 接受一个 Lambda 表达式作为参数, 返回一个 Lazy
val lazyValue: String by lazy {
println("computed!") // 第一次调用输出,第二次调用不执行 "Hello"
}
fun main(args: Array) {
println(lazyValue) // 第一次执行,执行两次输出表达式
println(lazyValue) // 第二次执行,只输出返回值
}
执行输出结果:
computed!
Hello
Hello
可观察属性 Observable
observable 可以用于实现观察者模式。
Delegates.observable() 函数接受两个参数: 第一个是初始化值, 第二个是属性值变化事件的响应器(handler)。
在属性赋值后会执行事件的响应器(handler),它有三个参数:被赋值的属性、旧值和新值:
import kotlin.properties.Delegates
class User {
var name: String by Delegates.observable("初始值") {
prop, old, new ->
println("旧值:$old -> 新值:$new")
}
}
fun main(args: Array) {
val user = User()
user.name = "第一次赋值"
user.name = "第二次赋值"
}
执行输出结果:
旧值:初始值 -> 新值:第一次赋值
旧值:第一次赋值 -> 新值:第二次赋值
好了,半小时也过了,看了这么多,也知道你们有点困了,“困”就对了,learning确实能帮助治愈失眠。言归正传,以上技能你们都掌握了吗?
(Time for a true display of skill ----Ezreal)