kotlin 基本语法

330 阅读8分钟

TIP : www.kotlincn.net/docs/refere… 官方文档

Byte
Short
Int
Long
Float
Double Char(注:char不是数值类型,是一个独立数据类型
在 Java 中 char 是 int 的一部分,可以直接隐式转换成 int,
在 Kotlin 中 Char 不再是 Int 的一部分了,而是一个单独的数据类型,不可以直接转换。)
int? 带有?符号是会被装箱操作  Integer.

定义常量与变量

变量就是用来存储数据,而函数就是用来处理数据。
var <标识符> : <类型> = <初始化值>
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或配合?:做空判断处理  . 当一个引用可能为 null 值时, 对应的类型声明必须明确地标记为可为 null。

//类型后面加?表示可为空   会对变量做一次非空确认之后再调用方法
var age: String? = "23" 

//不做处理返回 null       先做非空确认 再调用 线程安全做法
val ages1 = age?.toInt()  

//抛出空指针异常          断言式  我保证这里的 view 一定是非空的,告诉编译器有问题我自己负责
val ages = age!!.toInt()

//age为空返回-1
val ages2 = age?.toInt() ?: -1

lateinit 延迟初始化   以上是关于空的处理

类型检测及自动类型转换

使用 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
}

区间

区间表达式由具有操作符形式 .. 的 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<String>) {
    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,值相等 
}

For循环

for (item in collection) print(item)

for (item: Int in ints) {
    // ……
}
for ((index, value) in array.withIndex()) {
    // 下标及值
    println("the element at $index is $value")
}


getter 和 setter

v不允许设置setter函数,因为它是只读的 lateinit 提供一种可以延迟初始化

var allByDefault: Int? // 错误: 需要一个初始化语句, 默认实现了 getter 和 setter 方法
var initialized = 1    // 类型为 Int, 默认实现了 getter 和 setter
val simple: Int?       // 类型为 Int ,默认实现 getter ,但必须在构造函数中初始化
val inferredType = 1   // 类型为 Int 类型,默认实现 getter

实例:tip       field 关键词只能用于属性的访问器,

class Person {    var lastName: String = "zhang"
        get() = field.toUpperCase()   // 将变量赋值后转换为大写
        set

    var no: Int = 100
        get() = field                // 后端变量
        set(value) {
            if (value < 10) {       // 如果传入的值小于 10 返回该值
                field = value
            } else {
                field = -1         // 如果传入的值大于等于 10 返回 -1
            }
        }

    var heiht: Float = 145.4f
        private set
}

field:
定义 var no: Int 变量,当你写出 no = ... 这种形式的时候,这个等于号都会被编译器翻译成调用 setter 方法;
而同样,在任何位置引用变量时,只要出现 no 变量的地方都会被编译器翻译成 getter 方法。那么问题就来了,
当你在 setter 方法内部写出 no = ... 时,相当于在 setter 方法中调用 setter 方法,形成递归,进而形成死循环

类的属性

1 val site = Runoob() // Kotlin 中没有 new 关键字
2 class Person constructor(firstName: String) {
    // 可以有一个 主构造器,以及一个或多个次构造器,主构造器是类头部的一部分,位于类名称之后
}
3 class Person { 
    //次构造函数
    constructor(parent: Person) {
       parent.children.add(this)     }
}
4 class DontCreateMe private constructor () {
    //私有的
}

5 class Runoob  constructor(name: String) {  // 类名为 Runoob
    // 大括号内是类体构成    var url: String = "http://www.runoob.com"
    var country: String = "CN"
    var siteName = name

    init {
        println("初始化网站名: ${name}")
    }
    // 次构造函数
    constructor (name: String, alexa: Int) : this(name) {
        println("Alexa 排名 $alexa")
    }

    fun printTest() {
        println("我是类的函数")
    }
}

fun main(args: Array<String>) {
    val runoob =  Runoob("my is mrwang", 10000)
    println(runoob.siteName)
    println(runoob.url)
    println(runoob.country)
    runoob.printTest()
}
输出结果:

初始化网站名: my is mrwang
Alexa 排名 10000
my is mrwang
http://www.runoob.com
CN
我是类的函数

内部类

内部类使用 inner 关键字来表示。

类的修饰符

abstract    // 抽象类  
final       // 类不可继承,默认属性
enum        // 枚举类
open        // 类可继承,类默认是final的
annotation  // 注解类
访问权限修饰符
private    // 仅在同一个文件中可见
protected  // 同一个文件中或子类可见
public     // 所有调用的地方都可见
internal   // 同一个模块中可见
如果没有显式指定修饰符的话,默认可见性是 public

继承

  • 使用 open 关键字进行修饰。 所有类都有一个共同的超类Any 默认超类 
  • Any 有三个方法:equals()hashCode()toString()
  •  子类有主构造函数
open class Person(var name : String, var age : Int){// 基类

}

class Student(name : String, age : Int, var no : String, var score : Int) : Person(name, age) {

}
  • 子类没有主构造函数
次类没有主构造函数,则必须在每一个二级构造函数中用 super 关键字初始化基类
class Student : Person {

    constructor(ctx: Context) : super(ctx) {
    } 

    constructor(ctx: Context, attrs: AttributeSet) : super(ctx,attrs) {
    }
}

重写

使用fun声明函数时,此函数默认为final修饰,不能被子类重写。如果允许子类重写该函数,那么就要手动添加 open 修饰它, 子类重写方法使用 override 关键词

/**用户基类**/
open class Person{
    open fun study(){       // 允许子类重写
        println("我毕业了")
    }
}

/**子类继承 Person 类**/
class Student : Person() {

    override fun study(){    // 重写方法
        println("我在读大学")
    }
}

有多个相同的方法(继承或者实现自其他类,如A、B类),则必须要重写该方法,使用super范型去选择性地调用父类的实现

open class A {
    open fun f () { print("A") }
    fun a() { print("a") }
}

interface B {
    fun f() { print("B") } //接口的成员变量默认是 open 的
    fun b() { print("b") }
}

class C() : A() , B{
    override fun f() {
        super<A>.f()//调用 A.f()
        super<B>.f()//调用 B.f()
    }
}

子类继承父类时,不能有跟父类同名的变量,除非父类中该变量为 private,或者父类中该变量为 open 并且子类用 override 关键字重写:

open class Person(var name: String, var age: Int) {    
    open var sex: String = "unknow"    
    init {        
        println("基类初始化")    
    }
}
// 子类的主构造方法的 name 前边也加了 var,这是不允许的,会报'name' hides member of supertype and needs 'override' modifier
class Student(var name: String, age: Int, var no: String, var score: Int) : Person(name, age) {
    override var sex: String = "male"
}

属性重写

属性重写使用 override 关键字

接口

interface MyInterface {
    fun bar()    // 未实现
    fun foo() {  //已实现
      // 可选的方法体
      println("foo")
    }
}

扩展函数

可以在已有类中添加新的方法,不会对原类做修改,扩展函数定义形式:

class User(var name:String){

}

/**扩展函数**/
fun User.Print(){
    this指向User作用域
    print("用户名 $name")
}

!!!!! 真是些神奇的骚操作-_-

扩展函数和成员函数一致,则使用该函数时,会优先使用成员函数
class C {
    fun foo() { println("成员函数") }
}

fun C.foo() { println("扩展函数") }

扩展函数是静态解析的

扩展函数是静态解析的,并不是接收者类型的虚拟成员,在调用扩展函数时,具体被调用的的是哪一个函数,由调用函数的的对象表达式来决定的,而不是动态的类型决定的:

open class C

class D: C()

fun C.foo() = "c"   // 扩展函数 foo

fun D.foo() = "d"   // 扩展函数 foo

fun printFoo(c: C) {
    println(c.foo())  // 类型是 C 类
}

fun main(arg:Array<String>){
    printFoo(D())
}
实例执行输出结果为:  c

扩展属性

扩展属性允许定义在类或者kotlin文件中,不允许定义在函数中。
初始化属性因为属性没有后端字段(backing field),所以不允许被初始化
只能由显式提供的 getter/setter 定义。
val <T> List<T>.lastIndex: Int
    get() = size - 1

val Foo.bar = 1 // 错误:扩展属性不能有初始化器 只能被声明为 val。

扩展的作用域

扩展函数或属性定义在顶级包下:
package foo.bar

fun Baz.goo() { …… } 

对象表达式

对象表达式实现一个匿名内部类的对象用于方法的参数中
window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) {
        // ...
    }
    override fun mouseEntered(e: MouseEvent) {
        // ...
    }
})
对象可以继承于某个基类,或者实现其他接口
open class A(x: Int) {
    public open val y: Int = x
}

interface B {……}

val ab: A = object : A(1), B {
    override val y = 15
}


通过对象表达式可以越过类的定义直接得到一个对象
fun main(args: Array<String>) {
    val site = object {
        var name: String = "this name"
        var url: String = "www.baidu.com"
    }
    println(site.name)
    println(site.url)
}

对象声明

Kotlin 使用 object 关键字来声明一个对象。

Kotlin 中我们可以方便的通过对象声明来获得一个单例。

object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) {
        // ……
    }

    val allDataProviders: Collection<DataProvider>
        get() = // ……
}

使用:DataProviderManager.registerDataProvider(……)

对象表达式和对象声明之间的语义差异

对象表达式和对象声明之间有一个重要的语义差别:

对象表达式是在使用他们的地方立即执行的

对象声明是在第一次被访问到时延迟初始化的

伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配

类委托

即一个类中定义的方法实际是调用另一个类的对象的方法来实现的。

// 创建接口
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<String>) {
    val b = BaseImpl(10)
    Derived(b).print() // 输出 10
}

属性委托

属性委托指的是一个类的某个属性值不是在类中直接进行定义,而是将其托付给一个代理类,从而实现对该类的属性统一管理。

val/var <属性名>: <类型> by <表达式>

by 关键字之后的表达式就是委托, 属性的 get() 方法(以及set() 方法)将被委托给这个对象的 getValue() 和 setValue() 方法。属性委托不必实现任何接口, 但必须提供 getValue() 函数(对于 var属性,还需要 setValue() 函数)。

参数 thisRef 为进行委托的类的对象,property为进行委托的类的属性

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<String>) {
    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 <T> 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lamda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。(没搞懂它的作用 就没写例子了)

可观察属性 Observable

observable 可以用于实现观察者模式

Delegates.observable() 函数接受两个参数: 第一个是初始化值, 第二个是属性值变化事件的响应器(handler)。

class User {
    var name: String by Delegates.observable("初始值") {
        prop, old, new ->
        println("旧值:$old -> 新值:$new")
    }
}

fun main(args: Array<String>) {
    val user = User()
    user.name = "第一次赋值"
    user.name = "第二次赋值"
}

续……